From 1c96a04523167f67aac7c168a84b048c1eb650aa Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 17 Jun 2025 10:35:36 -0400 Subject: [PATCH] fix: fix language default in picker. Misc i18n fixes --- .githooks/_/pre-commit | 12 +++++ src/components/LanguageSwitcher.tsx | 14 ++---- src/components/UI/ErrorPage.tsx | 2 +- src/core/hooks/useLang.ts | 71 ++++++++--------------------- src/i18n/config.ts | 31 ++++++++++--- 5 files changed, 63 insertions(+), 67 deletions(-) create mode 100755 .githooks/_/pre-commit diff --git a/.githooks/_/pre-commit b/.githooks/_/pre-commit new file mode 100755 index 00000000..a6e72484 --- /dev/null +++ b/.githooks/_/pre-commit @@ -0,0 +1,12 @@ +#!/bin/sh + +if [ "$SKIP_SIMPLE_GIT_HOOKS" = "1" ]; then + echo "[INFO] SKIP_SIMPLE_GIT_HOOKS is set to 1, skipping hook." + exit 0 +fi + +if [ -f "$SIMPLE_GIT_HOOKS_RC" ]; then + . "$SIMPLE_GIT_HOOKS_RC" +fi + +deno task lint:fix && deno task format \ No newline at end of file diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx index d33bc474..64da5a05 100644 --- a/src/components/LanguageSwitcher.tsx +++ b/src/components/LanguageSwitcher.tsx @@ -16,15 +16,11 @@ interface LanguageSwitcherProps { disableHover?: boolean; } -export default function LanguageSwitcher( - { disableHover = false }: LanguageSwitcherProps, -) { +export default function LanguageSwitcher({ + disableHover = false, +}: LanguageSwitcherProps) { const { i18n } = useTranslation("ui"); - const { set: setLanguage } = useLang(); - - const currentLanguage = - supportedLanguages.find((lang) => lang.code === i18n.language) || - supportedLanguages[0]; + const { set: setLanguage, currentLanguage } = useLang(); const handleLanguageChange = async (languageCode: LangCode) => { await setLanguage(languageCode, true); @@ -65,7 +61,7 @@ export default function LanguageSwitcher( "group-hover:text-gray-900 dark:group-hover:text-white", )} > - {currentLanguage.name} + {currentLanguage?.name} diff --git a/src/components/UI/ErrorPage.tsx b/src/components/UI/ErrorPage.tsx index 0f7037db..40da4ad1 100644 --- a/src/components/UI/ErrorPage.tsx +++ b/src/components/UI/ErrorPage.tsx @@ -60,7 +60,7 @@ export function ErrorPage({ error }: { error: Error }) {
Chirpy the Meshtastic error diff --git a/src/core/hooks/useLang.ts b/src/core/hooks/useLang.ts index 156e697c..0b408d7b 100644 --- a/src/core/hooks/useLang.ts +++ b/src/core/hooks/useLang.ts @@ -1,86 +1,55 @@ import { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { LangCode } from "@app/i18n/config.ts"; +import { + FALLBACK_LANGUAGE_CODE, + Lang, + LangCode, + supportedLanguages, +} from "../../i18n/config.ts"; import useLocalStorage from "./useLocalStorage.ts"; -/** - * Hook to set the i18n language - * - * @returns The `set` function - */ const STORAGE_KEY = "language"; type LanguageState = { - language: string; + language: LangCode; }; + function useLang() { const { i18n } = useTranslation(); - const [_, setLanguage] = useLocalStorage( + const [_, setLanguageInStorage] = useLocalStorage( STORAGE_KEY, null, ); - const regionNames = useMemo(() => { - return new Intl.DisplayNames(i18n.language, { - type: "region", - fallback: "none", - style: "long", - }); + const currentLanguage = useMemo((): Lang | undefined => { + const lang = supportedLanguages.find((l) => l.code === i18n.language); + if (lang) { + return lang; + } + return supportedLanguages.find((l) => l.code === FALLBACK_LANGUAGE_CODE); }, [i18n.language]); const collator = useMemo(() => { - return new Intl.Collator(i18n.language, {}); + return new Intl.Collator(i18n.language, { sensitivity: "base" }); }, [i18n.language]); - /** - * Sets the i18n language. - * - * @param lng - The language tag to set - * @param persist - Whether to persist the language setting in local storage - */ const set = useCallback( async (lng: LangCode, persist = true) => { if (i18n.language === lng) { return; } - try { - console.info("setting language:", lng); if (persist) { - setLanguage({ language: lng }); + setLanguageInStorage({ language: lng }); } await i18n.changeLanguage(lng); } catch (e) { - console.warn(e); - } - }, - [i18n], - ); - - /** - * Get the localized country name - * - * @param code - Two-letter country code - */ - const getCountryName = useCallback( - (code: LangCode) => { - let name = null; - try { - name = regionNames.of(code); - } catch (e) { - console.warn(e); + console.warn("Failed to change language:", e); } - return name; }, - [regionNames], + [i18n, setLanguageInStorage], ); - /** - * Compare two strings according to the sort order of the current language - * - * @param a - The first string to compare - * @param b - The second string to compare - */ const compare = useCallback( (a: string, b: string) => { return collator.compare(a, b); @@ -88,7 +57,7 @@ function useLang() { [collator], ); - return { compare, set, getCountryName }; + return { compare, set, currentLanguage }; } export default useLang; diff --git a/src/i18n/config.ts b/src/i18n/config.ts index a028a25c..a19092d8 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -7,6 +7,7 @@ export type Lang = { code: Intl.Locale["language"]; name: string; flag: string; + region: string; }; export type LangCode = Lang["code"]; @@ -21,13 +22,27 @@ function getFlagEmoji(regionCode: string): string { return String.fromCodePoint(...codePoints); } -export const supportedLanguages: Lang[] = [ - { code: "de", name: "German", flag: getFlagEmoji("de") }, - { code: "en", name: "English", flag: getFlagEmoji("us") }, - { code: "fi", name: "Finnish", flag: getFlagEmoji("fi") }, - { code: "sv", name: "Swedish", flag: getFlagEmoji("se") }, +const languageDefinitions = [ + { code: "de", name: "Deutschland" }, + { code: "en", name: "English", region: "us" }, + { code: "fi", name: "Suomi" }, + { code: "sv", name: "Svenska" }, ]; +export const supportedLanguages: Lang[] = languageDefinitions.map( + ({ code, name, region }) => { + const regionCode = region ?? code; + return { + code, + name, + region: regionCode.toUpperCase(), + flag: getFlagEmoji(regionCode), + }; + }, +); + +export const FALLBACK_LANGUAGE_CODE: LangCode = "en"; + i18next .use(Backend) .use(initReactI18next) @@ -45,7 +60,11 @@ i18next caches: ["localStorage"], }, fallbackLng: { - "default": ["en"], + default: [FALLBACK_LANGUAGE_CODE], + "en-GB": [FALLBACK_LANGUAGE_CODE], + "fi": ["fi-FI"], + "sv": ["sv-SE"], + "de": ["de-DE"], }, fallbackNS: ["common", "ui", "dialog"], debug: import.meta.env.MODE === "development",