Working with formatting syntax across platforms
You may know this concept by various names. It refers to the use of placeholders such as "%s"
or "{name}"
that will be substituted for parameters in your application.
On many platforms these strings are used by applying functions like printf. The process is called string interpolation.
Loco's support for formatted strings serves several purposes in your workflow:
- Validating translations for correct syntax matching the source text.
- Syntax highlighting makes it easier to see mistakes as you type.
- Converting formats between platforms that have different syntax.
Supported platforms
Loco understands platform-specific printf
styles for:
- Java:
java.util.Formatter
(used by Android), - Objective C:
NSString
(used by iOS), - PHP:
printf
(e.g. Wordpress), and - Python: old style.
Additionally it understands ICU MessageFormat, which is a more powerful, cross-platform syntax explained further down. This is the default format used for auto-detection when no other format is specified. See developer settings.
Although Loco is platform agnostic, many formatting implementations are not interoperable. For this reason every asset in your project has a setting which tells Loco what syntax you're using.
Some common tokens like "%d"
may be universal, but there are plenty more examples where the intended platform makes a difference.
When you import a file from one of the supported platforms, formatted strings are detected automatically and your assets will be assigned the appropriate property where possible. Outside of platform-specific contexts, your project settings apply.
Changing the asset property
The formatting syntax of each asset is held in a property you control. You can change or disable an asset's formatting via the asset properties dialogue, as shown below:
Asset properties are accessible from the Management view of your project, by clicking on an asset's :cog icon:. The tab shown above requires Developer permissions on the project. Translators don't generally have permission to change this setting.
Note that changing this property from the dropdown list doesn't modify any translations. You're only telling Loco what the format is intended to be. Knowing the intended syntax is necessary for accurate validation and conversion of strings.
Validating translations
You want to be sure that when your translators copy the formatting of your source text, that they do it correctly and don't cause errors in your application.
For example: The source text "Hello %s"
could be translated to "Hola %s"
or "Hola %1$s"
, but "Hola $s"
would be a mistake.
As long as Loco knows the intended syntax of a source text, it will validate the translations and alert you to errors. You can also enable automatic flagging of invalid translations in your project settings.
Code highlighting
Validation is performed once a translation is saved, but you can check validity as you type by enabling the code view. Clicking the editor's :code icon: will enable syntax highlighting for string formatting tokens.
False positives
If you see validation errors for strings that are not supposed to be formatted, you can open the asset properties and disable formatting syntax. This will suppress validation for all translations of the string, and also prevent highlighting of the phantom tokens.
Example: The text "20% off"
is probably not intended to be formatted, but actually it is a valid printf template on all supported platforms
because "% o"
is a space-padded octal. More about false positives in the custom syntax section.
Plural forms
When validating a plural form like "%d items"
translations are permitted to have one missing argument.
This tolerates the common practice of passing the same arguments to a plural string containing a number, as to a singular string that doesn't require a number.
Most platforms forgive the passing of redundant arguments, but complain if the string has too many.
Note that entering a numeric pattern like this doesn't automatically tell Loco the asset is a plural form. See Managing pluralized translations.
Exporting and converting between platforms
Loco's various export formats will treat your formatted strings in different ways, depending on the format setting of the asset and the syntax of the target platform.
iOS strings
iOS exports (including.strings
,.stringsdict
and.xliff
for Xcode) will convert formatting tokens to Apple's Objective C format. See Android to iOS conversion.Android strings
Android XML and other Java exports will automatically convert formatting tokens to the Java printf format as supported by Android. See iOS to Android conversion.PHP exports
The various PHP export formats will automatically convert formatting tokens to PHP's sprintf format.Gettext
PO and POT exports don't have a native syntax, but messages will be exported with the appropriate"x-format"
or"no-x-format"
flags, where"x"
is one of the supported platforms.ICU MessageFormat
ICU MessageFormat is cross-platform, so Loco never converts this syntax automatically. However,printf
formats can be coerced to ICU MessageFormat by passingprintf=icu
to the export API. See the dedicated section below.Other
All other export formats will render your formatting tokens as is, but if you're using the export API you can force conversion by specifying the printf parameter.
Note that in order for any formatting conversion to work for an individual asset, it's formatting property must be set:
- Assets with no format defined will NOT have their formatting converted.
- Assets with formatting disabled will NOT have their formatting converted.
- Assets with the same formatting as the target platform will NOT have their formatting converted.
Interoperable syntax
Loco will try to convert string formatting as far as possible, but it's recommended to stick to simple patterns if you need good interoperability.
Complex patterns using proprietary features might be totally incompatible with other platforms you want to export to. In some cases the target platform simply has no equivalent to what you take for granted in your source platform, for example:
- Java date formatting like
"%tD"
has no equivalent in other printf dialects, so defaults to"%s"
. - A floating point like
"%.*2$f"
is meaningful in C, but no other format supports variable precision. - The string
"%Td
is valid in Java and C, but with totally different meanings.
Incompatible flags and modifiers are dropped during conversion, and any incompatible conversion type defaults to the target platform's most basic string type.
Custom template syntax
It's not uncommon for developers to use custom syntax for proprietary template engines. Care must be taken to avoid unwanted behaviour, because Loco may not understand your particular engine. Take the following string imported from a PHP file:
[ "Result" => "You scored %score% out of 100" ]
A human can see that "%score%"
is supposed to be a custom template variable, but the string actually contains two valid printf
arguments.
The first "%s"
is quite obvious, but "% o"
is also valid. (It's a space-padded octal).
For this reason you may find that your strings have been automatically assigned a format, causing your translators to get strange errors when their texts are validated.
The solution is to disable string formatting for the asset, or add appropriate metadata to your source files before you import them.
Some file formats allow you to declare that a string is not formatted:
- Gettext supports a
"no-x-format"
flag in PO and POT files, (wherex
is a format such as"php"
). - Loco's PHP extractor supports Gettext flags in comments, e.g.
/* xgettext: no-php-format */
- Android XML supports a
formatted="false"
attribute on<string>
elements.
Android ↔ iOS formatted strings
Converting formatted strings between iOS and Android deserves a special mention because it's a common pair of platforms to convert between and their respective printf
styles have a lot of differences.
Android to iOS
If an asset is configured as a Java string, it will be converted to iOS as follows:
- All instances of
"%s"
are converted to"%@"
. - Positional arguments are maintained even if they would be valid without. e.g.
"%1$s %2$s"
will become"%1$@ %2$@"
NOT"%@ %@"
. - Unsupported conversion types such as
"%h"
and"%tY"
will simply default to"%@"
.
Note that "%s"
is valid in Objective C, but "%@
" is conventional and more likely desired when converting from Java.
If you must keep "%s"
in your iOS export, then set the asset property to "Objective C".
iOS to Android
If an asset is configured as an Objective C string, it will be converted to Android as follows:
- All instances of
"%@"
are converted to"%s"
. - Positional arguments are enforced for multiple substitutions. e.g
"%d %d"
becomes"%$1d %2$d"
. - Objective C integer types like
"%i"
and"%u"
are converted to"%d"
. - Other unsupported conversion types default to
"%s"
.
For backward compatibility, strings that have no defined syntax but contain "%@"
symbols will be converted to Android automatically.
If this legacy behaviour is unwanted then disable formatting for the asset.
ICU MessageFormat
MessageFormat is much more expressive than printf
syntax and is available on multiple platforms. As part of Unicode's ICU project
there are official libraries for C and Java and it is well supported in PHP's Intl extension. Support in other languages may be provided by libraries, such as Format.js for JavaScript.
As MessageFormat is inherently cross-platform, you're unlikely to be converting it to other (simpler) printf styles. However, this is possible.
printf → MessageFormat
At its simplest, MessageFormat provides a linear sequence of scalar formatters like printf
does. This allows Loco to convert basic placeholders, like the following:
%s
→{0}
%d
→{0,number,#}
%tR
→{0,time,HH:mm}
(Java only)
MessageFormat syntax is not native to any particular file format. To perform conversion like above, you must pass printf=icu
to the export API.
Doing so will also convert Loco's linked plural forms into ICU PluralFormat.
See Managing pluralized translations.
MessageFormat → printf
Conversion in reverse is supported, but severely limited by the simplicity of printf
formatting. Complex types, nested placeholders and named parameters will all be lost.
However conversion of simple types like above will work in reverse.
Loco will never convert from MessageFormat automatically, because it isn't a platform specific style. For example: exporting to Android will keep ICU-formatted strings as they are,
unless you force conversion by passing printf=java
to the export API. The same goes for all supported printf styles.