--- title: 'HOWTO: l10n/i18n with QML' disqus: hackmd --- l10n[^l10n]/i18n[^i18n] with QML === [^l10n]: localization [^i18n]: internationalization [TOC] ## Overview This HOWTO serves is a collection of pointers and good practices on how to properly deal with stuff like date/time formatting, displaying numbers, or simply being available in the user's local language while respecting their locality rules. See this Wikipedia article for more detailed info: https://en.wikipedia.org/wiki/Internationalization_and_localization ## Making strings translatable ### Basic In the most basic form, a developer marks all the strings that could be visible to the user with the function `qsTr()`, with optional comments and/or disambiguation[^context]; e.g.: [^context]: "Sun" name of a planet, or day in the week? ```qml Button { // This user interface string is only used here //: Short day of the week // <--- comment for translators //~ Context Day of the week // <--- disambiguation text: qsTr("Sun") } ``` Strings that are meant only for developers, like debug output, need not be translated. ### Dynamic If the string to be translated consists of variable/dynamic parts, always use the positional placeholders (`%1, %2, ...`) and `String.arg()` method to concatenate the parts together, ie: ```qml let current = 0; const quarters = [1, 2, 3, 4] ; const text = qsTr("Current quarter: %1").arg(quarters[current]) ``` ### Plurals A lot of languages make a more comprehensive distinction between quantities of `1` (singular) and `more` (plural); some have a special meaning for `zero`, some have an extra category called `dual` (usually 2, 3, 4). Example: ```qml Text { property int count: messages.count text: qsTr("%n message(s) saved", "", count) } ``` Depending on the user's locale, this would produce for example: ```qml 1 message; n messages // in English; <n> is anything > 1 1 zpráva; 2,3,4 zprávy; 5+ zpráv // in Czech 1 сообщение; 2,3,4 сообщения; 5+ сообщений // in Russian ``` [More info about plurals, for C++](https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals) [Table of plural rules](https://doc.qt.io/qt-5/i18n-plural-rules.html) ## The Locale object The [`Locale`](https://doc.qt.io/qt-5/qml-qtqml-locale.html) object may only be created via the [`Qt.locale()`](https://doc.qt.io/qt-5/qml-qtqml-qt.html#locale-method) function, it can not be created directly. The `Qt.locale()` function returns a JS Locale object representing the locale with the specified name, which has the format `language[_territory][.codeset][@modifier]` or `C`. Locale supports the concept of a default locale, which is determined from the system's locale settings at application startup. If no parameter is passed to `Qt.locale()`, the default locale object is returned. The Locale object provides a number of functions and properties providing data for the specified language/country pair. **NOTE**: As a rule of a thumb, do not "assume" anything. Some languages are written from right to left (RTL), so don't hardcode strings or layouts, decimal separator can be different from just `.` or `,`, first day of week is usually Sunday or Monday (except for countries where it's not!), timezone differences are usually in integral hours (except for a few countries with a 90 minute difference :) Etc... ## Formatting "stuff" ### Numbers The easiest (and unfortunately least known) way to format a number is to use the special `L` prefix to the `.arg()` format specifier: ```qml const metric = Qt.locale().measurementSystem === Locale.MetricSystem; const distance = metric ? 384400.123 : 238855.345; const unit = metric ? "km" : "mi"; Button { text: qsTr("Distance to moon: %L1 %2").arg(distance).arg(unit) } ``` would display e.g. ``238,855.345 mi`` for English users, while it will display `384.400,123 km` for German users (notice the use of a different thousands and decimal separators). This works for any string object in fact: ```javascript let anotherFormattedNumber = "%L1".arg(3.1415926) // works as well! ``` In addition, the regular JavaScript `Number` object has a couple of "augmented" methods in QML, such as [`toLocaleString(locale, format, precision)`](https://doc.qt.io/qt-5/qml-qtqml-number.html#toLocaleString-method): ```qml Text { text: qsTr("The value is: %1") .arg(Number(4742378.423).toLocaleString(Qt.locale("de_DE"))) } ``` This works for any number too, as long as it contains the decimal separator: ```qml 123.0.toLocaleString(Qt.locale("de_DE")) // OK ``` ### Currency values #### Symbol To get the currency symbol, call [`string Locale.currencySymbol(format)`](https://doc.qt.io/qt-6/qml-qtqml-locale.html#currencySymbol-method), where `format` is one of: ```qml enum { Locale.CurrencyIsoCode // a ISO-4217 code of the currency, e.g. "EUR" Locale.CurrencySymbol // a currency symbol, e.g. "€" Locale.CurrencyDisplayName // a user readable name of the currency, e.g. "Euro" } ``` #### Money, money, money To format a monetary value, use [`string Number.toLocaleCurrencyString(locale, symbol)`](https://doc.qt.io/qt-5/qml-qtqml-number.html#toLocaleCurrencyString-method) method, for example: ```json console.log(123.5.toLocaleString()) // prints e.g. "123,5 Kč"; both params defaulted console.log(123.5.toLocaleString(Qt.locale("de_DE")) // prints "123,5 €" ``` ### Date & time Just like `Number`, the JavaScript `Date` object also has a couple of interesting methods under QML: 1. To format date only: [`string Date.toLocaleString(locale, format)`](https://doc.qt.io/qt-5/qml-qtqml-date.html#toLocaleDateString-method) 2. To format time only: [`string Date.toLocaleTimeString(locale, format)`](https://doc.qt.io/qt-5/qml-qtqml-date.html#toLocaleTimeString-method) 3. To format both date and time: [`string Date.toLocaleString(locale, format)`](https://doc.qt.io/qt-5/qml-qtqml-date.html#toLocaleString-method) where `locale` is of type [`Locale`](https://doc.qt.io/qt-5/qml-qtqml-locale.html), usually obtained by `Qt.locale()`, and `format` can be either an enum of: ```qml enum { Locale.LongFormat // Longer format Locale.ShortFormat // Shorter format Locale.NarrowFormat // In this context same as Locale.ShortFormat } ``` In general, always prefer the former approach with a symbolic name (e.g. `Locale.ShortFormat`), do not make assumptions about what components and separators make up for a correct string-based format for a specific locale. <details> <summary>or a literal string format: (click to expand)</summary> |Expression|Output| |-|-| d|The day as a number without a leading zero (1 to 31) dd|The day as a number with a leading zero (01 to 31) ddd|The abbreviated localized day name (e.g. 'Mon' to 'Sun'). Uses the system locale to localize the name, i.e. QLocale::system(). dddd|The long localized day name (e.g. 'Monday' to 'Sunday'). Uses the system locale to localize the name, i.e. QLocale::system(). M|The month as a number without a leading zero (1 to 12) MM|The month as a number with a leading zero (01 to 12) MMM|The abbreviated localized month name (e.g. 'Jan' to 'Dec'). Uses the system locale to localize the name, i.e. QLocale::system(). MMMM|The long localized month name (e.g. 'January' to 'December'). Uses the system locale to localize the name, i.e. QLocale::system(). yy|The year as a two digit number (00 to 99) yyyy|The year as a four digit number. If the year is negative, a minus sign is prepended, making five characters. h|The hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display) hh|The hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display) H|The hour without a leading zero (0 to 23, even with AM/PM display) HH|The hour with a leading zero (00 to 23, even with AM/PM display) m|The minute without a leading zero (0 to 59) mm|The minute with a leading zero (00 to 59) s|The whole second, without any leading zero (0 to 59) ss|The whole second, with a leading zero where applicable (00 to 59) z|The fractional part of the second, to go after a decimal point, without trailing zeroes (0 to 999). Thus "s.z" reports the seconds to full available (millisecond) precision without trailing zeroes. zzz|The fractional part of the second, to millisecond precision, including trailing zeroes where applicable (000 to 999). AP or A|Use AM/PM display. A/AP will be replaced by an upper-case version of either QLocale::amText() or QLocale::pmText(). ap or a|Use am/pm display. a/ap will be replaced by a lower-case version of either QLocale::amText() or QLocale::pmText(). t|The timezone (for example "CEST") </details> ### Calendaring Our Locale object has again a few methods to help us here, like [`firstDayOfWeek`](https://doc.qt.io/qt-5/qml-qtqml-locale.html#firstDayOfWeek-prop), [`string dayName(day, type)`](https://doc.qt.io/qt-5/qml-qtqml-locale.html#standaloneDayName-method) or [`string monthName(month, type)`](https://doc.qt.io/qt-5/qml-qtqml-locale.html#standaloneMonthName-method). Note that in the JS world, days are numbered from `0` (Sunday) to `6` (Saturday), and months from `0` (January) to `11` (December). ### Timezones TODO ## RTL languages TODO? ## Appendix :::info **Find this document incomplete?** Leave a comment! ::: ###### tags: `l10n` `i18n` `qt` `qml`