Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
pomianowski committed May 20, 2024
1 parent 62db552 commit 2f5271a
Show file tree
Hide file tree
Showing 53 changed files with 1,221 additions and 293 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
# Mono auto generated files
mono_crash.*

# Coverage
coverage/

# Build results
[Dd]ebug/
[Dd]ebugPublic/
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</Target>

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

Expand Down
9 changes: 8 additions & 1 deletion Lepo.i18n.sln
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lepo.i18n.DependencyInjecti
EndProject
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}"
EndProject
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}"
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}"
EndProject
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -64,6 +66,10 @@ Global
{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2}.Release|Any CPU.Build.0 = Release|Any CPU
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -72,6 +78,7 @@ Global
{98789E33-F370-46FF-B4B6-CEAA81E9C2C3} = {AA93DD64-B88F-46ED-9981-EABA0F3C3E95}
{274021AA-F5A4-4A02-B8B7-224B254031A1} = {AA93DD64-B88F-46ED-9981-EABA0F3C3E95}
{ED677E81-8D0F-42B2-A001-D37FEB3AAEA2} = {AA93DD64-B88F-46ED-9981-EABA0F3C3E95}
{46BB2EDE-5A5C-46BC-930F-F1FB6128B4AE} = {AA93DD64-B88F-46ED-9981-EABA0F3C3E95}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BF9C3C0B-D0EC-4FA3-A0D5-A8F70572737A}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@
<PackageId>Lepo.i18n.DependencyInjection</PackageId>
<TargetFrameworks>netstandard2.0;netstandard2.1;net462;net6.0;net8.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsTrimmable>true</IsTrimmable>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PublishAot>true</PublishAot>
<StripSymbols>true</StripSymbols>
<OptimizationPreference>Speed</OptimizationPreference>
</PropertyGroup>

<!-- Necessary polyfills -->
Expand Down
12 changes: 4 additions & 8 deletions src/Lepo.i18n.DependencyInjection/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

namespace Lepo.i18n;
namespace Lepo.i18n.DependencyInjection;

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

configure(builder);

LocalizationProvider localizer =
new(CultureInfo.CurrentCulture, builder.GetLocalizations());
LocalizationProvider.SetInstance(localizer);
LocalizationProviderFactory.SetInstance(builder.Build());

_ = services.AddSingleton<ILocalizationProvider>(
(_) => LocalizationProvider.GetInstance()!
);
_ = services.AddSingleton(_ => LocalizationProviderFactory.GetInstance()!);
_ = services.AddTransient<ILocalizationCultureManager, LocalizationCultureManager>();
_ = services.AddTransient<IStringLocalizer, StringLocalizer>();
_ = services.AddTransient<IStringLocalizer, StaticStringLocalizer>();

return services;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

namespace Lepo.i18n;
namespace Lepo.i18n.DependencyInjection;

/// <summary>
/// Provides functionality to localize strings.
/// </summary>
public class StringLocalizer(
public class StaticStringLocalizer(
ILocalizationProvider localizations,
ILocalizationCultureManager cultureManager
) : IStringLocalizer
Expand Down Expand Up @@ -37,9 +37,8 @@ ILocalizationCultureManager cultureManager
public IEnumerable<LocalizedString> GetAllStrings(bool _)
{
return localizations
.Get(cultureManager.GetCulture())
?.Strings.Select(x => new LocalizedString(x.Key, x.Value ?? x.Key))
?? Enumerable.Empty<LocalizedString>();
.GetLocalizationSet(cultureManager.GetCulture(), default)
?.Strings.Select(x => new LocalizedString(x.Key, x.Value ?? x.Key)) ?? [];
}

private LocalizedString LocalizeString(string name, object[] placeholders)
Expand Down
49 changes: 49 additions & 0 deletions src/Lepo.i18n.Json/Converters/TranslationsContainerConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

using Lepo.i18n.Json.Models;

namespace Lepo.i18n.Json.Converters;

/// <summary>
/// JSON converter for the ITranslationsContainer interface.
/// </summary>
internal class TranslationsContainerConverter : JsonConverter<ITranslationsContainer>
{
public override ITranslationsContainer? Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options
)
{
JsonElement jsonObject = JsonDocument.ParseValue(ref reader).RootElement;

string version = "1.0";

foreach (JsonProperty property in jsonObject.EnumerateObject())
{
if (string.Equals(property.Name, "Version", StringComparison.OrdinalIgnoreCase))
{
version = property.Value.GetString() ?? version;
break;
}
}

return new TranslationsContainer(new Version(version).ToString());
}

public override void Write(
Utf8JsonWriter writer,
ITranslationsContainer? value,
JsonSerializerOptions options
)
{
JsonSerializer.Serialize(

Check warning on line 43 in src/Lepo.i18n.Json/Converters/TranslationsContainerConverter.cs

View workflow job for this annotation

GitHub Actions / deploy

Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(Utf8JsonWriter, TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.

Check warning on line 43 in src/Lepo.i18n.Json/Converters/TranslationsContainerConverter.cs

View workflow job for this annotation

GitHub Actions / deploy

Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(Utf8JsonWriter, TValue, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.

Check warning on line 43 in src/Lepo.i18n.Json/Converters/TranslationsContainerConverter.cs

View workflow job for this annotation

GitHub Actions / deploy

Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(Utf8JsonWriter, TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.
writer,
new TranslationsContainer(value?.Version ?? "1.0"),
options
);
}
}
12 changes: 12 additions & 0 deletions src/Lepo.i18n.Json/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

global using System;
global using System.Collections.Generic;
global using System.Globalization;
global using System.IO;
global using System.Reflection;
global using System.Text.Json;
global using System.Text.Json.Serialization;
8 changes: 0 additions & 8 deletions src/Lepo.i18n.Json/Lepo.i18n.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@
<PackageId>Lepo.i18n.Json</PackageId>
<TargetFrameworks>netstandard2.0;netstandard2.1;net462;net6.0;net8.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsTrimmable>true</IsTrimmable>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PublishAot>true</PublishAot>
<StripSymbols>true</StripSymbols>
<OptimizationPreference>Speed</OptimizationPreference>
</PropertyGroup>

<!-- Necessary polyfills -->
Expand Down
121 changes: 121 additions & 0 deletions src/Lepo.i18n.Json/LocalizationBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

using Lepo.i18n.IO;
using Lepo.i18n.Json.Converters;
using Lepo.i18n.Json.Models;
using Lepo.i18n.Json.Models.v1;

namespace Lepo.i18n.Json;

/// <summary>
/// Provides extension methods for the <see cref="LocalizationBuilder"/> class.
/// </summary>
public static class LocalizationBuilderExtensions
{
private static readonly JsonSerializerOptions DefaultJsonSerializerOptions =
new()
{
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,
Converters = { new TranslationsContainerConverter() }
};

/// <summary>
/// Loads localization data from a JSON file in the calling assembly.
/// </summary>
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localization data to.</param>
/// <param name="path">The path to the JSON file.</param>
/// <param name="culture">The culture of the localization data.</param>
/// <returns>The updated <see cref="LocalizationBuilder"/>.</returns>
public static LocalizationBuilder FromJson(
this LocalizationBuilder builder,
string path,
CultureInfo culture
)
{
return builder.FromJson(Assembly.GetCallingAssembly(), path, culture);
}

/// <summary>
/// Loads localization data from a JSON file in the specified assembly.
/// </summary>
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localization data to.</param>
/// <param name="assembly">The assembly that contains the JSON file.</param>
/// <param name="path">The path to the JSON file.</param>
/// <param name="culture">The culture of the localization data.</param>
/// <returns>The updated <see cref="LocalizationBuilder"/>.</returns>
public static LocalizationBuilder FromJson(
this LocalizationBuilder builder,
Assembly assembly,
string path,
CultureInfo culture
)
{
if (!path.EndsWith(".json"))
{
throw new ArgumentException(
$"Parameter {nameof(path)} in {nameof(FromJson)} must be path to the JSON file."
);
}

string? contents = EmbeddedResourceReader.ReadToEnd(path, assembly);

if (contents is null)
{
throw new LocalizationBuilderException(
$"Resource {path} not found in assembly {assembly.FullName}."
);
}

Version schemaVersion =
new(
JsonSerializer

Check warning on line 75 in src/Lepo.i18n.Json/LocalizationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / deploy

Using member 'System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.

Check warning on line 75 in src/Lepo.i18n.Json/LocalizationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / deploy

Using member 'System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.
.Deserialize<ITranslationsContainer>(contents, DefaultJsonSerializerOptions)
?.Version ?? "1.0.0"
);

if (!schemaVersion.Major.Equals(1))
{
throw new LocalizationBuilderException(
$"Localization file with schema version \"{schemaVersion.ToString() ?? "unknown"}\" is not supported."
);
}

TranslationFile? translationFile = JsonSerializer.Deserialize<TranslationFile>(

Check warning on line 87 in src/Lepo.i18n.Json/LocalizationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / deploy

Using member 'System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.
contents,
DefaultJsonSerializerOptions
);

if (translationFile is null)
{
throw new LocalizationBuilderException("Unable to extract data from json file.");
}

Dictionary<string, string> localizedStrings = new();

foreach (TranslationEntity localizedString in translationFile.Strings)
{
if (localizedStrings.ContainsKey(localizedString.Name))
{
throw new LocalizationBuilderException(
$"The {path} file contains duplicate \"{localizedString.Name}\" keys."
);
}

localizedStrings.Add(localizedString.Name, localizedString.Value);
}

builder.AddLocalization(
new LocalizationSet(
Path.GetFileNameWithoutExtension(path).Trim().ToLowerInvariant(),
culture,
localizedStrings!
)
);

return builder;
}
}
17 changes: 17 additions & 0 deletions src/Lepo.i18n.Json/Models/ITranslationsContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

namespace Lepo.i18n.Json.Models;

/// <summary>
/// Defines a contract for a translations container with a schema version.
/// </summary>
internal interface ITranslationsContainer
{
/// <summary>
/// Gets the version of the translation container.
/// </summary>
string Version { get; }
}
8 changes: 8 additions & 0 deletions src/Lepo.i18n.Json/Models/TranslationsContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

namespace Lepo.i18n.Json.Models;

internal record TranslationsContainer(string Version) : ITranslationsContainer;
14 changes: 14 additions & 0 deletions src/Lepo.i18n.Json/Models/v1/TranslationEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

namespace Lepo.i18n.Json.Models.v1;

/// <summary>
/// Represents a translation entity with a name and a value.
/// </summary>
/// <param name="Name">The name of the translation entity.</param>
/// <param name="Value">The value of the translation entity.</param>
[method: JsonConstructor]
internal readonly record struct TranslationEntity(string Name, string Value);
15 changes: 15 additions & 0 deletions src/Lepo.i18n.Json/Models/v1/TranslationFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

namespace Lepo.i18n.Json.Models.v1;

/// <summary>
/// Represents a translation file with a version and a collection of translation entities.
/// </summary>
/// <param name="Version">The version of the translation file.</param>
/// <param name="Strings">The collection of translation entities in the file.</param>
[method: JsonConstructor]
internal sealed record TranslationFile(string Version, IEnumerable<TranslationEntity> Strings)
: TranslationsContainer(Version);
Loading

0 comments on commit 2f5271a

Please sign in to comment.