-
Notifications
You must be signed in to change notification settings - Fork 20
feat(localization): add resource-based localization foundation #421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| namespace GenHub.Core.Constants; | ||
|
|
||
| /// <summary> | ||
| /// Constants used by the application localization infrastructure. | ||
| /// </summary> | ||
| public static class LocalizationConstants | ||
| { | ||
| /// <summary> | ||
| /// The neutral culture embedded in the main application assembly. | ||
| /// </summary> | ||
| public const string DefaultCultureName = "en"; | ||
|
|
||
| /// <summary> | ||
| /// The property name used to notify bindings that all indexer values changed. | ||
| /// </summary> | ||
| public const string IndexerPropertyName = "Item"; | ||
|
|
||
| /// <summary> | ||
| /// The application resource key used to expose the localization service to XAML. | ||
| /// </summary> | ||
| public const string ResourceServiceKey = "LocalizationService"; | ||
|
|
||
| /// <summary> | ||
| /// The fully qualified base name of the application's string resources. | ||
| /// </summary> | ||
| public const string StringResourceBaseName = "GenHub.Resources.Localization.Strings"; | ||
|
|
||
| /// <summary> | ||
| /// The suffix used by .NET satellite resource assemblies. | ||
| /// </summary> | ||
| public const string SatelliteAssemblySuffix = ".resources.dll"; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| using System.Collections.Generic; | ||
| using System.ComponentModel; | ||
| using System.Globalization; | ||
| using GenHub.Core.Models.Results; | ||
|
|
||
| namespace GenHub.Core.Interfaces.Common; | ||
|
|
||
| /// <summary> | ||
| /// Provides localized application strings and runtime culture switching. | ||
| /// </summary> | ||
| public interface ILocalizationService : INotifyPropertyChanged | ||
| { | ||
| /// <summary> | ||
| /// Gets the cultures backed by the neutral resource or a deployed satellite assembly. | ||
| /// </summary> | ||
| IReadOnlyList<CultureInfo> AvailableCultures { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the culture used for resource lookup and formatting performed by <see cref="GetString"/>. | ||
| /// </summary> | ||
| CultureInfo CurrentCulture { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets a localized string by resource key. | ||
| /// </summary> | ||
| /// <param name="key">The resource key to resolve.</param> | ||
| /// <returns>The localized value, its English fallback, or the key when no resource exists.</returns> | ||
| string this[string key] { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets and optionally formats a localized string. | ||
| /// </summary> | ||
| /// <param name="key">The resource key to resolve.</param> | ||
| /// <param name="arguments">Optional format arguments.</param> | ||
| /// <returns>The localized value, its English fallback, or the key when no resource exists.</returns> | ||
| string GetString(string key, params object?[] arguments); | ||
|
|
||
| /// <summary> | ||
| /// Changes the active culture when it has a deployed translation. | ||
| /// </summary> | ||
| /// <param name="culture">The culture to activate.</param> | ||
| /// <returns>A result indicating whether the requested culture was available and applied.</returns> | ||
| OperationResult SetCulture(CultureInfo culture); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| using Avalonia; | ||
| using Avalonia.Headless; | ||
| using Avalonia.Headless.XUnit; | ||
| using GenHub.Core.Interfaces.Common; | ||
| using GenHub.Core.Interfaces.GameProfiles; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Moq; | ||
|
|
||
| [assembly: AvaloniaTestApplication(typeof(GenHub.Tests.Core.App.TestAppBuilder))] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Assembly-level Reply with |
||
|
|
||
| namespace GenHub.Tests.Core.App; | ||
|
|
||
| /// <summary> | ||
| /// Configures the GenHub application for cross-platform headless lifecycle tests. | ||
| /// </summary> | ||
| internal static class TestAppBuilder | ||
| { | ||
| private static readonly Mock<ILocalizationService> LocalizationServiceMock = new(); | ||
| private static readonly IServiceProvider ServiceProvider = CreateServiceProvider(); | ||
|
|
||
| /// <summary> | ||
| /// Gets the localization service registered in the headless application. | ||
| /// </summary> | ||
| internal static ILocalizationService LocalizationService => LocalizationServiceMock.Object; | ||
|
|
||
| /// <summary> | ||
| /// Creates the Avalonia application builder used by headless tests. | ||
| /// </summary> | ||
| /// <returns>The configured application builder.</returns> | ||
| public static AppBuilder BuildAvaloniaApp() | ||
| => AppBuilder.Configure(() => new global::GenHub.App(ServiceProvider)) | ||
| .UseHeadless(new AvaloniaHeadlessPlatformOptions()); | ||
|
|
||
| private static IServiceProvider CreateServiceProvider() | ||
| { | ||
| var services = new ServiceCollection(); | ||
| services.AddSingleton(Mock.Of<IUserSettingsService>()); | ||
| services.AddSingleton(Mock.Of<IConfigurationProviderService>()); | ||
| services.AddSingleton(LocalizationService); | ||
| services.AddSingleton(Mock.Of<IProfileLauncherFacade>()); | ||
|
|
||
| return services.BuildServiceProvider(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| namespace GenHub.Tests.Core.Collections; | ||
|
|
||
| /// <summary> | ||
| /// Prevents culture-mutating localization tests from running beside unrelated tests. | ||
| /// </summary> | ||
| [CollectionDefinition(Name, DisableParallelization = true)] | ||
| public sealed class LocalizationCultureCollection | ||
| { | ||
| /// <summary> | ||
| /// The collection name used by culture-mutating tests. | ||
| /// </summary> | ||
| public const string Name = "Localization culture"; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING: The test does not exercise the resolution path it claims to verify.
LocalizeExtension.ProvideValue(IServiceProvider)callsApplication.Current?.TryGetResource(...)(LocalizeExtension.cs:32) — it never touchesserviceProvider— so passingMock.Of<IServiceProvider>()proves nothing about the pre-AvaloniaXamlLoader.Loadregistration on App.axaml.cs:53. The assertion only confirms the binding'sSourceis the same mock service that was just placed intoapp.Resourcesby the post-Load statement (App.axaml.cs:57), which would pass identically if the pre-Load assignment at line 53 were removed. To actually verify the App-level XAML resolution path that justifies keeping line 53, exercise the extension against a realIServiceProviderresolved fromAvaloniaRuntimeXamlLoader/AvaloniaXamlLoaderwhileApp.axamlis being parsed (e.g. parse a small XAML fragment containing{localization:Localize App.Name}and assert the resolvedBinding.Sourcematches the registered service).Reply with
@kilocode-bot fix itto have Kilo Code address this issue.