-
Notifications
You must be signed in to change notification settings - Fork 43
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
Added: Localization Backend & Documentation (fixes #452) #563
Conversation
[I didn't notice the defaults for my machine were there. Oopsie]
|
||
private readonly LocalizedStringUpdater _nameUpdater; | ||
|
||
public IconViewModel(Func<string> getName) => _nameUpdater = new LocalizedStringUpdater(() => Name = getName()); |
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.
Why do we need a function here? Vs a constant string?
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.
For contributors/developers, this is documented in LocalizationAndTranslation.md
:
NexusMods.App/docs/LocalizationAndTranslation.md
Lines 75 to 133 in 7732677
#### Updating Strings Dynamically | |
If you have a string in C# which is long lived, and will be live when the language is changed; it must be updated dynamically. | |
For this purpose, the `LocalizedStringUpdater` class is provided; it is used like this: | |
```csharp | |
_localizable = new LocalizedStringUpdater(() => ReactiveField = Language.MyGames); | |
``` | |
To give an example: | |
```csharp | |
public class SomeViewModel : AViewModel<ISomeViewModel>, ISomeViewModel, IDisposable | |
{ | |
[Reactive] | |
public string Name { get; set; } = ""; | |
private readonly LocalizedStringUpdater _nameUpdater; | |
public SomeViewModel(Func<string> getName) | |
{ | |
_nameUpdater = new LocalizedStringUpdater(() => Name = getName()); | |
} | |
// Note: Multi-dispose guard not needed; LocalizedStringUpdater.Dispose by contract only unsubscribes from an event. | |
public void Dispose() => _nameUpdater.Dispose(); | |
} | |
``` | |
When you call `new LocalizedStringUpdater` and every time the language is changed | |
through the use [Localizer.Instance.LoadLanguage](#switching-a-language), the callback given in the `LocalizedStringUpdater` | |
will be executed. | |
As for the `[Reactive]` fields, the autogenerated `INotifyPropertyChanged` handlers will always | |
do a comparison on the string in the setter. So if a string remains unchanged, no UI redraw will occur | |
for the affected element as `PropertyChanged` will not fire. | |
!!! note "The API is specifically designed like this to ensure compile time safety." | |
!!! danger "`LocalizedStringUpdater` must be properly disposed when no longer in use. Due to nature of .NET events, lack of proper disposal will lead to memory leak as the class subscribes to `Localizer` which has singleton lifetime." | |
##### Use within Reactive Code | |
When possible, use the `WhenActivated` ReactiveUI method; alongside `DisposeWith`. This will ensure that `LocalizedStringUpdater(s)` and structures which use them will be disposed safely. | |
```csharp | |
this.WhenActivated(disposable => | |
{ | |
var items = new ILeftMenuItemViewModel[] | |
{ | |
new IconViewModel(() => Language.Newsfeed) { ... }.DisposeWith(disposable), | |
new IconViewModel(() => Language.MyGames) { ... }.DisposeWith(disposable), | |
new IconViewModel(() => Language.BrowseGames) { ... }.DisposeWith(disposable) | |
}; | |
}); | |
``` | |
### Reference from XAML |
As for the technical reason; it's pretty simple.
Some parts of the UI are driven by code behind. When you change a language behind the .resx
file, there is no 'magic trigger' to automatically replace all instances of a given string throughout the application, so this must be handled manually.
The LocalizedStringUpdater
class automatically fires any callbacks given to it when the user changes the language in the UI. (In this case Name = getName()
)
2023-08-17.17-20-30.mp4
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.
Does the need for live updating outweigh the added complexity and performance loss? Most applications will just prompt the user to restart the application if they change the language.
Summary
Closes #452
No special merge text for this PR; for all the change details, refer to the newly added docs in this PR.