Skip to content

Commit 2f5271a

Browse files
committed
Update
1 parent 62db552 commit 2f5271a

File tree

53 files changed

+1221
-293
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1221
-293
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
# Mono auto generated files
1717
mono_crash.*
1818

19+
# Coverage
20+
coverage/
21+
1922
# Build results
2023
[Dd]ebug/
2124
[Dd]ebugPublic/

Directory.Build.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</Target>
2929

3030
<!-- Define NETSTANDARD2_1_OR_GREATER for .NET Standard 2.1 targets and above -->
31-
<PropertyGroup Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net7.0'">
31+
<PropertyGroup Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net8.0'">
3232
<DefineConstants>NETSTANDARD2_1_OR_GREATER</DefineConstants>
3333
</PropertyGroup>
3434

Lepo.i18n.sln

+8-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lepo.i18n.DependencyInjecti
2828
EndProject
2929
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lepo.i18n.DependencyInjection.UnitTests", "tests\Lepo.i18n.DependencyInjection.UnitTests\Lepo.i18n.DependencyInjection.UnitTests.csproj", "{274021AA-F5A4-4A02-B8B7-224B254031A1}"
3030
EndProject
31-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lepo.i18n.Wpf.UnitTests", "tests\Lepo.i18n.Wpf.UnitTests\Lepo.i18n.Wpf.UnitTests.csproj", "{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2}"
31+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lepo.i18n.Wpf.UnitTests", "tests\Lepo.i18n.Wpf.UnitTests\Lepo.i18n.Wpf.UnitTests.csproj", "{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2}"
32+
EndProject
33+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lepo.i18n.Json.UnitTests", "tests\Lepo.i18n.Json.UnitTests\Lepo.i18n.Json.UnitTests.csproj", "{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}"
3234
EndProject
3335
Global
3436
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -64,6 +66,10 @@ Global
6466
{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
6567
{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
6668
{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2}.Release|Any CPU.Build.0 = Release|Any CPU
69+
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
70+
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
71+
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
72+
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}.Release|Any CPU.Build.0 = Release|Any CPU
6773
EndGlobalSection
6874
GlobalSection(SolutionProperties) = preSolution
6975
HideSolutionNode = FALSE
@@ -72,6 +78,7 @@ Global
7278
{98789E33-F370-46FF-B4B6-CEAA81E9C2C3} = {AA93DD64-B88F-46ED-9981-EABA0F3C3E95}
7379
{274021AA-F5A4-4A02-B8B7-224B254031A1} = {AA93DD64-B88F-46ED-9981-EABA0F3C3E95}
7480
{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2} = {AA93DD64-B88F-46ED-9981-EABA0F3C3E95}
81+
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE} = {AA93DD64-B88F-46ED-9981-EABA0F3C3E95}
7582
EndGlobalSection
7683
GlobalSection(ExtensibilityGlobals) = postSolution
7784
SolutionGuid = {BF9C3C0B-D0EC-4FA3-A0D5-A8F70572737A}

src/Lepo.i18n.DependencyInjection/Lepo.i18n.DependencyInjection.csproj

-8
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@
44
<PackageId>Lepo.i18n.DependencyInjection</PackageId>
55
<TargetFrameworks>netstandard2.0;netstandard2.1;net462;net6.0;net8.0</TargetFrameworks>
66
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
7-
<IsTrimmable>true</IsTrimmable>
8-
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
9-
</PropertyGroup>
10-
11-
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0'">
12-
<PublishAot>true</PublishAot>
13-
<StripSymbols>true</StripSymbols>
14-
<OptimizationPreference>Speed</OptimizationPreference>
157
</PropertyGroup>
168

179
<!-- Necessary polyfills -->

src/Lepo.i18n.DependencyInjection/ServiceCollectionExtensions.cs

+4-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
44
// All Rights Reserved.
55

6-
namespace Lepo.i18n;
6+
namespace Lepo.i18n.DependencyInjection;
77

88
/// <summary>
99
/// Provides extension methods for the <see cref="IServiceCollection"/> interface.
@@ -25,15 +25,11 @@ Action<LocalizationBuilder> configure
2525

2626
configure(builder);
2727

28-
LocalizationProvider localizer =
29-
new(CultureInfo.CurrentCulture, builder.GetLocalizations());
30-
LocalizationProvider.SetInstance(localizer);
28+
LocalizationProviderFactory.SetInstance(builder.Build());
3129

32-
_ = services.AddSingleton<ILocalizationProvider>(
33-
(_) => LocalizationProvider.GetInstance()!
34-
);
30+
_ = services.AddSingleton(_ => LocalizationProviderFactory.GetInstance()!);
3531
_ = services.AddTransient<ILocalizationCultureManager, LocalizationCultureManager>();
36-
_ = services.AddTransient<IStringLocalizer, StringLocalizer>();
32+
_ = services.AddTransient<IStringLocalizer, StaticStringLocalizer>();
3733

3834
return services;
3935
}

src/Lepo.i18n.DependencyInjection/StringLocalizer.cs src/Lepo.i18n.DependencyInjection/StaticStringLocalizer.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
44
// All Rights Reserved.
55

6-
namespace Lepo.i18n;
6+
namespace Lepo.i18n.DependencyInjection;
77

88
/// <summary>
99
/// Provides functionality to localize strings.
1010
/// </summary>
11-
public class StringLocalizer(
11+
public class StaticStringLocalizer(
1212
ILocalizationProvider localizations,
1313
ILocalizationCultureManager cultureManager
1414
) : IStringLocalizer
@@ -37,9 +37,8 @@ ILocalizationCultureManager cultureManager
3737
public IEnumerable<LocalizedString> GetAllStrings(bool _)
3838
{
3939
return localizations
40-
.Get(cultureManager.GetCulture())
41-
?.Strings.Select(x => new LocalizedString(x.Key, x.Value ?? x.Key))
42-
?? Enumerable.Empty<LocalizedString>();
40+
.GetLocalizationSet(cultureManager.GetCulture(), default)
41+
?.Strings.Select(x => new LocalizedString(x.Key, x.Value ?? x.Key)) ?? [];
4342
}
4443

4544
private LocalizedString LocalizeString(string name, object[] placeholders)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
4+
// All Rights Reserved.
5+
6+
using Lepo.i18n.Json.Models;
7+
8+
namespace Lepo.i18n.Json.Converters;
9+
10+
/// <summary>
11+
/// JSON converter for the ITranslationsContainer interface.
12+
/// </summary>
13+
internal class TranslationsContainerConverter : JsonConverter<ITranslationsContainer>
14+
{
15+
public override ITranslationsContainer? Read(
16+
ref Utf8JsonReader reader,
17+
Type typeToConvert,
18+
JsonSerializerOptions options
19+
)
20+
{
21+
JsonElement jsonObject = JsonDocument.ParseValue(ref reader).RootElement;
22+
23+
string version = "1.0";
24+
25+
foreach (JsonProperty property in jsonObject.EnumerateObject())
26+
{
27+
if (string.Equals(property.Name, "Version", StringComparison.OrdinalIgnoreCase))
28+
{
29+
version = property.Value.GetString() ?? version;
30+
break;
31+
}
32+
}
33+
34+
return new TranslationsContainer(new Version(version).ToString());
35+
}
36+
37+
public override void Write(
38+
Utf8JsonWriter writer,
39+
ITranslationsContainer? value,
40+
JsonSerializerOptions options
41+
)
42+
{
43+
JsonSerializer.Serialize(
44+
writer,
45+
new TranslationsContainer(value?.Version ?? "1.0"),
46+
options
47+
);
48+
}
49+
}

src/Lepo.i18n.Json/GlobalUsings.cs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
4+
// All Rights Reserved.
5+
6+
global using System;
7+
global using System.Collections.Generic;
8+
global using System.Globalization;
9+
global using System.IO;
10+
global using System.Reflection;
11+
global using System.Text.Json;
12+
global using System.Text.Json.Serialization;

src/Lepo.i18n.Json/Lepo.i18n.Json.csproj

-8
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@
44
<PackageId>Lepo.i18n.Json</PackageId>
55
<TargetFrameworks>netstandard2.0;netstandard2.1;net462;net6.0;net8.0</TargetFrameworks>
66
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
7-
<IsTrimmable>true</IsTrimmable>
8-
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
9-
</PropertyGroup>
10-
11-
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0'">
12-
<PublishAot>true</PublishAot>
13-
<StripSymbols>true</StripSymbols>
14-
<OptimizationPreference>Speed</OptimizationPreference>
157
</PropertyGroup>
168

179
<!-- Necessary polyfills -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
4+
// All Rights Reserved.
5+
6+
using Lepo.i18n.IO;
7+
using Lepo.i18n.Json.Converters;
8+
using Lepo.i18n.Json.Models;
9+
using Lepo.i18n.Json.Models.v1;
10+
11+
namespace Lepo.i18n.Json;
12+
13+
/// <summary>
14+
/// Provides extension methods for the <see cref="LocalizationBuilder"/> class.
15+
/// </summary>
16+
public static class LocalizationBuilderExtensions
17+
{
18+
private static readonly JsonSerializerOptions DefaultJsonSerializerOptions =
19+
new()
20+
{
21+
PropertyNameCaseInsensitive = true,
22+
AllowTrailingCommas = true,
23+
Converters = { new TranslationsContainerConverter() }
24+
};
25+
26+
/// <summary>
27+
/// Loads localization data from a JSON file in the calling assembly.
28+
/// </summary>
29+
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localization data to.</param>
30+
/// <param name="path">The path to the JSON file.</param>
31+
/// <param name="culture">The culture of the localization data.</param>
32+
/// <returns>The updated <see cref="LocalizationBuilder"/>.</returns>
33+
public static LocalizationBuilder FromJson(
34+
this LocalizationBuilder builder,
35+
string path,
36+
CultureInfo culture
37+
)
38+
{
39+
return builder.FromJson(Assembly.GetCallingAssembly(), path, culture);
40+
}
41+
42+
/// <summary>
43+
/// Loads localization data from a JSON file in the specified assembly.
44+
/// </summary>
45+
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localization data to.</param>
46+
/// <param name="assembly">The assembly that contains the JSON file.</param>
47+
/// <param name="path">The path to the JSON file.</param>
48+
/// <param name="culture">The culture of the localization data.</param>
49+
/// <returns>The updated <see cref="LocalizationBuilder"/>.</returns>
50+
public static LocalizationBuilder FromJson(
51+
this LocalizationBuilder builder,
52+
Assembly assembly,
53+
string path,
54+
CultureInfo culture
55+
)
56+
{
57+
if (!path.EndsWith(".json"))
58+
{
59+
throw new ArgumentException(
60+
$"Parameter {nameof(path)} in {nameof(FromJson)} must be path to the JSON file."
61+
);
62+
}
63+
64+
string? contents = EmbeddedResourceReader.ReadToEnd(path, assembly);
65+
66+
if (contents is null)
67+
{
68+
throw new LocalizationBuilderException(
69+
$"Resource {path} not found in assembly {assembly.FullName}."
70+
);
71+
}
72+
73+
Version schemaVersion =
74+
new(
75+
JsonSerializer
76+
.Deserialize<ITranslationsContainer>(contents, DefaultJsonSerializerOptions)
77+
?.Version ?? "1.0.0"
78+
);
79+
80+
if (!schemaVersion.Major.Equals(1))
81+
{
82+
throw new LocalizationBuilderException(
83+
$"Localization file with schema version \"{schemaVersion.ToString() ?? "unknown"}\" is not supported."
84+
);
85+
}
86+
87+
TranslationFile? translationFile = JsonSerializer.Deserialize<TranslationFile>(
88+
contents,
89+
DefaultJsonSerializerOptions
90+
);
91+
92+
if (translationFile is null)
93+
{
94+
throw new LocalizationBuilderException("Unable to extract data from json file.");
95+
}
96+
97+
Dictionary<string, string> localizedStrings = new();
98+
99+
foreach (TranslationEntity localizedString in translationFile.Strings)
100+
{
101+
if (localizedStrings.ContainsKey(localizedString.Name))
102+
{
103+
throw new LocalizationBuilderException(
104+
$"The {path} file contains duplicate \"{localizedString.Name}\" keys."
105+
);
106+
}
107+
108+
localizedStrings.Add(localizedString.Name, localizedString.Value);
109+
}
110+
111+
builder.AddLocalization(
112+
new LocalizationSet(
113+
Path.GetFileNameWithoutExtension(path).Trim().ToLowerInvariant(),
114+
culture,
115+
localizedStrings!
116+
)
117+
);
118+
119+
return builder;
120+
}
121+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
4+
// All Rights Reserved.
5+
6+
namespace Lepo.i18n.Json.Models;
7+
8+
/// <summary>
9+
/// Defines a contract for a translations container with a schema version.
10+
/// </summary>
11+
internal interface ITranslationsContainer
12+
{
13+
/// <summary>
14+
/// Gets the version of the translation container.
15+
/// </summary>
16+
string Version { get; }
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
4+
// All Rights Reserved.
5+
6+
namespace Lepo.i18n.Json.Models;
7+
8+
internal record TranslationsContainer(string Version) : ITranslationsContainer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
4+
// All Rights Reserved.
5+
6+
namespace Lepo.i18n.Json.Models.v1;
7+
8+
/// <summary>
9+
/// Represents a translation entity with a name and a value.
10+
/// </summary>
11+
/// <param name="Name">The name of the translation entity.</param>
12+
/// <param name="Value">The value of the translation entity.</param>
13+
[method: JsonConstructor]
14+
internal readonly record struct TranslationEntity(string Name, string Value);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
4+
// All Rights Reserved.
5+
6+
namespace Lepo.i18n.Json.Models.v1;
7+
8+
/// <summary>
9+
/// Represents a translation file with a version and a collection of translation entities.
10+
/// </summary>
11+
/// <param name="Version">The version of the translation file.</param>
12+
/// <param name="Strings">The collection of translation entities in the file.</param>
13+
[method: JsonConstructor]
14+
internal sealed record TranslationFile(string Version, IEnumerable<TranslationEntity> Strings)
15+
: TranslationsContainer(Version);

0 commit comments

Comments
 (0)