Browse Source

Move to new enum

Manav Rathi 1 year ago
parent
commit
1b6f020b23

+ 0 - 1
apps/photos/package.json

@@ -34,7 +34,6 @@
         "ffmpeg-wasm": "file:./thirdparty/ffmpeg-wasm",
         "file-type": "^16.5.4",
         "formik": "^2.1.5",
-        "get-user-locale": "^2.1.3",
         "hdbscan": "0.0.1-alpha.5",
         "heic-convert": "^2.0.0",
         "idb": "^7.1.1",

+ 23 - 14
apps/photos/src/components/Sidebar/Preferences/LanguageSelector.tsx

@@ -2,41 +2,50 @@ import DropdownInput, { DropdownOption } from 'components/DropdownInput';
 import { useLocalState } from '@ente/shared/hooks/useLocalState';
 import { t } from 'i18next';
 import { useRouter } from 'next/router';
-import { Language, getBestPossibleUserLocale } from '@/ui/i18n';
+import {
+    type SupportedLocale,
+    supportedLocales,
+    closestSupportedLocale,
+} from '@/ui/i18n';
 import { LS_KEYS } from '@ente/shared/storage/localStorage';
 import { getUserLocaleString } from '@ente/shared/storage/localStorage/helpers';
 
-const getLocaleDisplayName = (l: Language) => {
-    switch (l) {
-        case Language.en:
+/**
+ * Human readable name for each supported locale
+ *
+ * TODO (MR): This names themselves should be localized.
+ */
+export const localeName = (locale: SupportedLocale) => {
+    switch (locale) {
+        case 'en':
             return 'English';
-        case Language.fr:
+        case 'fr':
             return 'Français';
-        case Language.zh:
+        case 'zh':
             return '中文';
-        case Language.nl:
+        case 'nl':
             return 'Nederlands';
-        case Language.es:
+        case 'es':
             return 'Español';
     }
 };
 
-const getLanguageOptions = (): DropdownOption<Language>[] => {
-    return Object.values(Language).map((lang) => ({
-        label: getLocaleDisplayName(lang),
-        value: lang,
+const getLanguageOptions = (): DropdownOption<SupportedLocale>[] => {
+    return supportedLocales.map((locale) => ({
+        label: localeName(locale),
+        value: locale,
     }));
 };
 
 export const LanguageSelector = () => {
     const [userLocale, setUserLocale] = useLocalState(
         LS_KEYS.LOCALE,
-        getBestPossibleUserLocale(getUserLocaleString())
+        closestSupportedLocale(getUserLocaleString())
     );
 
     const router = useRouter();
 
-    const updateCurrentLocale = (newLocale: Language) => {
+    const updateCurrentLocale = (newLocale: SupportedLocale) => {
         setUserLocale(newLocale);
         router.reload();
     };

+ 56 - 42
packages/ui/i18n.ts

@@ -5,6 +5,22 @@ import { isDevBuild } from "@/utils/env";
 import { getUserLocales } from "get-user-locale";
 import { includes } from "@/utils/type-guards";
 
+/**
+ * List of all {@link SupportedLocale}s.
+ *
+ * Locales are combinations of a language code, and an optional region code.
+ *
+ * For example, "en", "en-US", "en-IN" (Indian English), "pt" (Portuguese),
+ * "pt-BR" (Brazilian Portuguese).
+ *
+ * In our Crowdin Project, we have work-in-progress translations into more
+ * languages than this. When a translation reaches a high enough coverage, say
+ * 90%, then we manually add it to this list of supported languages.
+ */
+export const supportedLocales = ["en", "fr", "zh", "nl", "es"] as const;
+/** The type of  {@link supportedLocale}s. */
+export type SupportedLocale = (typeof supportedLocales)[number];
+
 /**
  * Load translations.
  *
@@ -19,7 +35,8 @@ import { includes } from "@/utils/type-guards";
  * - react-i18next, which adds React specific APIs
  */
 export const setupI18n = async (savedLocaleString?: string) => {
-    const lng = getBestPossibleUserLocale(savedLocaleString);
+    const locale = closestSupportedLocale(savedLocaleString);
+
     // https://www.i18next.com/overview/api
     await i18n
         // i18next-http-backend: Asynchronously loads translations over HTTP
@@ -34,7 +51,8 @@ export const setupI18n = async (savedLocaleString?: string) => {
             debug: isDevBuild,
             returnEmptyString: false,
             fallbackLng: "en",
-            lng: lng,
+            // i18next calls it language, but it really is the locale
+            lng: locale,
             interpolation: {
                 escapeValue: false, // not needed for react as it escapes by default
             },
@@ -62,22 +80,6 @@ export const setupI18n = async (savedLocaleString?: string) => {
     });
 };
 
-/**
- * List of all {@link SupportedLocale}s.
- *
- * Locales are combinations of a language code, and an optional region code.
- *
- * For example, "en", "en-US", "en-IN" (Indian English), "pt" (Portuguese),
- * "pt-BR" (Brazilian Portuguese).
- *
- * In our Crowdin Project, we have work-in-progress translations into more
- * languages than this. When a translation reaches a high enough coverage, say
- * 90%, then we manually add it to this list of supported languages.
- */
-export const supportedLocales = ["en", "fr", "zh", "nl", "es"] as const;
-/** The type of  {@link supportedLocale}s. */
-export type SupportedLocale = (typeof supportedLocales)[number];
-
 /**
  * Return the current locale in which our user interface is being shown.
  *
@@ -90,18 +92,24 @@ export const currentLocale = () => {
     return locale && includes(supportedLocales, locale) ? locale : "en";
 };
 
-/** Enums of supported locale */
-export enum Language {
-    en = "en",
-    fr = "fr",
-    zh = "zh",
-    nl = "nl",
-    es = "es",
-}
-
-export function getBestPossibleUserLocale(
+/**
+ * Return the closest / best matching {@link SupportedLocale}.
+ *
+ * It takes as input a {@link savedLocaleString}, which denotes the user's
+ * explicitly chosen preference (which we then persist in local storage).
+ * Subsequently, we use this to (usually literally) return the supported locale
+ * that it represents.
+ *
+ * If {@link savedLocaleString} is `undefined`, it tries to deduce the closest
+ * {@link SupportedLocale} that matches the browser's locale.
+ */
+export function closestSupportedLocale(
     savedLocaleString?: string,
-): Language {
+): SupportedLocale {
+    const ss = savedLocaleString;
+    if (ss && includes(supportedLocales, ss)) return ss;
+
+    /*
     switch (savedLocaleString) {
         case "en":
             return Language.en;
@@ -114,20 +122,26 @@ export function getBestPossibleUserLocale(
         case "es":
             return Language.es;
     }
+    */
 
-    const userLocales = getUserLocales();
-    for (const lc of userLocales) {
-        if (lc.startsWith("en")) {
-            return Language.en;
-        } else if (lc.startsWith("fr")) {
-            return Language.fr;
-        } else if (lc.startsWith("zh")) {
-            return Language.zh;
-        } else if (lc.startsWith("nl")) {
-            return Language.nl;
-        } else if (lc.startsWith("es")) {
-            return Language.es;
+    for (const us of getUserLocales()) {
+        // Exact match
+        if (us && includes(supportedLocales, us)) return us;
+
+        // Language match
+        if (us.startsWith("en")) {
+            return "en";
+        } else if (us.startsWith("fr")) {
+            return "fr";
+        } else if (us.startsWith("zh")) {
+            return "zh";
+        } else if (us.startsWith("nl")) {
+            return "nl";
+        } else if (us.startsWith("es")) {
+            return "es";
         }
     }
-    return Language.en;
+
+    // Fallback
+    return "en";
 }

+ 1 - 0
packages/ui/package.json

@@ -11,6 +11,7 @@
         "@emotion/styled": "^11.11",
         "@mui/icons-material": "^5.15",
         "@mui/material": "^5.15",
+        "get-user-locale": "^2.3.1",
         "i18next": "^23.10.0",
         "i18next-http-backend": "^2.5.0",
         "react": "18.2.0",

+ 1 - 1
yarn.lock

@@ -2322,7 +2322,7 @@ get-tsconfig@^4.5.0:
   dependencies:
     resolve-pkg-maps "^1.0.0"
 
-get-user-locale@^2.1.3:
+get-user-locale@^2.3.1:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-2.3.1.tgz#fc7319429c8a70fac01b3b2a0b08b0c71c1d3fe2"
   integrity sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==