Skip to content

Localization

Reactor (Multi-Hub/Multi-System) can be localized for your language. Currently, the following languages are supported:

  • US English (included in distribution; default application language)
  • Swedish (included in distribution; community contribution by @Crille)

If your language is not listed above, you can contribute a localization if you're willing to do the small amount of work it takes. See Custom Language Files below.

By default, Reactor chooses the localization language based on your browser settings and the settings of the operating system on which Reactor is running. It therefore logically follows that both your browser and your Reactor host system should be configured for the same locale.

Setting Host System Locale

Setting the host system locale is system-dependent. Consult the documentation for your host system OS for instructions on how to set the locale.

Log files

Reactor log files on the host system (e.g. reactor.log) always use a fixed time format and UTC timezone. This is not configurable/changeable, by design. Additionally, most messages in the log files are not localized (e.g. they always appear in US English or whatever language the author wrote them in).

Setting Browser Locale

Locale configuration for your browser is controlled in your browser settings. Refer to the documentation for your browser for instructions on how to set the locale.

Browser vs Host

Browsers and host systems have their own locales and their own ways of setting an managing them, and it's easily possible for the host locale to be different from a browser's locale. Because some messages displayed in the UI are generated not by the UI (browser) but by the Engine (running on the host), it is possible for some messages and output to be in the host's configured language when the browser is configured for a different language. This is a normal side-effect of the reality that a browser is not part of the host system, but rather belongs to a separate system, and will not be considered a bug.

Overriding Locale Settings

You can override the locale Reactor uses for both browser and host display by setting the locale key in the reactor section of reactor.yaml. The locale must be set to a recognized ISO 639-1 language code value, which may optionally include an ISO 3166-2 country code for a country-specific translation (for example, to address the differences between French in France, Switzerland, and Canada; fr-FR, fr-CH,fr-CA). A comma-separated list of locales, in order of preference, may be given (e.g. sv-SE,sv), and the first locale with a translation available will be selected. If no translations are available, Reactor will use the default en-US.

Avoid Locale/Language Overrides

Override configuration only affects Reactor's behavior, but not your browser's or your host's. Your browser will continue to display its own menus and dialogs in its configured language, and other side-effects may be noted (e.g. unusual sorting of alphabetical lists). The configuration override is for testing or temporary use, and is not intended as a permanent configuration item. The automatically-negotiated language configuration of a properly configured browser and host system are the best and recommended/supported way to select the language/locale.

By default, dates and times are displayed in the settings determined by the locale (including the order of month, day and year in date displays, and 12/24 time display). You can override the date format Reactor uses (both in the browser UI and the host Engine) by setting date_format in the reactor section of reactor.yaml. Formatting rules are similar to those used by the Linux date command. For example, if you prefer to see dates as four-digit year, abbreviated month name, and day ("2021-Sep-05"), then the format specification would be date_format: "%Y-%b-%d". Similarly, time format can be modified by setting time_format, so if you prefer 24-hour time, specify time_format: "%H:%M:%S".

If you have questions about the use of a string, find an untranslated string in the application, or otherwise need help, please open an issue in the Github repository for the language template. This is best place to ask your questions and get support. Please do not use the Smart Home Community forums for translation questions or issues.

Custom Language Files

If Reactor does not provide a conversion file for your language, you can create one. The process is fairly straightforward:

  1. DO NOT copy the locale_en-US.yaml file and start translating. Get the latest, official version of the file from Github: Multi-hub Reactor Localization Template.
  2. Change the name of your file to reflect your target language and country code (e.g. locale_fr-CA.yaml for Canadian French). The language code is ISO 639-1, and the country code is ISO 3166-1 Alpha 2. If your language does not change from country to country, then the language code alone is sufficient (locale_gu.yaml).
  3. Set the locale for your browser and host system using the methods described above, if you have not already.
  4. Translate the strings on the right side of the ":" (colon) each to your language. Do not modify the left-side (key) strings. Some strings contain substitutions, where data is inserted into the strings. See the substitution reference below for details on how the substitutions work and how you work with them.
  5. Restart Reactor and hard-refresh your browser after making changes to your language file to test your new translation.

Info

If your language is not yet supported by Reactor and you create a language file for it, consider either publishing your language file on Github, and contributing your language file to Reactor for inclusion in builds/releases (the builds will pull it from your Github repository, and you will receive attribution for your contribution).

Updates Change Things

Be aware that future versions of Reactor will add, remove or replace strings, so you may need to update your language file when new versions of Reactor are released. This is another good reason to keep your language file on Github — you can diff versions of the US English file to find strings that have been added, changed, or removed.

Practices and Guidelines for Translators

As the product changes, additional strings will necessarily be added that will need to be translated, so from time to time, you should be prepared to review the CHANGELOG.md file, which will document these changes, and make the necessary updates to your translation file. The following are the recommended practices for translators and handling of their translation files:

  • You may alter your translation of a string (i.e. the right side of each key/value pair) freely to get the best translation. It's not a problem to alter/improve translations between versions of your file, and is encouraged.
  • Remember to update the version and ref_version keys in your file every time you make changes. The version is a Julian date (two-digit year followed by day of the year 1-366; tip: use command date +%y%j in a Linux shell). This key must be updated. The ref_version field is provided for your convenience, and may be used to store the version number of the reference (locale_en-US.yaml) file you are updating to (this is also the version number shown in the CHANGELOG headings). You can use this to determine what revisions needs to be applied to bring your translation up to current product.
  • If the CHANGELOG describes a string that is "disused", do not remove the string from your translation file. Rather, it is recommended that the string (the entire key/value pairing) be moved to the bottom of your translation file under a comment heading # disused as of NNNNN, where NNNNN is the revision of reference (en-US) file. In this way, your translation file will continue to carry the disused strings and be usable in both earlier and later versions of Reactor equally.
  • It does not matter where you place new strings in your translation file, but it is recommended that you refer to the reference file for location and mirror that placement (put the new string in the same relative location as it appears in the reference file).
  • From time to time, it may be necessary for Reactor to change the construction or semantics/meaning of a string. If this occurs, a new string with a different tag will be created and the old string will become disused. Again, this will ensure that your translation file will remain compatible/usable with a wide range of Reactor versions.
  • If you find a string or construction is troublesome to translate cleanly into your language, please open an issue in the Github repository.
  • If your language has a universal/formal form and a national, regional, or local dialects, and you are capable of providing additional translations, please consider doing so. A universal form of a language would have a translation file named only with the language code. For example, Standard High German (Hochdeutsch) may be used as the default German translation in locale_de.yaml, while Swiss German would be in locale_de-CH.yaml for German speakers in Switzerland. Going further, a regional translation file for the Bavarian German dialect may be named locale_de-DE-BYL.yaml (German language, country of Germany, region of Bayern Land). Regional translations, however, should only be made for the most differentiated and necessary cultural sensibilities.
  • If you decide to stop supporting a translation, please notify the Reactor developer(s).

Substitutions in Translations

Substititions in translation strings allow Reactor to put data into translated output in a way that makes sense for the language. Of course, many languages use a natural word order that is different from that of US English, so Reactor tries to avoid building strings from pieces in English word order, but rather give you the largest translatable unit along with the data that needs to be placed, and let you determine where and in what order that data appears for a correct translation to your language.

Substitutions in strings can be identified by their enclosure in "curly braces" ({}). If you look at the locale_en-US.yaml file, you will find any strings with {0}, {1}, etc., as well as more complex variants like {0:d?>0}. Lets start simple and build up...

The {n} form (where n is a positive integer starting with 0/zero) is replaced with the nth data value (like the subscripts of an array). This n value will be referred to here as the value index. If the string "The count is {0}." is formatted with a single value 5, the resultant string is The count is 5..

In the foregoing example (The count is {0}), the formatter simply uses JavaScript's default rules for converting the data value to a string. Often, more specific formatting than this default is needed. In order to specify any special handling or formatting, format specifications can be included in each substitution. We first separate the value index from the format specifications with a colon (:), and the specifications follow. Here's the full definition of what can appear between {}:

    {value-index[:[flags][alignment][width][.prec][format][?condition]}

The flags are optional, and there is currently only one available flag: # will cause the absolute value of a numeric value to be displayed.

The alignment follows, and can be <, ^, or > for left, center or right alignment, respectively. Alignment logically requires that a width follow.

The optional width sets the width of the field in which the value is displayed; if not specified, the field is as wide as is required to display the value in the format later specified. If the width starts with a '0', the field will be zero-padded (otherwise space-padded). Normally, fields are not truncated to the given width if the result string is longer; to force truncation to the specified width, specify '=' before the width (e.g. {0:=64s} limits the length of the displayed value of parameter 0 to 64 characters).

A decimal point (.) followed by a positive integer prec (precision) sets the number of digits to be displayed right of the decimal for formats f, e, E, g and G. If width is not specified, the value is formatted with the whole portion of the value taking as much space as needed, followed by the fractional part of the value to the specified prec.

The format is the format to be used to display the value:

  • s — display the value as a string; this is the default if no format is specified. For data values that are not strings, the JavaScript rules for converting to string are applied.
  • q — display the value as a quoted string (e.g. "hello there"); if the value contains quotes, they will shown escaped (e.g. "Dave's not \"here\" man!").
  • d — value must be numeric and is displayed as a decimal (base 10) number.
  • b — value must be an integer and is displayed as a binary (base 2) number.
  • o — value must be an integer and is displayed as an octal (base 8) number.
  • x or X — value must be an integer is formatted as a hexadecimal (base 16) number (if capital X is used, hex digits A-F are used rather than a-f).
  • f — value must be numeric and is displayed as a decimal number; typically prec is specified with this form (e.g. {0:.2f} for two digits right of the decimal).
  • e or E — value must be numeric and is formatted in scientific notation as one might see from a scientific calculator, such as 5.25E+3 for the number 5250. As with f, the use of prec is optional, but recommended (e.g. {0:.5e}). The case of the e in the output matches the format chosen here.
  • g or G — value must be numeric and is displayed like f unless very large or very small, in which case e (or E) is used.
  • t, h or H — the value (a Linux timestamp) is formatted as a date and time, time only, or date only, respectively, using the current locale settings;
  • T — the value is recursively put through the translator, that is, the value is looked up as a translatable string and, if found, the translation is inserted;
  • "quoted-string" — if the format specified is a quoted string (delimited at each end by ", ', or \``), then regardless of the value at the value index, the quoted string is inserted. This is intended for use with *conditions* (below) to remap values (e.g. you could have it outputnoneinstead of0`) or help with pluralization of values. See the examples below.

Finally, there is an optional condition (preceded by a question mark ?). When present, the value is tested against the condition, and the value is only included if the test succeeds (is true). If the test fails, the substitution produces no output. The following condition operations are permitted:

  • # (first character only) — replaces the parameter value with the string length of the parameter value; the replaced value (i.e. the length) is used in the condition to follow. The most common use of this is to add elipsis when a fixed-length string is truncated: {0:=64s}{0:"..."?#>64}
  • @ (first character only) — replaces the parameter value with the (JavaScript) type of the parameter value; the replaced value (i.e. the type) is used in the condition to follow. Only the string comparison operators (: and ::) should be used in operations to compare type.
  • ! (leading character) — if the leading character (the first character after # or @, if used) is !, the sense of the condition defined by the remaining characters is inverted.
  • >m, >=m, <m, <=m, =m — compares if the value at the value index is greater than, greater than or equal to, less than, less than or equal to, or simply equal to, respectively, the value m (which must be numeric).
  • t or f — matches the boolean value true or false (the value at the value index must be a boolean type value).
  • n — matches null.
  • : or :: — string comparison. Because = is a numeric equality test (only), the : and :: must be used to compare a value to a string. The compared string is the remainder of the condition after the :/::. The difference between : and :: is that : performs a case-insensitive search (e.g. a matches A), where :: is case-sensitive (e.g. a does not match A). If the string starts with / or ~ it is assumed to be a JavaScript-compatible regular expression. The regular expression may include the JavaScript Unicode special patterns.

As you can see, there is potential for these substitutions to get quite complex, but let's look at few examples, and hopefully you'll get a feel for how they work.

String: pulse for {0} secs{1:", repeat after "?>0}{1:d?>0}{1:" secs"?>0}{2:" up to "?>0}{2:d?>0}{2:" times"?>0} — the locale_en-US.yaml file tells is that the values are 0=pulse time (integer), 1=break time before repeat pulse (integer, 0 if non-repeating), and 2=max number of repeats (integer, 0 if no limit). Looking at each part:

  • {0} formats value 0 using the default conversion.

  • The goal for value 1 is to display nothing if it is 0, or otherwise display ", repeat after n secs" (where n is the value) otherwise. This is done in three parts: a prefix, the value, and a suffix. A condition (?>0), identical in each part in this case, controls whether the three parts together are displayed or suppressed:

    1. The format {1:", repeat after"?>0} inserts the string ", repeat after" into the output if value 1 is greater than 0. It produces an empty string otherwise (it's basically skipped). Note that the prefix also contains a comma and space to format it properly to follow value 0 in the result if the string is added to the result.
    2. The format {1:d?>0} inserts formatted value 1 only if it is greater than 0 (same test as the previous format); it is empty otherwise.
    3. Finally, format {1:" secs"?>0} inserts the string " secs" if value 1 is greater than 0 (again, same test); it is empty otherwise.
  • Value 2 is displayed in the same matter as value 1 (nothing if the value is zero, prefix + value + suffix otherwise).

String: ({0:d} condition{0:"s"?!=1} collapsed) — this is used to display a count of collapsed conditions. In English, this will display "1 condition" or "n conditions" (pluralized) if n<>0; the "s" is added when the value is not 1 (singular).

String: ({0:"not "?f}case sensitive) — the result is "not case sensitive" if the boolean value 0 is false, or "case sensitive" if it is true.

String: {0:"none"?=0}{0:d?!=0} &mdaash; the result is "none" if value 0 is 0, and the formatted value otherwise (e.g. "5").

String: 0x{0:08x} — formats value 0 as an 8-digit hexadecimal string with leading zeroes and prefixed "0x" (e.g. 65535 would be formatted "0x0000ffff").

String: {0:^16s} — formats value 0 centered in a space of 16 characters; if the value is "device", then the result would be "⋅⋅⋅⋅⋅device⋅⋅⋅⋅⋅" (blanks are shown as ⋅ for clarity).

Contributing Your Language

If you want to contribute your language file to the Reactor community, there are a few things you should know:

  1. You will be expected to support users of your localization, which includes:

    • Keeping up with updates/changes to the language strings as new versions of Reactor are published;
    • Accepting problem reports from users of your language, and responding to them (and fixing them if necessary);
    • If you are no longer able to support your language, finding another person to take the project and carry it forward.
  2. You should publish your work on Github, to make it easy for users of your language to find and update their copy of your language file.

  3. You may, at your option, contribute your language file to be included with future builds of Reactor as an add-on (so in the build packages but not a part of Reactor — it remains your work product and property). To do so, just contact the author (toggledbits) via the Smart Home Community and let it be known you want your translation included in builds, and include the URL to your Github repository. We will simply pull your latest language file from your Github repository directly when and as needed during builds; we will not modify the file in any way, and you do not need to send us publishable versions. We will also include attribution on the About page of Reactor (using the attribution key text from the language file) when your translation is configured.

Tip

We recommend the use of Github branches to separate development from released product. Github typically starts with a "main" branch, which you can use as your release branch. Users (and Reactor, if your work is included in builds) will/should only pull from your main branch. Any other branches you create are for interim versions during modifications/development.

Tip

The txtools.js tool (in the tools subdirectory of your Reactor install directory) can be used to compare versions of translation files, and in particular, compare your translation file made from a previous version of the reference (US English) file with the current version. The output format is a little easier to read (I think) than diff, and makes it easier to find things that need to be changed (new strings, removed strings, etc.). See the source of the txtools.js for examples of how to do various comparisons. For information only, this is the tool that is now used to produce CHANGELOG entries.

Updated: 2021-Oct-13