---
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`