Class I18n
I18nString validates its cache against.
Shaped after Fonts and ControlSize, because a language change
is measurement-affecting in exactly the way a font change is: it notifies
listeners, every scene subscribes and re-lays-out,
and widgets re-read their text on the way through. An application switches
languages with one call and nothing else:
I18n.addBundle(PropertyBundle.family("/i18n/settings"));
I18n.addBundle(PropertyBundle.family("/i18n/editor"));
I18n.setLocale(Locale.forLanguageTag("pt-BR"));
Nothing is required. With no bundle registered every string resolves to the English it carries, which is what the toolkit did before this class existed.
A subtree can hold its own language
A widget that declares a locale gives its whole subtree one, resolved down the tree exactly asControlSize is
(ADR 035). The mechanism this class contributes is the scope: while the
toolkit measures, lays out, paints or dispatches an event to a widget, that
widget's effective locale is in scope, and locale()
answers it. Everything that already read I18n.locale() at the moment it
resolved, formatted or broke text — I18nString.get(),
I18nString.format(java.lang.Object...), localizeDigits(java.lang.String), a chart format, a line
breaker — follows the subtree it is working inside without knowing
subtrees exist. That is the point: after ADR 006's own argument, the default
spelling is the correct one, and a widget written naively cannot capture the
wrong language.
Mutators are meant to be called on the UI thread (checked when a Ui
runtime is installed, so headless tests can drive them directly).
-
Method Summary
Modifier and TypeMethodDescriptionstatic voidaddBundle(StringBundle bundle) Registers a source of translations; the most recently added is consulted first, so an application's own bundle overrides one the toolkit installed.static voidaddChangeListener(Runnable listener) Subscribes to language changes (idempotent per instance).static List<StringBundle> bundles()The registered bundles, newest first.static Collatorcollator()A collator for the text locale, for ordering what a user reads: list items, table rows, anything sorted for display.Every key declared so far, mapped to its English, in declaration order: the translation catalog as a runtime fact rather than a document that drifts.static longepoch()The counter everyI18nStringmemo is keyed on.static Localelocale()The language in effect here: the innermost open scope's locale, else the process locale.static StringlocalizeDigits(String text) textwith every ASCII digit rewritten in the active numbering system, and everything else untouched.static NumberingSystemThe digits a formatted number is written in: the own system of the locale in effect here, unlesssetNumberingSystem(limn.i18n.NumberingSystem)declared otherwise.static voidCloses the innermost scope by restoring whatpushScope(java.util.Locale)returned;null(the usual outermost case) restores "no scope", which is the process locale.static LocaleThe process-wide UI language, ignoring any open scope: whatsetLocale(java.util.Locale)set,Locale.getDefault()until something says otherwise, and the root of the widget resolution chain.static LocaleOpens a resolution scope: until the matchingpopScope(java.util.Locale),locale()on this thread answerslocale.static voidreleaseLocale(Locale locale) Undoes oneretainLocale(java.util.Locale).static voidremoveBundle(StringBundle bundle) Removes a bundle.static voidremoveChangeListener(Runnable listener) Unsubscribes; no-op when it was never registered.static voidretainLocale(Locale locale) Declares thatlocaleis being read somewhere — a widget or scene subtree resolves through it — so every bundle keeps (and any late-registered bundle gains) a prepared table for it, andsetLocale(java.util.Locale)moving the process away from it does not drop what that subtree is still reading.static voidSwitches the UI language.static voidsetNumberingSystem(NumberingSystem system) Overrides the locale's numbering system;nullreturns to following the locale.static voidsetTextLocale(Locale next) Declares the language of the content being ordered and case-mapped;nullreturns to following the UI locale.static LocaleThe language text is ordered and case-mapped in: the locale in effect here, unlesssetTextLocale(java.util.Locale)declared the content is in another language.static StringtoAsciiDigits(String text) textwith every digit of every known system folded back to ASCII: the parse-time half of ADR 033, and deliberately independent of the active system — a value pasted under one locale must survive being committed under another.static StringtoLowerCase(String text) The lower-case twin oftoUpperCase(java.lang.String), in the same locale.static StringtoUpperCase(String text) textupper-cased in the text locale: the case mapping for something a user reads.
-
Method Details
-
locale
The language in effect here: the innermost open scope's locale, else the process locale.Inside a widget's measure, layout, paint or event dispatch this is that widget's effective locale, because the toolkit opens the scope around each of those; everywhere else — application startup, a posted task, a worker thread — it is the process locale, exactly as before ADR 035. Reading it at the moment text is resolved or formatted is what makes code follow the subtree it is working inside.
-
processLocale
The process-wide UI language, ignoring any open scope: whatsetLocale(java.util.Locale)set,Locale.getDefault()until something says otherwise, and the root of the widget resolution chain. Almost every reader wantslocale()instead. -
setLocale
Switches the UI language. No-op when unchanged. Every registered bundle is prepared first (so a file-backed bundle reads from disk here rather than inside the next measure pass), then the epoch is bumped and listeners run, which is what re-lays-out every live scene. The outgoing language's tables are released, unless a retained subtree still reads them. -
pushScope
Opens a resolution scope: until the matchingpopScope(java.util.Locale),locale()on this thread answerslocale. This is how a widget's effective locale reaches everything that resolves or formats text while the toolkit is inside that widget —Widgetopens one around measure, layout, paint and event dispatch, andWidget.tooltip()around its own resolution. An application may open one too, to format something for a particular pane from outside a pass.Scopes nest: the returned value is the scope this call replaced, and handing it back to
popScope(java.util.Locale)restores it, so the idiom isLocale enclosing = I18n.pushScope(locale); try { ... } finally { I18n.popScope(enclosing); }- Returns:
- the scope in effect before this call, possibly
null: the valuepopScope(java.util.Locale)must be given back
-
popScope
Closes the innermost scope by restoring whatpushScope(java.util.Locale)returned;null(the usual outermost case) restores "no scope", which is the process locale. Always call it in afinally. -
retainLocale
Declares thatlocaleis being read somewhere — a widget or scene subtree resolves through it — so every bundle keeps (and any late-registered bundle gains) a prepared table for it, andsetLocale(java.util.Locale)moving the process away from it does not drop what that subtree is still reading. Counted:Widget.setLocaleandScene.setLocalecall this for a new declaration andreleaseLocale(java.util.Locale)for the one it replaced, and an application with its own reason to resolve a language outside the tree may do the same.Preparation happens here, on the first retain, for the reason
setLocale(java.util.Locale)prepares: a file-backed bundle must read from disk now, not inside the measure pass of the frame that first shows the subtree. -
releaseLocale
Undoes oneretainLocale(java.util.Locale). When the last retain forlocaleis released and it is not the process locale, every bundle is told to release its table for it. Releasing a locale that was never retained is a no-op, so a clear-before-declare cannot throw. -
epoch
public static long epoch()The counter everyI18nStringmemo is keyed on. Bumped by a locale change and by any bundle registration; both change what a key resolves to. -
numberingSystem
The digits a formatted number is written in: the own system of the locale in effect here, unlesssetNumberingSystem(limn.i18n.NumberingSystem)declared otherwise. There is no numbering-system axis and ADR 033's Decision 1 still stands: substitution happens at format time inside the widgets that render numbers they own, so application strings are never rewritten. What changed with ADR 035 is only which locale the system is derived from — the effective one, so an Arabic subtree's spinner writes Arabic-Indic digits inside a Latin interface with no second mechanism. The declared override stays process-wide and wins everywhere, because it is a statement about the process ("this deployment writes Latin digits"), not about a subtree. -
setNumberingSystem
Overrides the locale's numbering system;nullreturns to following the locale. Treated as a text change: the epoch bumps and every live scene re-lays-out, exactly as a locale switch does, because every formatted number on screen just changed. -
localizeDigits
textwith every ASCII digit rewritten in the active numbering system, and everything else untouched. Returns its argument underNumberingSystem.LATNor when there is nothing to rewrite, so the default locale pays an object comparison and a scan, not an allocation.This is the format-time half of ADR 033: call it on a string the widget itself rendered from a number, never on text an application authored.
-
toAsciiDigits
textwith every digit of every known system folded back to ASCII: the parse-time half of ADR 033, and deliberately independent of the active system — a value pasted under one locale must survive being committed under another. -
addBundle
Registers a source of translations; the most recently added is consulted first, so an application's own bundle overrides one the toolkit installed. Prepared for the process locale and every retained one before it can be seen, then treated as a text change.Registering the same instance twice is a no-op.
-
removeBundle
Removes a bundle. No-op when it was never registered. -
addChangeListener
Subscribes to language changes (idempotent per instance). -
removeChangeListener
Unsubscribes; no-op when it was never registered. -
declaredKeys
Every key declared so far, mapped to its English, in declaration order: the translation catalog as a runtime fact rather than a document that drifts. A test asserts a bundle covers it; a tool dumps it as a starter.properties.It sees only what has been loaded: a component's strings are
static final, so they register when its class first initialises. Anything enumerating the catalog must touch the classes it cares about first. -
bundles
The registered bundles, newest first. -
textLocale
The language text is ordered and case-mapped in: the locale in effect here, unlesssetTextLocale(java.util.Locale)declared the content is in another language. The two are different facts — an English interface listing Swedish names must still put ä after z — but almost every application never needs to say so, and the default keeps order and case in the language the user is reading. Followinglocale()rather than the process locale is what makes a type-ahead fold, or a sort run inside a pass, answer for the subtree it is working inside (ADR 035), exactly as digits do; the declared override stays process-wide and wins everywhere, for ADR 034's reason. -
setTextLocale
Declares the language of the content being ordered and case-mapped;nullreturns to following the UI locale. Treated as a text change, exactly assetLocale(java.util.Locale)is: the epoch bumps and listeners run, because every order an application built throughcollator()is now stale and the application's own change listener is where it re-sorts. -
collator
A collator for the text locale, for ordering what a user reads: list items, table rows, anything sorted for display. A machine order — a key, a slug, a file format — keepscompareTo.Every call answers a fresh instance, because a
Collatorcarries mutable per-comparison state and must not be shared across threads. Fetch one per sort, not one per comparison:names.sort(I18n.collator()); items.sort(Comparator.comparing(Item::label, I18n.collator())); -
toUpperCase
textupper-cased in the text locale: the case mapping for something a user reads. A machine format keepsLocale.ROOT— under Turkish this mapsitoİ, which is exactly right in a list of cities and exactly wrong in a hex color. -
toLowerCase
The lower-case twin oftoUpperCase(java.lang.String), in the same locale.
-