dataintl.rst (12845B)
1 .. role:: js(code) 2 :language: javascript 3 4 ========================= 5 UI Internationalization 6 ========================= 7 8 There are many types of data that need to be formatted into a locale specific format, 9 or require locale specific API operations. 10 11 Gecko provides a rich set of locale aware APIs for operations such as: 12 13 * date and time formatting 14 * number formatting 15 * searching 16 * sorting 17 * plural rules 18 * calendar and locale information 19 20 .. note:: 21 22 Most of the APIs are backed by the Unicode projects `CLDR`_ and `ICU`_ and are 23 focused on enabling front-end code internationalization, which means the majority of 24 the APIs are primarily available in JavaScript, with C++ and Rust having only a small 25 subset of them exposed. 26 27 JavaScript Internationalization API 28 =================================== 29 30 Data internationalization APIs are formalized in the JavaScript standard `ECMA 402`_. 31 These APIs are supported by all major JS environments. 32 33 It is best to consult the MDN article on the current state of the `Intl API`_. 34 Mozilla has an excellent support of the API and relies on it for majority 35 of its needs. Yet, when working on Firefox UI the :js:`Services.intl` wrapper 36 should be used. 37 38 Services.intl 39 ============= 40 41 :js:`Services.intl` is an extension of the JS Intl API which should be used whenever 42 working with Gecko app user interface with chrome privileges. 43 44 The API provides the same objects and methods as :js:`Intl.*`, but fine tunes them 45 to the Gecko app user preferences, including matching OS Preferences and 46 other locale choices that web content exposed JS Intl API cannot. 47 48 For example, here's an example of a locale aware date formatting 49 using the regular :js:`Intl.DateTimeFormat`: 50 51 .. code-block:: javascript 52 53 let rtf = new Intl.DateTimeFormat(navigator.languages, { 54 year: "numeric", 55 month: "long", 56 day: "numeric" 57 }); 58 let value = rtf.format(new Date()); 59 60 It will do a good job at formatting the date to the user locale, but it will 61 only be able to use the customization bits that are exposed to the Web, based on 62 the locale the user broadcasts to the Web and any additional settings. 63 64 But that ignores bits of information that could inform the formatting. 65 66 Public API such as :js:`Intl.*` will not be able to look into the Operating System for 67 regional preferences. It will also respect settings such as `Resist Fingerprinting` 68 by masking its timezone and locale settings. 69 70 This is a fair tradeoff when dealing with the Web Content, but in most cases, the 71 privileged UI of the Gecko application should be able to access all of those 72 additional bits and not be affected by the anti-fingerprinting masking. 73 74 `mozIntl` is a simple wrapper which in its simplest form works exactly the same. It's 75 exposed on :js:`Services.intl` object and can be used just like a regular `Intl` API: 76 77 .. code-block:: javascript 78 79 let rtf = new Services.intl.DateTimeFormat(undefined, { 80 year: "numeric", 81 month: "long", 82 day: "numeric" 83 }); 84 let value = rtf.format(new Date()); 85 86 The difference is that this API will now use the set of locales as defined for 87 Gecko, and will also respect additional regional preferences that Gecko 88 will fetch from the Operating System. 89 90 For those reasons, when dealing with Gecko application UI, it is always recommended 91 to use the :js:`Services.intl` wrapper. 92 93 Additional APIs 94 ================ 95 96 On top of wrapping up `Intl` API, `mozIntl` provides a number of features 97 in form of additional options to existing APIs as well as completely new APIs. 98 99 Many of those extensions are in the process of being standardized, but are 100 already available to Gecko developers for internal use. 101 102 Below is the list of current extensions: 103 104 mozIntl.DateTimeFormat 105 ---------------------- 106 107 `DateTimeFormat` in `mozIntl` gets additional options that provide greater 108 simplicity and consistency to the API. 109 110 * :js:`timeStyle` and :js:`dateStyle` can take values :js:`short`, :js:`medium`, 111 :js:`long` and :js:`full`. 112 These options can replace the manual listing of tokens like :js:`year`, :js:`day`, :js:`hour` etc. 113 and will compose the most natural date or time format of a given style for the selected 114 locale. 115 116 Using :js:`timeStyle` and :js:`dateStyle` is highly recommended over listing the tokens, 117 because different locales may use different default styles for displaying the same tokens. 118 119 Additional value is that using those styles allows `mozIntl` to look into 120 Operating System patterns, which gives users the ability to customize those 121 patterns to their liking. 122 123 Example use: 124 125 .. code-block:: javascript 126 127 let dtf = new Services.intl.DateTimeFormat(undefined, { 128 timeStyle: "short", 129 dateStyle: "short" 130 }); 131 let value = dtf.format(new Date()); 132 133 This will select the best locale to match the current Gecko application locale, 134 then potentially check for Operating System regional preferences customizations, 135 produce the correct pattern for short date+time style and format the date into it. 136 137 138 mozIntl.getCalendarInfo(locale) 139 ------------------------------- 140 141 The API will return the following calendar information for a given locale code: 142 143 * firstDayOfWeek 144 an integer in the range 1=Monday to 7=Sunday indicating the day 145 considered the first day of the week in calendars, e.g. 7 for en-US, 146 1 for en-GB, 7 for bn-IN 147 * minDays 148 an integer in the range of 1 to 7 indicating the minimum number 149 of days required in the first week of the year, e.g. 1 for en-US, 4 for de 150 * weekend 151 an array with values in the range 1=Monday to 7=Sunday indicating the days 152 of the week considered as part of the weekend, e.g. [6, 7] for en-US and en-GB, 153 [7] for bn-IN (note that "weekend" is *not* necessarily two days) 154 155 Those bits of information should be especially useful for any UI that works 156 with calendar data. 157 158 Example: 159 160 .. code-block:: javascript 161 162 // omitting the `locale` argument will make the API return data for the 163 // current Gecko application UI locale. 164 let { 165 firstDayOfWeek, // 1 166 minDays, // 4 167 weekend, // [6, 7] 168 calendar, // "gregory" 169 locale, // "pl" 170 } = Services.intl.getCalendarInfo(); 171 172 173 mozIntl.DisplayNames(locales, options) 174 ----------------------------------------- 175 176 :js:`DisplayNames` API is useful to retrieve various terms available in the 177 internationalization API. :js:`mozIntl.DisplayNames` extends the standard 178 `Intl.DisplayNames`_ to additionally provide localization for date-time types. 179 180 The API takes a locale fallback chain list, and an options object which can contain 181 two keys: 182 183 * :js:`style` which can take values :js:`narrow`, :js:`short`, :js:`abbreviated`, :js:`long` 184 * :js:`type` which can take values :js:`language`, :js:`script`, :js:`region`, 185 :js:`currency`, :js:`weekday`, :js:`month`, :js:`quarter`, :js:`dayPeriod`, 186 :js:`dateTimeField` 187 188 Example: 189 190 .. code-block:: javascript 191 192 let dateTimeFieldDisplayNames = new Services.intl.DisplayNames(undefined, { 193 type: "dateTimeField", 194 }); 195 dateTimeFieldDisplayNames.resolvedOptions().locale = "pl"; 196 dateTimeFieldDisplayNames.of("year") = "rok"; 197 198 let monthDisplayNames = new Services.intl.DisplayNames(undefined, { 199 type: "month", style: "long", 200 }); 201 monthDisplayNames.of(1) = "styczeń"; 202 203 let weekdaysDisplayNames = new Services.intl.DisplayNames(undefined, { 204 type: "weekday", style: "short", 205 }); 206 weekdaysDisplayNames.of(1) = "pon"; 207 208 let dayPeriodsDisplayNames = new Services.intl.DisplayNames(undefined, { 209 type: "dayPeriod", style: "narrow", 210 }); 211 dayPeriodsDisplayNames.of("am") = "AM"; 212 213 214 mozIntl.RelativeTimeFormat(locales, options) 215 -------------------------------------------- 216 217 API which can be used to format an interval or a date into a textual 218 representation of a relative time, such as **5 minutes ago** or **in 2 days**. 219 220 This API is in the process of standardization and in its raw form will not handle 221 any calculations to select the best unit. It is intended to just offer a way 222 to format a value. 223 224 `mozIntl` wrapper extends the functionality providing the calculations and 225 allowing the user to get the current best textual representation of the delta. 226 227 Example: 228 229 .. code-block:: javascript 230 231 let rtf = new Services.intl.RelativeTimeFormat(undefined, { 232 style: "long", // "narrow" | "short" | "long" (default) 233 numeric: "auto", // "always" | "auto" (default) 234 }); 235 236 let now = Date.now(); 237 rtf.formatBestUnit(new Date(now - 3 * 1000 * 60)); // "3 minutes ago" 238 239 The option `numeric` has value set to `auto` by default, which means that when possible 240 the formatter will use special textual terms like *yesterday*, *last year*, and so on. 241 242 Those values require specific calculations that the raw `Intl.*` API cannot provide. 243 For example, *yesterday* requires the algorithm to know not only the time delta, 244 but also what time of the day `now` is. 15 hours ago may be *yesterday* if it 245 is 10am, but will still be *today* if it is 11pm. 246 247 For that reason the future `Intl.RelativeTimeFormat` will use *always* as default, 248 since terms such as *15 hours ago* are independent of the current time. 249 250 .. note:: 251 252 In the current form, the API should be only used to format standalone values. 253 Without additional capitalization rules, it cannot be freely used in sentences. 254 255 mozIntl.getLanguageDisplayNames(locales, langCodes) 256 --------------------------------------------------- 257 258 API which returns a list of language names formatted for display. 259 260 Example: 261 262 .. code-block:: javascript 263 264 let langs = getLanguageDisplayNames(["pl"], ["fr", "de", "en"]); 265 langs === ["Francuski", "Niemiecki", "Angielski"]; 266 267 268 mozIntl.getRegionDisplayNames(locales, regionCodes) 269 --------------------------------------------------- 270 271 API which returns a list of region names formatted for display. 272 273 Example: 274 275 .. code-block:: javascript 276 277 let regs = getRegionDisplayNames(["pl"], ["US", "CA", "MX"]); 278 regs === ["Stany Zjednoczone", "Kanada", "Meksyk"]; 279 280 mozIntl.getLocaleDisplayNames(locales, localeCodes) 281 --------------------------------------------------- 282 283 API which returns a list of region names formatted for display. 284 285 Example: 286 287 .. code-block:: javascript 288 289 let locs = getLocaleDisplayNames(["pl"], ["sr-RU", "es-MX", "fr-CA"]); 290 locs === ["Serbski (Rosja)", "Hiszpański (Meksyk)", "Francuski (Kanada)"]; 291 292 mozIntl.getAvailableLocaleDisplayNames(type) 293 --------------------------------------------------- 294 295 API which returns a list of locale display name codes available for a 296 given type. 297 Available types are: "language", "region". 298 299 Example: 300 301 .. code-block:: javascript 302 303 let codes = getAvailableLocaleDisplayNames("region"); 304 codes === ["au", "ae", "af", ...]; 305 306 Best Practices 307 ============== 308 309 The most important best practice when dealing with data internationalization is to 310 perform it as close to the actual UI as possible; right before the UI is displayed. 311 312 The reason for this practice is that internationalized data is considered *"opaque"*, 313 which means that no code should ever attempt to operate on it. Late resolution also 314 increases the chance that the data will be formatted in the current locale 315 selection and not formatted and cached prematurely. 316 317 It's very important to not attempt to search, concatenate or in any other way 318 alter the output of the API. Once it gets formatted, the only thing to do with 319 the output should be to present it to the user. 320 321 Testing 322 ------- 323 324 The above is also important in the context of testing. It is a common mistake to 325 attempt to write tests that verify the output of the UI with internationalized data. 326 327 The underlying data set used to create the formatted version of the data may and will 328 change over time, both due to dataset improvements and also changes to the language 329 and regional preferences over time. 330 That means that tests that attempt to verify the exact output will require 331 significantly higher level of maintenance and will remain brittle. 332 333 Most of the APIs provide special method, like :js:`resolvedOptions` which should be used 334 instead to verify that the output is matching the expectations. 335 336 Future extensions 337 ================= 338 339 If you find yourself in the need of additional internationalization APIs not currently 340 supported, you can verify if the API proposal is already in the works here, 341 and file a bug in the component `Core::Internationalization`_ to request it. 342 343 .. _ECMA 402: https://tc39.github.io/ecma402/ 344 .. _Intl API: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl 345 .. _CLDR: http://cldr.unicode.org/ 346 .. _ICU: http://site.icu-project.org/ 347 .. _Core::Internationalization: https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Internationalization 348 .. _Intl.DisplayNames: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames