/app/assets/javascripts/locale/index.js

https://github.com/gitlabhq/gitlabhq · JavaScript · 130 lines · 52 code · 11 blank · 67 comment · 7 complexity · 62f255e535e61f036a46b3052fc93a35 MD5 · raw file

  1. import Jed from 'jed';
  2. import ensureSingleLine from './ensure_single_line';
  3. import sprintf from './sprintf';
  4. const GITLAB_FALLBACK_LANGUAGE = 'en';
  5. const languageCode = () =>
  6. document.querySelector('html').getAttribute('lang') || GITLAB_FALLBACK_LANGUAGE;
  7. const locale = new Jed(window.translations || {});
  8. delete window.translations;
  9. /**
  10. Translates `text`
  11. @param text The text to be translated
  12. @returns {String} The translated text
  13. */
  14. const gettext = (text) => locale.gettext(ensureSingleLine(text));
  15. /**
  16. Translate the text with a number
  17. if the number is more than 1 it will use the `pluralText` translation.
  18. This method allows for contexts, see below re. contexts
  19. @param text Singular text to translate (eg. '%d day')
  20. @param pluralText Plural text to translate (eg. '%d days')
  21. @param count Number to decide which translation to use (eg. 2)
  22. @returns {String} Translated text with the number replaced (eg. '2 days')
  23. */
  24. const ngettext = (text, pluralText, count) => {
  25. const translated = locale
  26. .ngettext(ensureSingleLine(text), ensureSingleLine(pluralText), count)
  27. .replace(/%d/g, count)
  28. .split('|');
  29. return translated[translated.length - 1];
  30. };
  31. /**
  32. Translate context based text
  33. Either pass in the context translation like `Context|Text to translate`
  34. or allow for dynamic text by doing passing in the context first & then the text to translate
  35. @param keyOrContext Can be either the key to translate including the context
  36. (eg. 'Context|Text') or just the context for the translation
  37. (eg. 'Context')
  38. @param key Is the dynamic variable you want to be translated
  39. @returns {String} Translated context based text
  40. */
  41. const pgettext = (keyOrContext, key) => {
  42. const normalizedKey = ensureSingleLine(key ? `${keyOrContext}|${key}` : keyOrContext);
  43. const translated = gettext(normalizedKey).split('|');
  44. return translated[translated.length - 1];
  45. };
  46. /**
  47. * Filters navigator languages by the set GitLab language.
  48. *
  49. * This allows us to decide better what a user wants as a locale, for using with the Intl browser APIs.
  50. * If they have set their GitLab to a language, it will check whether `navigator.languages` contains matching ones.
  51. * This function always adds `en` as a fallback in order to have date renders if all fails before it.
  52. *
  53. * - Example one: GitLab language is `en` and browser languages are:
  54. * `['en-GB', 'en-US']`. This function returns `['en-GB', 'en-US', 'en']` as
  55. * the preferred locales, the Intl APIs would try to format first as British English,
  56. * if that isn't available US or any English.
  57. * - Example two: GitLab language is `en` and browser languages are:
  58. * `['de-DE', 'de']`. This function returns `['en']`, so the Intl APIs would prefer English
  59. * formatting in order to not have German dates mixed with English GitLab UI texts.
  60. * If the user wants for example British English formatting (24h, etc),
  61. * they could set their browser languages to `['de-DE', 'de', 'en-GB']`.
  62. * - Example three: GitLab language is `de` and browser languages are `['en-US', 'en']`.
  63. * This function returns `['de', 'en']`, aligning German dates with the chosen translation of GitLab.
  64. *
  65. * @returns {string[]}
  66. */
  67. export const getPreferredLocales = () => {
  68. const gitlabLanguage = languageCode();
  69. // The GitLab language may or may not contain a country code,
  70. // so we create the short version as well, e.g. de-AT => de
  71. const lang = gitlabLanguage.substring(0, 2);
  72. const locales = navigator.languages.filter((l) => l.startsWith(lang));
  73. if (!locales.includes(gitlabLanguage)) {
  74. locales.push(gitlabLanguage);
  75. }
  76. if (!locales.includes(lang)) {
  77. locales.push(lang);
  78. }
  79. if (!locales.includes(GITLAB_FALLBACK_LANGUAGE)) {
  80. locales.push(GITLAB_FALLBACK_LANGUAGE);
  81. }
  82. return locales;
  83. };
  84. /**
  85. Creates an instance of Intl.DateTimeFormat for the current locale.
  86. @param formatOptions for available options, please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
  87. @returns {Intl.DateTimeFormat}
  88. */
  89. const createDateTimeFormat = (formatOptions) =>
  90. Intl.DateTimeFormat(getPreferredLocales(), formatOptions);
  91. /**
  92. * Formats a number as a string using `toLocaleString`.
  93. *
  94. * @param {Number} value - number to be converted
  95. * @param {options?} options - options to be passed to
  96. * `toLocaleString` such as `unit` and `style`.
  97. * @param {langCode?} langCode - If set, forces a different
  98. * language code from the one currently in the document.
  99. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
  100. *
  101. * @returns If value is a number, the formatted value as a string
  102. */
  103. function formatNumber(value, options = {}, langCode = languageCode()) {
  104. if (typeof value !== 'number' && typeof value !== 'bigint') {
  105. return value;
  106. }
  107. return value.toLocaleString(langCode, options);
  108. }
  109. export { languageCode };
  110. export { gettext as __ };
  111. export { ngettext as n__ };
  112. export { pgettext as s__ };
  113. export { sprintf };
  114. export { createDateTimeFormat };
  115. export { formatNumber };
  116. export default locale;