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 }) {

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",