Skip to content

Commit

Permalink
Add typed localizers
Browse files Browse the repository at this point in the history
  • Loading branch information
pomianowski committed Jun 1, 2024
1 parent 180ea48 commit 3fbce60
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 65 deletions.
1 change: 0 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

<PropertyGroup>
<Version>2.0.0</Version>
<PackageVersion>2.0.0-rc.2</PackageVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ IHost host = Host.CreateDefaultBuilder()
{
services.AddStringLocalizer(b =>
{
b.FromResource(assembly, "Lepo.i18n.Resources.Test", new("pl-PL"));
b.FromResource(assembly, "Lepo.i18n.Resources.Test", new("en-US"));
b.FromResource<Translations>(new("pl-PL"));
b.FromYaml(assembly, "Lepo.i18n.Resources.Translations-en.yaml", new("en-US"));
});
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.DependencyInjection;

/// <summary>
/// Provides a dependency injection-based implementation of the <see cref="LocalizationBuilder"/> class.
/// </summary>
/// <remarks>
/// This class uses the .NET Core dependency injection framework to register localization services.
/// It allows the registration of resources for specific cultures using the <see cref="FromResource{TResource}"/> method.
/// </remarks>
public class DependencyInjectionLocalizationBuilder(IServiceCollection services)
: LocalizationBuilder
{
/// <inheritdoc />
public override void FromResource<TResource>(CultureInfo culture)
{
base.FromResource<TResource>(culture);

_ = services.AddSingleton<
IStringLocalizer<TResource>,
ProviderBasedStringLocalizer<TResource>
>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.DependencyInjection;

/// <summary>
/// Provides a provider-based implementation of the <see cref="IStringLocalizer{TResource}"/> interface.
/// </summary>
/// <typeparam name="TResource">The type of the resource used for localization.</typeparam>
/// <remarks>
/// This class uses an <see cref="ILocalizationProvider"/> to retrieve localization sets,
/// and an <see cref="ILocalizationCultureManager"/> to manage the current culture.
/// </remarks>
public class ProviderBasedStringLocalizer<TResource>(
ILocalizationProvider localizations,
ILocalizationCultureManager cultureManager
) : IStringLocalizer<TResource>
{
/// <inheritdoc />
public LocalizedString this[string name] => this[name, []];

/// <inheritdoc />
public LocalizedString this[string name, params object[] arguments] =>
LocalizeString(name, arguments);

/// <inheritdoc />
public IEnumerable<LocalizedString> GetAllStrings(bool _)
{
return localizations
.GetLocalizationSet(
cultureManager.GetCulture(),
typeof(TResource).FullName?.ToLower()
)
?.Strings.Select(x => new LocalizedString(x.Key, x.Value ?? x.Key)) ?? [];
}

/// <summary>
/// Fills placeholders in a string with the provided values.
/// </summary>
/// <param name="name">The string with placeholders.</param>
/// <param name="placeholders">The values to fill the placeholders with.</param>
/// <returns>The string with filled placeholders.</returns>
private LocalizedString LocalizeString(string name, object[] placeholders)
{
return new LocalizedString(
name,
FillPlaceholders(
GetAllStrings(true).FirstOrDefault(x => x.Name == name) ?? name,
placeholders
)
);
}

/// <summary>
/// Fills placeholders in a string with the provided values.
/// </summary>
/// <param name="value">The string with placeholders.</param>
/// <param name="placeholders">The values to fill the placeholders with.</param>
/// <returns>The string with filled placeholders.</returns>
private static string FillPlaceholders(string value, object[] placeholders)
{
for (int i = 0; i < placeholders.Length; i++)
{
value = value.Replace($"{{{i}}}", placeholders[i].ToString());
}

return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static IServiceCollection AddStringLocalizer(
Action<LocalizationBuilder> configure
)
{
LocalizationBuilder builder = new();
DependencyInjectionLocalizationBuilder builder = new(services);

configure(builder);

Expand Down
51 changes: 48 additions & 3 deletions src/Lepo.i18n/LocalizationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

using Lepo.i18n.IO;

namespace Lepo.i18n;

/// <summary>
Expand All @@ -18,7 +20,7 @@ public class LocalizationBuilder
/// Builds an <see cref="ILocalizationProvider"/> using the current culture and localizations.
/// </summary>
/// <returns>An <see cref="ILocalizationProvider"/> with the current culture and localizations.</returns>
public ILocalizationProvider Build()
public virtual ILocalizationProvider Build()
{
return new LocalizationProvider(
selectedCulture ?? CultureInfo.CurrentCulture,
Expand All @@ -30,7 +32,7 @@ public ILocalizationProvider Build()
/// Sets the culture for the <see cref="LocalizationBuilder"/>.
/// </summary>
/// <param name="culture">The culture to set.</param>
public void SetCulture(CultureInfo culture)
public virtual void SetCulture(CultureInfo culture)
{
selectedCulture = culture;
}
Expand All @@ -40,7 +42,7 @@ public void SetCulture(CultureInfo culture)
/// </summary>
/// <param name="localization">The localization set to add.</param>
/// <exception cref="InvalidOperationException">Thrown when a localization set for the same culture already exists in the collection.</exception>
public void AddLocalization(LocalizationSet localization)
public virtual void AddLocalization(LocalizationSet localization)
{
if (
localizations.Any(x =>
Expand All @@ -56,4 +58,47 @@ public void AddLocalization(LocalizationSet localization)

_ = localizations.Add(localization);
}

/// <summary>
/// Adds localized strings from a resource in the calling assembly to the <see cref="LocalizationBuilder"/>.
/// </summary>
/// <typeparam name="TResource">The type of the resource.</typeparam>
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localized strings to.</param>

Check warning on line 66 in src/Lepo.i18n/LocalizationBuilder.cs

View workflow job for this annotation

GitHub Actions / deploy

XML comment has a param tag for 'builder', but there is no parameter by that name

Check warning on line 66 in src/Lepo.i18n/LocalizationBuilder.cs

View workflow job for this annotation

GitHub Actions / deploy

XML comment has a param tag for 'builder', but there is no parameter by that name

Check warning on line 66 in src/Lepo.i18n/LocalizationBuilder.cs

View workflow job for this annotation

GitHub Actions / deploy

XML comment has a param tag for 'builder', but there is no parameter by that name
/// <param name="culture">The culture for which the localized strings are provided.</param>
/// <returns>The <see cref="LocalizationBuilder"/> with the added localized strings.</returns>
public virtual void FromResource<TResource>(CultureInfo culture)
{
Type resourceType = typeof(TResource);
string? resourceName = resourceType.FullName;

if (resourceName is null)
{
return;
}

FromResource(resourceType.Assembly, resourceName, culture);
}

/// <summary>
/// Adds localized strings from a resource with the specified base name in the specified assembly to the <see cref="LocalizationBuilder"/>.
/// </summary>
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localized strings to.</param>

Check warning on line 85 in src/Lepo.i18n/LocalizationBuilder.cs

View workflow job for this annotation

GitHub Actions / deploy

XML comment has a param tag for 'builder', but there is no parameter by that name

Check warning on line 85 in src/Lepo.i18n/LocalizationBuilder.cs

View workflow job for this annotation

GitHub Actions / deploy

XML comment has a param tag for 'builder', but there is no parameter by that name
/// <param name="assembly">The assembly that contains the resource.</param>
/// <param name="baseName">The base name of the resource.</param>
/// <param name="culture">The culture for which the localized strings are provided.</param>
/// <returns>The <see cref="LocalizationBuilder"/> with the added localized strings.</returns>
/// <exception cref="LocalizationBuilderException">Thrown when the resource cannot be found.</exception>
public virtual void FromResource(Assembly assembly, string baseName, CultureInfo culture)
{
LocalizationSet? localizationSet = LocalizationSetResourceParser.Parse(
assembly,
baseName,
culture
);

if (localizationSet is not null)
{
AddLocalization(localizationSet);
}
}
}
57 changes: 3 additions & 54 deletions src/Lepo.i18n/LocalizationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// Copyright (C) Leszek Pomianowski and Lepo.i18n Contributors.
// All Rights Reserved.

using Lepo.i18n.IO;

namespace Lepo.i18n;

/// <summary>
Expand Down Expand Up @@ -80,75 +78,26 @@ public static LocalizationBuilder FromResource<TResource>(
string culture
)
{
return builder.FromResource<TResource>(new CultureInfo(culture));
}

/// <summary>
/// Adds localized strings from a resource in the calling assembly to the <see cref="LocalizationBuilder"/>.
/// </summary>
/// <typeparam name="TResource">The type of the resource.</typeparam>
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localized strings to.</param>
/// <param name="culture">The culture for which the localized strings are provided.</param>
/// <returns>The <see cref="LocalizationBuilder"/> with the added localized strings.</returns>
public static LocalizationBuilder FromResource<TResource>(
this LocalizationBuilder builder,
CultureInfo culture
)
{
Type resourceType = typeof(TResource);
string? resourceName = resourceType.FullName;

if (resourceName is null)
{
return builder;
}

return builder.FromResource(resourceType.Assembly, resourceName, culture);
}
builder.FromResource<TResource>(new CultureInfo(culture));

/// <summary>
/// Adds localized strings from a resource with the specified base name in the specified assembly to the <see cref="LocalizationBuilder"/>.
/// </summary>
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localized strings to.</param>
/// <param name="baseName">The base name of the resource.</param>
/// <param name="culture">The culture for which the localized strings are provided.</param>
/// <returns>The <see cref="LocalizationBuilder"/> with the added localized strings.</returns>
/// <exception cref="LocalizationBuilderException">Thrown when the resource cannot be found.</exception>
public static LocalizationBuilder FromResource(
this LocalizationBuilder builder,
string baseName,
CultureInfo culture
)
{
return builder.FromResource(Assembly.GetCallingAssembly(), baseName, culture);
return builder;
}

/// <summary>
/// Adds localized strings from a resource with the specified base name in the specified assembly to the <see cref="LocalizationBuilder"/>.
/// </summary>
/// <param name="builder">The <see cref="LocalizationBuilder"/> to add the localized strings to.</param>
/// <param name="assembly">The assembly that contains the resource.</param>
/// <param name="baseName">The base name of the resource.</param>
/// <param name="culture">The culture for which the localized strings are provided.</param>
/// <returns>The <see cref="LocalizationBuilder"/> with the added localized strings.</returns>
/// <exception cref="LocalizationBuilderException">Thrown when the resource cannot be found.</exception>
public static LocalizationBuilder FromResource(
this LocalizationBuilder builder,
Assembly assembly,
string baseName,
CultureInfo culture
)
{
LocalizationSet? localizationSet = LocalizationSetResourceParser.Parse(
assembly,
baseName,
culture
);

if (localizationSet is not null)
{
builder.AddLocalization(localizationSet);
}
builder.FromResource(Assembly.GetCallingAssembly(), baseName, culture);

return builder;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Lepo.i18n/Translator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static bool LoadLanguages(
}
else
{
_ = builder.FromResource(
builder.FromResource(
applicationAssembly,
languageResource.Value,
new CultureInfo(languageResource.Key.Replace("_", "-"))
Expand Down
6 changes: 3 additions & 3 deletions tests/Lepo.i18n.UnitTests/LocalizationBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public void FromResource_ShouldThrowException_WhenResourceSetIsMissing()
{
LocalizationBuilder builder = new();

Func<LocalizationBuilder> action = () =>
Action action = () =>
builder.FromResource(
Assembly.GetExecutingAssembly(),
"Lepo.i18n.UnitTests.Resources.Invalid",
Expand All @@ -29,7 +29,7 @@ public void FromResource_ShouldLoadResourceFromNamespace()
{
LocalizationBuilder builder = new();

_ = builder.FromResource<Test>(new CultureInfo("en-US"));
builder.FromResource<Test>(new CultureInfo("en-US"));

ILocalizationProvider provider = builder.Build();

Expand All @@ -44,7 +44,7 @@ public void FromResource_ShouldLoadResource()
{
LocalizationBuilder builder = new();

_ = builder.FromResource(
builder.FromResource(
Assembly.GetExecutingAssembly(),
"Lepo.i18n.UnitTests.Resources.Test",
new CultureInfo("en-US")
Expand Down

0 comments on commit 3fbce60

Please sign in to comment.