Jelajahi Sumber

fix: Regional locales weren't being applied.

JC Brand 1 bulan lalu
induk
melakukan
fb4123beb2
6 mengubah file dengan 153 tambahan dan 55 penghapusan
  1. 0 1
      dev.html
  2. 1 0
      karma.conf.js
  3. 44 44
      src/i18n/index.js
  4. 80 0
      src/i18n/tests/i18n.js
  5. 5 1
      src/index.js
  6. 23 9
      src/types/i18n/index.d.ts

+ 0 - 1
dev.html

@@ -26,7 +26,6 @@
     });
 
     converse.initialize({
-        i18n: 'en',
         theme: 'nordic',
         dark_theme: 'dracula',
         auto_away: 300,

+ 1 - 0
karma.conf.js

@@ -155,6 +155,7 @@ module.exports = function(config) {
       { pattern: "src/plugins/rosterview/tests/unsaved-contacts.js", type: 'module' },
       { pattern: "src/shared/modals/tests/user-details-modal.js", type: 'module' },
       { pattern: "src/utils/tests/url.js", type: 'module' },
+      { pattern: "src/i18n/tests/i18n.js", type: 'module' },
 
       // For some reason this test causes issues when its run earlier
       { pattern: "src/headless/tests/persistence.js", type: 'module' },

+ 44 - 44
src/i18n/index.js

@@ -37,7 +37,7 @@ function determineLocale(preferred_locale, isSupportedByLibrary) {
 
     let locale;
     for (let i = 0; i < languages.length && !locale; i++) {
-        locale = isLocaleAvailable(languages[i], isSupportedByLibrary);
+        locale = isLocaleAvailable(languages[i].replace('-', '_'), isSupportedByLibrary);
     }
     return locale || 'en';
 }
@@ -51,7 +51,7 @@ function isLocaleAvailable(locale, available) {
     if (available(locale)) {
         return locale;
     } else {
-        var sublocale = locale.split('-')[0];
+        const sublocale = locale.split('_')[0];
         if (sublocale !== locale && available(sublocale)) {
             return sublocale;
         }
@@ -85,52 +85,52 @@ async function fetchTranslations() {
     return new Jed(data);
 }
 
+function getLocale() {
+    return locale;
+}
+
+/**
+ * @param {string} str - The string to be translated
+ * @param {Array<any>} args
+ */
+function translate(str, args) {
+    if (!jed_instance) {
+        return Jed.sprintf.apply(Jed, arguments);
+    }
+    const t = jed_instance.translate(str);
+    if (arguments.length > 1) {
+        return t.fetch.apply(t, args);
+    } else {
+        return t.fetch();
+    }
+}
+
+async function initialize() {
+    try {
+        const preferred_locale = api.settings.get('i18n');
+        const available_locales = api.settings.get('locales');
+        const isSupportedByLibrary = /** @param {string} pref */ (pref) => isConverseLocale(pref, available_locales);
+        locale = determineLocale(preferred_locale, isSupportedByLibrary);
+        jed_instance = await fetchTranslations();
+    } catch (e) {
+        log.fatal(e.message);
+        locale = 'en';
+    }
+}
+
+export function __(str, ...args) {
+    return i18n.translate(str, args);
+}
+
 /**
  * @namespace i18n
  */
 const i18n = Object.assign(i18nStub, {
-    getLocale() {
-        return locale;
-    },
-
-    /**
-     * @param {string} str - The string to be translated
-     * @param {Array<any>} args
-     */
-    translate(str, args) {
-        if (!jed_instance) {
-            return Jed.sprintf.apply(Jed, arguments);
-        }
-        const t = jed_instance.translate(str);
-        if (arguments.length > 1) {
-            return t.fetch.apply(t, args);
-        } else {
-            return t.fetch();
-        }
-    },
-
-    async initialize() {
-        if (u.isTestEnv()) {
-            locale = 'en';
-        } else {
-            try {
-                const preferred_locale = api.settings.get('i18n');
-                const available_locales = api.settings.get('locales');
-                const isSupportedByLibrary = /** @param {string} pref */ (pref) =>
-                    isConverseLocale(pref, available_locales);
-                locale = determineLocale(preferred_locale, isSupportedByLibrary);
-                jed_instance = await fetchTranslations();
-            } catch (e) {
-                log.fatal(e.message);
-                locale = 'en';
-            }
-        }
-    },
-
-    __(str, ...args) {
-        return i18n.translate(str, args);
-    },
+    __,
+    determineLocale,
+    getLocale,
+    initialize,
+    translate,
 });
 
 export { i18n };
-export const __ = i18n.__;

+ 80 - 0
src/i18n/tests/i18n.js

@@ -0,0 +1,80 @@
+/*global mock, converse */
+
+describe('i18n', () => {
+    describe('translate', () => {
+        it("can translate strings with placeholders",
+            mock.initConverse([], {}, async function (_converse) {
+                const { __ } = _converse.env.i18n;
+                const translated = __('Hello %1$s', 'world');
+                expect(translated).toBe('Hello world');
+            })
+        );
+    });
+
+    describe('determineLocale', () => {
+        it("returns the preferred locale if supported",
+            mock.initConverse([], {
+                locales: ['en', 'es', 'fr'],
+                i18n: 'en'
+            }, async function (_converse) {
+                const { i18n } = _converse.env;
+                const locale = i18n.determineLocale('es', (l) => ['en', 'es', 'fr'].includes(l));
+                expect(locale).toBe('es');
+            })
+        );
+
+        it("supports regional dialects",
+            mock.initConverse([], {}, async function (_converse) {
+                const { i18n } = _converse.env;
+                Object.defineProperty(navigator, 'languages', {
+                    value: ['pt-BR'],
+                    configurable: true,
+                });
+                const locale = i18n.determineLocale(undefined, (l) => ['pt_BR'].includes(l));
+                expect(locale).toBe('pt_BR');
+            })
+        );
+
+        it("falls back to the non-regional version",
+            mock.initConverse([], {}, async function (_converse) {
+                const { i18n } = _converse.env;
+                Object.defineProperty(navigator, 'languages', {
+                    value: ['pt-BR'],
+                    configurable: true,
+                });
+                const locale = i18n.determineLocale(undefined, (l) => ['pt'].includes(l));
+                expect(locale).toBe('pt');
+            })
+        );
+
+        it("falls back to browser language if preferred not supported",
+            mock.initConverse([], {
+                locales: ['en', 'es', 'fr'],
+                i18n: 'en'
+            }, async function (_converse) {
+                const { i18n } = _converse.env;
+                Object.defineProperty(navigator, 'languages', {
+                    value: ['fr-FR', 'fr', 'en-US', 'en'],
+                    configurable: true,
+                });
+                const locale = i18n.determineLocale('de', (l) => ['en', 'es', 'fr'].includes(l));
+                expect(locale).toBe('fr');
+            })
+        );
+
+        it("falls back to \"en\" if no supported locale found",
+            mock.initConverse([], {
+                locales: ['en', 'es', 'fr'],
+                i18n: 'en'
+            }, async function (_converse) {
+                const { i18n } = _converse.env;
+                Object.defineProperty(navigator, 'languages', {
+                    value: ['de', 'it'],
+                    configurable: true,
+                });
+                const locale = i18n.determineLocale('ja', (l) => ['en', 'es', 'fr'].includes(l));
+                expect(locale).toBe('en');
+            })
+        );
+    });
+});

+ 5 - 1
src/index.js

@@ -5,7 +5,7 @@
  */
 import 'shared/styles/index.scss';
 
-import "i18n/index.js";
+import { i18n } from "i18n/index.js";
 import "shared/registry.js";
 import 'shared/components/index.js';
 import { CustomElement } from 'shared/components/element';
@@ -13,6 +13,10 @@ import { VIEW_PLUGINS } from './shared/constants.js';
 import { _converse, converse } from "@converse/headless";
 import "./utils/index.js";
 
+_converse.__ = i18n.__; // DEPRECATED
+Object.assign(converse.env, { i18n });
+Object.assign(_converse.env, { i18n });
+
 /* START: Removable plugins
  * ------------------------
  * Any of the following plugin imports may be removed if the plugin is not needed

+ 23 - 9
src/types/i18n/index.d.ts

@@ -1,16 +1,30 @@
-export const __: typeof i18nStub.__ & ((str: any, ...args: any[]) => any);
+export function __(str: any, ...args: any[]): any;
 /**
  * @namespace i18n
  */
 export const i18n: typeof i18nStub & {
-    getLocale(): string;
-    /**
-     * @param {string} str - The string to be translated
-     * @param {Array<any>} args
-     */
-    translate(str: string, args: Array<any>, ...args: any[]): any;
-    initialize(): Promise<void>;
-    __(str: any, ...args: any[]): any;
+    __: typeof __;
+    determineLocale: typeof determineLocale;
+    getLocale: typeof getLocale;
+    initialize: typeof initialize;
+    translate: typeof translate;
 };
 import { i18n as i18nStub } from '@converse/headless';
+/**
+ * Determines which locale is supported by the user's system as well
+ * as by the relevant library (e.g. converse.js or dayjs).
+ * @param {string} preferred_locale
+ * @param {Function} isSupportedByLibrary - Returns a boolean indicating whether
+ *   the locale is supported.
+ * @returns {string}
+ */
+declare function determineLocale(preferred_locale: string, isSupportedByLibrary: Function): string;
+declare function getLocale(): string;
+declare function initialize(): Promise<void>;
+/**
+ * @param {string} str - The string to be translated
+ * @param {Array<any>} args
+ */
+declare function translate(str: string, args: Array<any>, ...args: any[]): any;
+export {};
 //# sourceMappingURL=index.d.ts.map