Package limn.i18n

Class I18nString

java.lang.Object
limn.i18n.I18nString

public final class I18nString extends Object
A piece of user-visible text, carried as a value instead of resolved into a String at the point of use.

A component stores one of these where it would otherwise store a String, and reads get() when it measures or paints. Holding the value is what makes keeping it correct: a lookup call whose result must never be kept in a field fails silently, leaving one widget in yesterday's language.


 private static final I18nString PLACEHOLDER =
         new I18nString("limn.searchField.placeholder", "Search…");
 ...
 canvas.drawText(PLACEHOLDER.get(), x, baseline, font, colour);
 

English is not a translation. The second argument is both the default text and the fallback for every key a bundle does not answer, so an application that registers nothing behaves exactly as it did before this type existed, and a missing translation degrades to English rather than to a key name on screen.

The cache lives here. get() memoises its resolution against I18n.epoch(), so nothing subscribes and nothing leaks: the re-resolution rides the relayout a language change already causes.

Declare them static final, so the cache is one slot per key for the whole process rather than one per widget; a list showing five hundred rows of the same label resolves once per language change, not five hundred times.

Immutable in every respect a caller can observe; the memo is mutated on the UI thread like the rest of the toolkit's state.

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final I18nString
    The empty literal: a text field's placeholder before anyone sets one.
  • Constructor Summary

    Constructors
    Constructor
    Description
    I18nString(String key, String english)
    Declares a localizable string.
  • Method Summary

    Modifier and Type
    Method
    Description
    The default-language text: also the fallback, and never null.
    boolean
    Value equality on key and English: two literals of the same text are the same string, and a component's "did this actually change" check keeps working when its field stops being a String.
    format(Object... args)
    The text with {0}-style arguments substituted, formatted by MessageFormat under the current locale.
    get()
    The text in the locale in effect here, or the English when nothing translates it.
    int
     
    boolean
    Whether this is a literal, which no bundle can ever change.
    key()
    The lookup key, or null for a literal.
    static I18nString
    Text that is not localizable and resolves to itself: what setText(String) wraps a caller's literal in, so a component can hold one field instead of two and never has to decide which of them wins.
    The resolved text, so logging and debugging read as the user sees it.

    Methods inherited from class java.lang.Object

    clone, finalize, getClass, notify, notifyAll, wait, wait, wait
  • Field Details

    • EMPTY

      public static final I18nString EMPTY
      The empty literal: a text field's placeholder before anyone sets one.
  • Constructor Details

    • I18nString

      public I18nString(String key, String english)
      Declares a localizable string.
      Parameters:
      key - the lookup key, dotted and globally unique; prefix it with the screen or component that owns it (settings.title, not title), because bundles share one namespace
      english - the text in the default language, used whenever no registered bundle answers the key
      Throws:
      IllegalStateException - if the key was already declared with different English: one key with two meanings is a bug that would otherwise surface as a mistranslation
  • Method Details

    • literal

      public static I18nString literal(String text)
      Text that is not localizable and resolves to itself: what setText(String) wraps a caller's literal in, so a component can hold one field instead of two and never has to decide which of them wins.
    • get

      public String get()
      The text in the locale in effect here, or the English when nothing translates it. Inside a widget's measure, layout, paint or event dispatch that locale is the widget's own effective one, so the same declaration reads correctly from every subtree without the caller doing anything.
    • format

      public String format(Object... args)
      The text with {0}-style arguments substituted, formatted by MessageFormat under the current locale. Never cached: the arguments vary per call, and caching them would be caching the wrong thing.

      Only parameterized keys pay MessageFormat's rules, which is why get() does not route through it: an apostrophe in an ordinary string needs no escaping. In a parameterized one it does (double it), and so does a stray brace; a pattern a translator got wrong falls back to the English rather than throwing in the middle of a paint.

      Numbers are formatted for the locale, so pass Integer.toString(n) where ASCII digits are required; digits the text pipeline cannot draw are worse than an unlocalised number.

    • key

      public String key()
      The lookup key, or null for a literal.
    • english

      public String english()
      The default-language text: also the fallback, and never null.
    • isLiteral

      public boolean isLiteral()
      Whether this is a literal, which no bundle can ever change.
    • toString

      public String toString()
      The resolved text, so logging and debugging read as the user sees it.
      Overrides:
      toString in class Object
    • equals

      public boolean equals(Object o)
      Value equality on key and English: two literals of the same text are the same string, and a component's "did this actually change" check keeps working when its field stops being a String.
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object