Repository navigation
Route every lookup through a replaceable message formatter #30
Copy link
Copy link
Closed
Labels
Milestone
Description
Feature 01 of the 11 features of ResXLocalization 2.0.0, parent issue #29. "Feature NN" in this
issue means an entry of the numbered feature list in the parent issue.
Why
In 1.x a resource value that cannot be formatted - Broken {0, or File {0} of {1} with one argument -
fails in three different ways: Get(key, args) throws FormatException, the Avalonia binding reports
an error, and the WPF text goes empty. One bad translation in one language can throw out of a view
model, and nothing reports it in a way an application can log. Later features (named arguments, ICU
MessageFormat) also need one place where formatting happens, and applications want one place where
they can post-process every translated text.
What changes
// Resource "Malformed" = "Broken {0"
// 2.0 call form; MessageArguments is introduced in feature 02.
Localizer.Current.Get("Malformed", MessageArguments.FromValues(1));
// 1.x (as Get("Malformed", 1)): throws FormatException; 2.0: "!Malformed!" + LookupFailed
Localizer.Current.LookupFailed += (_, e) =>
logger.LogWarning(e.Failure.Exception, "Lookup of {Key} in {Culture} failed: {Reason}", e.Failure.Key, e.Failure.Culture, e.Failure.Reason);
Localizer.Current.MessageFormatter = new MyFormatter(); // replaceable; the default is string.Format- New:
IMessageFormatter,ILocalizer.MessageFormatter,ILocalizer.LookupFailedwith
LookupFailedEventArgs, andMessageArgument(one argument of a lookup: its position, an
optional name and its value; a lookup passes aMessageArgument[]). - Every lookup, in code and in XAML, with or without arguments, goes through the same formatter, so it
can serve as one hook for all translated texts; the default output is identical to 1.x for
every successful format with at least one argument and for every lookup without arguments (an empty
argument array now counts as no arguments, see below). - New
ILocalizer.RefreshTriggeringRevision: changes whenever the culture, the formatter, the
failure text (LookupFailureTextBuilder, below) or the registered resource
managers change, so bound text can re-resolve
on every such change, not only on a language switch. - The default formatter does not format a value without arguments:
Get(key, args)with an empty array
no longer formats, likeGet(key)and XAML without arguments.
Breaking changes
- Formatting failures return the
!key!failure text and raiseLookupFailedinstead of throwing;
in XAML they render the failure text. Get(key, args)with an empty array returns the resource value unformatted ({{x}}stays{{x}}).MissingTranslationFormat(a format string) is replaced byLookupFailureTextBuilder, a
Func<LookupFailure, string>that produces the failure text - the text shown instead of a translation,
by default!key!- for a missing key and for a formatting failure.LookupFailurecarries the reason
(LookupFailureReason.TranslationNotFoundor.MessageFormatFailed), the key, the lookup's culture,
its resource manager (nullfor search-all), the lookup's arguments and, for a formatting failure, the
pattern and the exception:LookupFailureTextBuilder = failure => $"[{failure.Key}]". The documentation calls this text
the "failure text" instead of the "sentinel".TranslationNotFoundandTranslationNotFoundEventArgsare replaced byLookupFailedand
LookupFailedEventArgs. The new event reports every failed lookup - a missing key and, new, a
formatting failure - and carries the sameLookupFailurethatLookupFailureTextBuilderreceives.- Custom
ILocalizerimplementations must add the new members.
Acceptance criteria
- A formatting failure returns the failure text and raises
LookupFailedwith reason, key, culture,
resource manager, arguments, pattern and exception, without throwing fromGetor a binding unless
a consumer event handler throws orLookupFailureTextBuilderthrows or returnsnull. - A missing key raises
LookupFailedwith reasonTranslationNotFound; the failure text is never formatted. - Every resolved value reaches the formatter, also from
Get(key), the indexer, the enum lookups
and XAML without arguments; the default formatter returns such values unchanged. - The default formatter's successful output is unchanged from 1.x.
-
RefreshTriggeringRevisionadvances on every real change of culture, formatter,
lookup-failure text builder or registered managers, and never on a no-op. - The changelog and the new migration guide describe every break.
Details
- Depends on: nothing.
Activity
feat!: route every lookup through a replaceable message formatter (#31)