Skip to content

Commit 5761982

Browse files
committed
1.0.5
1 parent 4db58b2 commit 5761982

5 files changed

Lines changed: 227 additions & 39 deletions

File tree

.github/workflows/release.yml

Lines changed: 121 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,46 @@ jobs:
3535
- name: Resolve release version
3636
shell: pwsh
3737
run: |
38-
$version = "${{ github.ref_name }}"
3938
$releaseTag = "${{ github.ref_name }}"
40-
if ($version -eq "main" -or $version -eq "master") {
41-
$version = "1.0.4"
42-
$releaseTag = "v$version"
39+
40+
function Get-ProjectProperty([string]$project, [string]$propertyName) {
41+
$xml = [xml](Get-Content $project)
42+
foreach ($group in $xml.Project.PropertyGroup) {
43+
$value = $group.$propertyName
44+
if (-not [string]::IsNullOrWhiteSpace($value)) {
45+
return $value.Trim()
46+
}
47+
}
48+
49+
return $null
50+
}
51+
52+
function Resolve-ProjectVersion([string]$project) {
53+
$version = Get-ProjectProperty $project "PackageVersion"
54+
if ([string]::IsNullOrWhiteSpace($version)) {
55+
$version = Get-ProjectProperty $project "Version"
56+
}
57+
58+
if ($version -match '^\$\((?<PropertyName>[^)]+)\)$') {
59+
$version = Get-ProjectProperty $project $Matches.PropertyName
60+
}
61+
62+
if ([string]::IsNullOrWhiteSpace($version)) {
63+
$version = Get-ProjectProperty $project "VsixPackageVersion"
64+
}
65+
66+
if ([string]::IsNullOrWhiteSpace($version)) {
67+
throw "Cannot resolve version from $project"
68+
}
69+
70+
return $version
4371
}
44-
if ($version.StartsWith("v")) {
45-
$version = $version.Substring(1)
72+
73+
if ("${{ github.ref_type }}" -ne "tag") {
74+
$releaseVersion = Resolve-ProjectVersion "src/Lizerium.Localization.Toolkit/Lizerium.Localization.Toolkit.csproj"
75+
$releaseTag = "v$releaseVersion"
4676
}
4777
48-
"PACKAGE_VERSION=$version" >> $env:GITHUB_ENV
4978
"RELEASE_TAG=$releaseTag" >> $env:GITHUB_ENV
5079
5180
- name: Restore solution
@@ -67,6 +96,35 @@ jobs:
6796
run: |
6897
New-Item -ItemType Directory -Force artifacts/nuget | Out-Null
6998
99+
function Get-ProjectProperty([string]$project, [string]$propertyName) {
100+
$xml = [xml](Get-Content $project)
101+
foreach ($group in $xml.Project.PropertyGroup) {
102+
$value = $group.$propertyName
103+
if (-not [string]::IsNullOrWhiteSpace($value)) {
104+
return $value.Trim()
105+
}
106+
}
107+
108+
return $null
109+
}
110+
111+
function Resolve-ProjectVersion([string]$project) {
112+
$version = Get-ProjectProperty $project "PackageVersion"
113+
if ([string]::IsNullOrWhiteSpace($version)) {
114+
$version = Get-ProjectProperty $project "Version"
115+
}
116+
117+
if ($version -match '^\$\((?<PropertyName>[^)]+)\)$') {
118+
$version = Get-ProjectProperty $project $Matches.PropertyName
119+
}
120+
121+
if ([string]::IsNullOrWhiteSpace($version)) {
122+
throw "Cannot resolve version from $project"
123+
}
124+
125+
return $version
126+
}
127+
70128
$projects = @(
71129
"src/Lizerium.Localization.Core/Lizerium.Localization.Core.csproj",
72130
"src/Lizerium.AI.LocalizationAssistant.Core/Lizerium.AI.LocalizationAssistant.Core.csproj",
@@ -78,10 +136,13 @@ jobs:
78136
)
79137
80138
foreach ($project in $projects) {
139+
$version = Resolve-ProjectVersion $project
140+
Write-Host "Packing $project version $version"
141+
81142
dotnet pack $project `
82143
-c $env:CONFIGURATION `
83-
-p:Version=$env:PACKAGE_VERSION `
84-
-p:PackageVersion=$env:PACKAGE_VERSION `
144+
-p:Version=$version `
145+
-p:PackageVersion=$version `
85146
-o artifacts/nuget `
86147
--no-restore `
87148
--no-build
@@ -92,29 +153,61 @@ jobs:
92153
run: |
93154
New-Item -ItemType Directory -Force artifacts/vsix | Out-Null
94155
156+
function Get-ProjectProperty([string]$project, [string]$propertyName) {
157+
$xml = [xml](Get-Content $project)
158+
foreach ($group in $xml.Project.PropertyGroup) {
159+
$value = $group.$propertyName
160+
if (-not [string]::IsNullOrWhiteSpace($value)) {
161+
return $value.Trim()
162+
}
163+
}
164+
165+
return $null
166+
}
167+
168+
function Resolve-VsixVersion([string]$project) {
169+
$version = Get-ProjectProperty $project "VsixPackageVersion"
170+
if ([string]::IsNullOrWhiteSpace($version)) {
171+
$version = Get-ProjectProperty $project "Version"
172+
}
173+
174+
if ($version -match '^\$\((?<PropertyName>[^)]+)\)$') {
175+
$version = Get-ProjectProperty $project $Matches.PropertyName
176+
}
177+
178+
if ([string]::IsNullOrWhiteSpace($version)) {
179+
throw "Cannot resolve VSIX version from $project"
180+
}
181+
182+
return $version
183+
}
184+
95185
$vsixProjects = @(
96186
@{
97187
Project = "src/Lizerium.Localization.Xaml.Vsix/Lizerium.Localization.Xaml.Vsix.csproj"
98-
Output = "src/Lizerium.Localization.Xaml.Vsix/bin/$env:CONFIGURATION/net472/Lizerium.Localization.Xaml.Vsix.$env:PACKAGE_VERSION.vsix"
99-
ReleaseName = "Lizerium.Localization.Xaml.Vsix-$env:RELEASE_TAG.vsix"
188+
PackageId = "Lizerium.Localization.Xaml.Vsix"
100189
},
101190
@{
102191
Project = "src/Lizerium.Localization.EditorHints/Lizerium.Localization.EditorHints.csproj"
103-
Output = "src/Lizerium.Localization.EditorHints/bin/$env:CONFIGURATION/net472/Lizerium.Localization.EditorHints.$env:PACKAGE_VERSION.vsix"
104-
ReleaseName = "Lizerium.Localization.EditorHints-$env:RELEASE_TAG.vsix"
192+
PackageId = "Lizerium.Localization.EditorHints"
105193
}
106194
)
107195
108196
foreach ($item in $vsixProjects) {
197+
$version = Resolve-VsixVersion $item.Project
198+
$output = "src/$($item.PackageId)/bin/$env:CONFIGURATION/net472/$($item.PackageId).$version.vsix"
199+
$releaseName = "$($item.PackageId)-v$version.vsix"
200+
Write-Host "Building $($item.Project) version $version"
201+
109202
msbuild $item.Project `
110203
/t:Build `
111204
/p:Configuration=$env:CONFIGURATION `
112205
/p:RestorePackages=false `
113-
/p:VsixPackageVersion=$env:PACKAGE_VERSION `
114-
/p:Version=$env:PACKAGE_VERSION `
206+
/p:VsixPackageVersion=$version `
207+
/p:Version=$version `
115208
/v:minimal
116209
117-
Copy-Item $item.Output "artifacts/vsix/$($item.ReleaseName)" -Force
210+
Copy-Item $output "artifacts/vsix/$releaseName" -Force
118211
}
119212
120213
- name: Restore publish projects
@@ -177,32 +270,30 @@ jobs:
177270
178271
Copy-Item artifacts/vsix/*.vsix artifacts/release/ -Force
179272
Copy-Item CHANGELOG.md artifacts/release/CHANGELOG.md -Force
273+
Copy-Item CHANGELOG.ru.md artifacts/release/CHANGELOG.ru.md -Force
180274
181275
Get-ChildItem artifacts/release -File |
182276
Get-FileHash -Algorithm SHA256 |
183277
ForEach-Object { "$($_.Hash) $(Split-Path $_.Path -Leaf)" } |
184278
Set-Content "artifacts/release/SHA256SUMS.txt" -Encoding UTF8
185279
280+
$vsixList = Get-ChildItem artifacts/vsix/*.vsix |
281+
Sort-Object Name |
282+
ForEach-Object { "- VSIX для Visual Studio 2022: ``$($_.Name)``" }
283+
$vsixNotes = $vsixList -join "`n"
284+
186285
$releaseNotes = @"
187286
## Lizerium.Localization.Toolkit $tag
188287
189-
See the full bilingual changelog in `CHANGELOG.md`.
288+
Полное описание изменений:
190289
191-
### Included
192-
193-
- NuGet packages: Toolkit, Core, AI Core, Generator, Analyzer, AI Analyzer, GUI
194-
- Visual Studio 2022 VSIX: `Lizerium.Localization.Xaml.Vsix-$tag.vsix`
195-
- Visual Studio 2022 VSIX: `Lizerium.Localization.EditorHints-$tag.vsix`
196-
- Windows x64 standalone GUI editor
197-
- Windows x64 published WPF sample application
198-
- Sample project source archive
199-
- SHA-256 checksums
290+
- [Русский CHANGELOG](https://github.com/${{ github.repository }}/blob/${{ github.sha }}/CHANGELOG.ru.md)
291+
- [English CHANGELOG](https://github.com/${{ github.repository }}/blob/${{ github.sha }}/CHANGELOG.md)
200292
201293
### Что входит
202294
203-
- NuGet-пакеты: Toolkit, Core, AI Core, Generator, Analyzer, AI Analyzer, GUI
204-
- VSIX для Visual Studio 2022: `Lizerium.Localization.Xaml.Vsix-$tag.vsix`
205-
- VSIX для Visual Studio 2022: `Lizerium.Localization.EditorHints-$tag.vsix`
295+
- NuGet-пакеты: Toolkit, Core, AI Core, Generator, Analyzer, AI Analyzer, GUI. Версия каждого пакета берется из его `.csproj`.
296+
$vsixNotes
206297
- Самостоятельная Windows x64 сборка GUI-редактора
207298
- Опубликованная Windows x64 сборка WPF sample-приложения
208299
- Архив исходников sample-проекта
@@ -227,4 +318,5 @@ jobs:
227318
artifacts/release/*.zip
228319
artifacts/release/*.vsix
229320
artifacts/release/CHANGELOG.md
321+
artifacts/release/CHANGELOG.ru.md
230322
artifacts/release/SHA256SUMS.txt

src/Lizerium.Localization.Generator/Node.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,26 @@ public void Write(StringBuilder builder, int indent, bool isRoot = false)
6464
foreach (var child in _children.Values)
6565
child.Write(builder, indent + 1);
6666

67+
var usedMemberNames = new HashSet<string>(_children.Keys, StringComparer.Ordinal);
68+
6769
foreach (var group in _entries.GroupBy(item => item.MethodName))
6870
{
6971
var index = 0;
72+
var baseMethodName = group.Key;
73+
74+
if (usedMemberNames.Contains(baseMethodName))
75+
baseMethodName += "Value";
76+
7077
// If sanitized keys collide, keep the first method name and suffix following overload names.
7178
foreach (var entry in group)
72-
WriteMethod(builder, indent + 1, entry, index++ == 0 ? entry.MethodName : entry.MethodName + index);
79+
{
80+
var methodName = index++ == 0 ? baseMethodName : baseMethodName + index;
81+
while (usedMemberNames.Contains(methodName))
82+
methodName += "Value";
83+
84+
usedMemberNames.Add(methodName);
85+
WriteMethod(builder, indent + 1, entry, methodName);
86+
}
7387
}
7488

7589
AppendIndent(builder, indent);

src/Lizerium.Localization.Xaml.Vsix/Lizerium.Localization.Xaml.Vsix.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
1010
<GeneratePkgDefFile>true</GeneratePkgDefFile>
1111
<TemplateOutputDirectory>$(IntermediateOutputPath)Templates</TemplateOutputDirectory>
12-
<VsixPackageVersion>1.0.4</VsixPackageVersion>
12+
<VsixPackageVersion>1.0.5</VsixPackageVersion>
1313
<TargetVsixContainerName>Lizerium.Localization.Xaml.Vsix.$(VsixPackageVersion).vsix</TargetVsixContainerName>
1414
<TargetVsixContainer>$(MSBuildProjectDirectory)\bin\$(Configuration)\$(TargetFramework)\$(TargetVsixContainerName)</TargetVsixContainer>
1515
<CopyBuildOutputToOutputDirectory>true</CopyBuildOutputToOutputDirectory>

src/Lizerium.Localization.Xaml.Vsix/XamlLocalizationSuggestedActionProvider.cs

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.Text.RegularExpressions;
1616
using System.Threading;
1717
using System.Threading.Tasks;
18+
using System.Xml.Linq;
1819

1920
using Lizerium.AI.LocalizationAssistant.Core.Clients.Ollama;
2021
using Lizerium.AI.LocalizationAssistant.Core.Components.Ollama;
@@ -420,11 +421,12 @@ public void Invoke(CancellationToken cancellationToken)
420421
try
421422
{
422423
cancellationToken.ThrowIfCancellationRequested();
423-
WriteResources(_candidate, cancellationToken);
424+
var key = ResolveGeneratorSafeKey(_candidate);
425+
WriteResources(_candidate, key, cancellationToken);
424426
cancellationToken.ThrowIfCancellationRequested();
425427

426428
var snapshot = _textBuffer.CurrentSnapshot;
427-
var replacement = "{" + XamlLocalizationMarkupPrefix + " " + _candidate.Key + "}";
429+
var replacement = "{" + XamlLocalizationMarkupPrefix + " " + key + "}";
428430
using var edit = _textBuffer.CreateEdit();
429431

430432
EnsureLocNamespace(snapshot, edit, _candidate.XamlPath);
@@ -441,7 +443,7 @@ public void Invoke(CancellationToken cancellationToken)
441443
}
442444

443445
edit.Apply();
444-
Log("Localized XAML value. File=" + _candidate.XamlPath + "; Key=" + _candidate.Key);
446+
Log("Localized XAML value. File=" + _candidate.XamlPath + "; Key=" + key);
445447
}
446448
catch (Exception ex)
447449
{
@@ -706,17 +708,97 @@ private static void RemoveLocNamespaceIfUnused(
706708
edit.Delete(new Span(locNamespaceMatch.Index, locNamespaceMatch.Length));
707709
}
708710

709-
private static void WriteResources(XamlLocalizationCandidate candidate, CancellationToken cancellationToken)
711+
private static void WriteResources(XamlLocalizationCandidate candidate, string key, CancellationToken cancellationToken)
710712
{
711713
ThreadHelper.ThrowIfNotOnUIThread();
712714
var resourcesDirectory = FindResourcesDirectory(candidate.XamlPath);
713715
Directory.CreateDirectory(resourcesDirectory);
714716
var localization = GetLocalization(candidate.Value, cancellationToken);
715717

716718
var writer = new ResxWriter();
717-
writer.AddOrUpdate(Path.Combine(resourcesDirectory, "Strings.en.resx"), candidate.Key, localization.En);
718-
writer.AddOrUpdate(Path.Combine(resourcesDirectory, "Strings.ru.resx"), candidate.Key, localization.Ru);
719-
Log("Updated resources. Directory=" + resourcesDirectory + "; Key=" + candidate.Key);
719+
writer.AddOrUpdate(Path.Combine(resourcesDirectory, "Strings.en.resx"), key, localization.En);
720+
writer.AddOrUpdate(Path.Combine(resourcesDirectory, "Strings.ru.resx"), key, localization.Ru);
721+
Log("Updated resources. Directory=" + resourcesDirectory + "; Key=" + key);
722+
}
723+
724+
private static string ResolveGeneratorSafeKey(XamlLocalizationCandidate candidate)
725+
{
726+
var resourcesDirectory = FindResourcesDirectory(candidate.XamlPath);
727+
var existingKeys = LoadExistingResourceKeys(resourcesDirectory);
728+
var key = MakeKeyGeneratorSafe(candidate.Key, existingKeys);
729+
var suffix = 2;
730+
731+
while (existingKeys.Contains(key) || HasPrefixCollision(key, existingKeys))
732+
key = MakeKeyGeneratorSafe(candidate.Key + "_" + suffix++.ToString(CultureInfo.InvariantCulture), existingKeys);
733+
734+
return key;
735+
}
736+
737+
private static string MakeKeyGeneratorSafe(string key, HashSet<string> existingKeys)
738+
{
739+
if (!HasPrefixCollision(key, existingKeys))
740+
return key;
741+
742+
var parts = key.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries).ToList();
743+
if (parts.Count == 0)
744+
return key;
745+
746+
for (var prefixLength = parts.Count - 1; prefixLength >= 1; prefixLength--)
747+
{
748+
var prefix = string.Join("_", parts.Take(prefixLength));
749+
if (!existingKeys.Contains(prefix))
750+
continue;
751+
752+
parts[prefixLength - 1] += "Value";
753+
return string.Join("_", parts);
754+
}
755+
756+
parts[parts.Count - 1] += "Value";
757+
return string.Join("_", parts);
758+
}
759+
760+
private static bool HasPrefixCollision(string key, HashSet<string> existingKeys)
761+
{
762+
foreach (var existing in existingKeys)
763+
{
764+
if (string.Equals(existing, key, StringComparison.OrdinalIgnoreCase))
765+
continue;
766+
767+
if (key.StartsWith(existing + "_", StringComparison.OrdinalIgnoreCase))
768+
return true;
769+
770+
if (existing.StartsWith(key + "_", StringComparison.OrdinalIgnoreCase))
771+
return true;
772+
}
773+
774+
return false;
775+
}
776+
777+
private static HashSet<string> LoadExistingResourceKeys(string resourcesDirectory)
778+
{
779+
var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
780+
if (!Directory.Exists(resourcesDirectory))
781+
return keys;
782+
783+
foreach (var file in Directory.EnumerateFiles(resourcesDirectory, "Strings*.resx", SearchOption.TopDirectoryOnly))
784+
{
785+
try
786+
{
787+
var document = XDocument.Load(file);
788+
foreach (var item in document.Root?.Elements("data") ?? Enumerable.Empty<XElement>())
789+
{
790+
var key = item.Attribute("name")?.Value;
791+
if (!string.IsNullOrWhiteSpace(key))
792+
keys.Add(key!);
793+
}
794+
}
795+
catch
796+
{
797+
Log("Could not read RESX keys. File=" + file);
798+
}
799+
}
800+
801+
return keys;
720802
}
721803

722804
private static LocalizationResult GetLocalization(string sourceText, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)