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); + console.warn("Failed to change language:", e); } }, - [i18n], + [i18n, setLanguageInStorage], ); - /** - * 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); - } - return name; - }, - [regionNames], - ); - - /** - * 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 fddfde8f..097e3791 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -7,32 +7,20 @@ export type Lang = { code: Intl.Locale["language"]; name: string; flag: string; + region?: Intl.Locale["region"]; }; export type LangCode = Lang["code"]; -/** - * Generates a flag emoji from a two-letter country code. - * @param regionCode - The two-letter, uppercase country code (e.g., "US", "FI"). - * @returns A string containing the flag emoji. - */ -function getFlagEmoji(regionCode: string): string { - const A_LETTER_CODE = 0x1F1E6; - const a_char_code = "A".charCodeAt(0); - const codePoints = regionCode - .toUpperCase() - .split("") - .map((char) => A_LETTER_CODE + char.charCodeAt(0) - a_char_code); - return String.fromCodePoint(...codePoints); -} - export const supportedLanguages: Lang[] = [ - { code: "de-DE", name: "Deutschland", flag: getFlagEmoji("DE") }, - { code: "en-US", name: "English", flag: getFlagEmoji("US") }, - { code: "fi-FI", name: "Suomi", flag: getFlagEmoji("FI") }, - { code: "sv-SE", name: "Svenska", flag: getFlagEmoji("SE") }, + { code: "de", name: "Deutsch", flag: "🇩🇪" }, + { code: "en", name: "English", flag: "🇺🇸" }, + { code: "fi", name: "Suomi", flag: "🇫🇮" }, + { code: "sv", name: "Svenska", flag: "🇸🇪" }, ]; +export const FALLBACK_LANGUAGE_CODE: LangCode = "en"; + i18next .use(Backend) .use(initReactI18next) @@ -50,7 +38,13 @@ i18next order: ["localStorage", "navigator"], caches: ["localStorage"], }, - fallbackLng: "en-US", // Default to US English if detection fails + fallbackLng: { + 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", ns: [ diff --git a/vite.config.ts b/vite.config.ts index dcccf7a0..64f63903 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,5 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; -import { VitePWA } from "vite-plugin-pwa"; import { viteStaticCopy } from "vite-plugin-static-copy"; import { execSync } from "node:child_process"; import process from "node:process";