diff --git a/apps/website/index.html b/apps/website/index.html index 798ca14f9..55cb4fa6a 100644 --- a/apps/website/index.html +++ b/apps/website/index.html @@ -1,5 +1,5 @@ - + diff --git a/apps/website/src/i18n.spec.ts b/apps/website/src/i18n.spec.ts index eab06ba5a..fcc8d790a 100644 --- a/apps/website/src/i18n.spec.ts +++ b/apps/website/src/i18n.spec.ts @@ -19,6 +19,7 @@ describe("swapLocale", () => { expect(swapLocaleInUrl("/ro/get-started", "ro")).toStrictEqual("/ro/get-started"); expect(swapLocaleInUrl("/en/get-started", "ro")).toStrictEqual("/ro/get-started"); expect(swapLocaleInUrl("/ro/", "en")).toStrictEqual("/en/"); + expect(swapLocaleInUrl("/ro", "en")).toStrictEqual("/en"); }); }); diff --git a/apps/website/src/i18n.ts b/apps/website/src/i18n.ts index 435245217..93931d936 100644 --- a/apps/website/src/i18n.ts +++ b/apps/website/src/i18n.ts @@ -1,9 +1,36 @@ +import i18next from "i18next"; +import { initReactI18next } from "react-i18next"; + interface Locale { id: string; name: string; rtl?: boolean; } +i18next.use(initReactI18next); +const localeFiles = import.meta.glob("./translations/*/translation.json", { eager: true }); +const resources: Record>> = {}; +for (const [path, module] of Object.entries(localeFiles)) { + const id = path.split("/").at(-2); + if (!id) continue; + + const translations = (module as any).default ?? module; + resources[id] = { translation: translations }; +} + +export function initTranslations(lng: string) { + i18next.init({ + fallbackLng: "en", + lng, + returnEmptyString: false, + resources, + initAsync: false, + react: { + useSuspense: false + } + }); +} + export const LOCALES: Locale[] = [ { id: "en", name: "English" }, { id: "ro", name: "Română" }, @@ -35,7 +62,13 @@ export function mapLocale(locale: string) { export function swapLocaleInUrl(url: string, newLocale: string) { const components = url.split("/"); if (components.length === 2) { - return `/${newLocale}${url}`; + const potentialLocale = components[1]; + const correspondingLocale = LOCALES.find(l => l.id === potentialLocale); + if (correspondingLocale) { + return `/${newLocale}`; + } else { + return `/${newLocale}${url}`; + } } else { components[1] = newLocale; return components.join("/"); diff --git a/apps/website/src/index.tsx b/apps/website/src/index.tsx index 8cbea3d0e..5bef0fad2 100644 --- a/apps/website/src/index.tsx +++ b/apps/website/src/index.tsx @@ -8,11 +8,9 @@ import Footer from './components/Footer.js'; import GetStarted from './pages/GetStarted/get-started.js'; import SupportUs from './pages/SupportUs/SupportUs.js'; import { createContext } from 'preact'; -import { useLayoutEffect, useState } from 'preact/hooks'; -import { default as i18next, changeLanguage } from 'i18next'; -import { extractLocaleFromUrl, LOCALES, mapLocale } from './i18n'; -import HttpApi from 'i18next-http-backend'; -import { initReactI18next } from "react-i18next"; +import { useLayoutEffect, useRef } from 'preact/hooks'; +import { changeLanguage } from 'i18next'; +import { extractLocaleFromUrl, initTranslations, LOCALES, mapLocale } from './i18n'; export const LocaleContext = createContext('en'); @@ -42,34 +40,26 @@ export function App(props: {repoStargazersCount: number}) { export function LocaleProvider({ children }) { const { path } = useLocation(); - const localeId = mapLocale(extractLocaleFromUrl(path) || navigator.language); - const [ loaded, setLoaded ] = useState(false); + const localeId = getLocaleId(path); + const loadedRef = useRef(false); - useLayoutEffect(() => { - i18next - .use(HttpApi) - .use(initReactI18next); - i18next.init({ - lng: localeId, - fallbackLng: "en", - backend: { - loadPath: "/translations/{{lng}}/{{ns}}.json", - }, - returnEmptyString: false - }).then(() => setLoaded(true)) -}, []); - - useLayoutEffect(() => { - if (!loaded) return; + if (!loadedRef.current) { + initTranslations(localeId); + loadedRef.current = true; + } else { changeLanguage(localeId); + } + + // Update html lang and dir attributes + useLayoutEffect(() => { const correspondingLocale = LOCALES.find(l => l.id === localeId); document.documentElement.lang = localeId; document.documentElement.dir = correspondingLocale?.rtl ? "rtl" : "ltr"; - }, [ loaded, localeId ]); + }, [localeId]); return ( - {loaded && children} + {children} ); } @@ -78,12 +68,26 @@ if (typeof window !== 'undefined') { hydrate(, document.getElementById('app')!); } +function getLocaleId(path: string) { + const extractedLocale = extractLocaleFromUrl(path); + if (extractedLocale) return mapLocale(extractedLocale); + if (typeof window === "undefined") return 'en'; + return mapLocale(navigator.language); +} + export async function prerender(data) { // Fetch the stargazer count of the Trilium's GitHub repo on prerender to pass // it to the App component for SSR. // This ensures the GitHub API is not called on every page load in the client. const stargazersCount = await getRepoStargazersCount(); - return await ssr(); + const { html, links } = await ssr(); + return { + html, + links, + head: { + lang: extractLocaleFromUrl(data.url) ?? "en" + } + } } diff --git a/apps/website/public/translations/ar/translation.json b/apps/website/src/translations/ar/translation.json similarity index 100% rename from apps/website/public/translations/ar/translation.json rename to apps/website/src/translations/ar/translation.json diff --git a/apps/website/public/translations/ca/translation.json b/apps/website/src/translations/ca/translation.json similarity index 100% rename from apps/website/public/translations/ca/translation.json rename to apps/website/src/translations/ca/translation.json diff --git a/apps/website/public/translations/cs/translation.json b/apps/website/src/translations/cs/translation.json similarity index 100% rename from apps/website/public/translations/cs/translation.json rename to apps/website/src/translations/cs/translation.json diff --git a/apps/website/public/translations/de/translation.json b/apps/website/src/translations/de/translation.json similarity index 100% rename from apps/website/public/translations/de/translation.json rename to apps/website/src/translations/de/translation.json diff --git a/apps/website/public/translations/el/translation.json b/apps/website/src/translations/el/translation.json similarity index 100% rename from apps/website/public/translations/el/translation.json rename to apps/website/src/translations/el/translation.json diff --git a/apps/website/public/translations/en/translation.json b/apps/website/src/translations/en/translation.json similarity index 100% rename from apps/website/public/translations/en/translation.json rename to apps/website/src/translations/en/translation.json diff --git a/apps/website/public/translations/es/translation.json b/apps/website/src/translations/es/translation.json similarity index 100% rename from apps/website/public/translations/es/translation.json rename to apps/website/src/translations/es/translation.json diff --git a/apps/website/public/translations/fa/translation.json b/apps/website/src/translations/fa/translation.json similarity index 100% rename from apps/website/public/translations/fa/translation.json rename to apps/website/src/translations/fa/translation.json diff --git a/apps/website/public/translations/fi/translation.json b/apps/website/src/translations/fi/translation.json similarity index 100% rename from apps/website/public/translations/fi/translation.json rename to apps/website/src/translations/fi/translation.json diff --git a/apps/website/public/translations/fr/translation.json b/apps/website/src/translations/fr/translation.json similarity index 100% rename from apps/website/public/translations/fr/translation.json rename to apps/website/src/translations/fr/translation.json diff --git a/apps/website/public/translations/hr/translation.json b/apps/website/src/translations/hr/translation.json similarity index 100% rename from apps/website/public/translations/hr/translation.json rename to apps/website/src/translations/hr/translation.json diff --git a/apps/website/public/translations/hu/translation.json b/apps/website/src/translations/hu/translation.json similarity index 100% rename from apps/website/public/translations/hu/translation.json rename to apps/website/src/translations/hu/translation.json diff --git a/apps/website/public/translations/id/translation.json b/apps/website/src/translations/id/translation.json similarity index 100% rename from apps/website/public/translations/id/translation.json rename to apps/website/src/translations/id/translation.json diff --git a/apps/website/public/translations/it/translation.json b/apps/website/src/translations/it/translation.json similarity index 100% rename from apps/website/public/translations/it/translation.json rename to apps/website/src/translations/it/translation.json diff --git a/apps/website/public/translations/ja/translation.json b/apps/website/src/translations/ja/translation.json similarity index 100% rename from apps/website/public/translations/ja/translation.json rename to apps/website/src/translations/ja/translation.json diff --git a/apps/website/public/translations/ko/translation.json b/apps/website/src/translations/ko/translation.json similarity index 100% rename from apps/website/public/translations/ko/translation.json rename to apps/website/src/translations/ko/translation.json diff --git a/apps/website/public/translations/md/translation.json b/apps/website/src/translations/md/translation.json similarity index 100% rename from apps/website/public/translations/md/translation.json rename to apps/website/src/translations/md/translation.json diff --git a/apps/website/public/translations/nb-NO/translation.json b/apps/website/src/translations/nb-NO/translation.json similarity index 100% rename from apps/website/public/translations/nb-NO/translation.json rename to apps/website/src/translations/nb-NO/translation.json diff --git a/apps/website/public/translations/nl/translation.json b/apps/website/src/translations/nl/translation.json similarity index 100% rename from apps/website/public/translations/nl/translation.json rename to apps/website/src/translations/nl/translation.json diff --git a/apps/website/public/translations/pl/translation.json b/apps/website/src/translations/pl/translation.json similarity index 100% rename from apps/website/public/translations/pl/translation.json rename to apps/website/src/translations/pl/translation.json diff --git a/apps/website/public/translations/pt-BR/translation.json b/apps/website/src/translations/pt-BR/translation.json similarity index 100% rename from apps/website/public/translations/pt-BR/translation.json rename to apps/website/src/translations/pt-BR/translation.json diff --git a/apps/website/public/translations/pt/translation.json b/apps/website/src/translations/pt/translation.json similarity index 100% rename from apps/website/public/translations/pt/translation.json rename to apps/website/src/translations/pt/translation.json diff --git a/apps/website/public/translations/ro/translation.json b/apps/website/src/translations/ro/translation.json similarity index 100% rename from apps/website/public/translations/ro/translation.json rename to apps/website/src/translations/ro/translation.json diff --git a/apps/website/public/translations/ru/translation.json b/apps/website/src/translations/ru/translation.json similarity index 100% rename from apps/website/public/translations/ru/translation.json rename to apps/website/src/translations/ru/translation.json diff --git a/apps/website/public/translations/sl/translation.json b/apps/website/src/translations/sl/translation.json similarity index 100% rename from apps/website/public/translations/sl/translation.json rename to apps/website/src/translations/sl/translation.json diff --git a/apps/website/public/translations/sr/translation.json b/apps/website/src/translations/sr/translation.json similarity index 100% rename from apps/website/public/translations/sr/translation.json rename to apps/website/src/translations/sr/translation.json diff --git a/apps/website/public/translations/sv/translation.json b/apps/website/src/translations/sv/translation.json similarity index 100% rename from apps/website/public/translations/sv/translation.json rename to apps/website/src/translations/sv/translation.json diff --git a/apps/website/public/translations/tr/translation.json b/apps/website/src/translations/tr/translation.json similarity index 100% rename from apps/website/public/translations/tr/translation.json rename to apps/website/src/translations/tr/translation.json diff --git a/apps/website/public/translations/uk/translation.json b/apps/website/src/translations/uk/translation.json similarity index 100% rename from apps/website/public/translations/uk/translation.json rename to apps/website/src/translations/uk/translation.json diff --git a/apps/website/public/translations/vi/translation.json b/apps/website/src/translations/vi/translation.json similarity index 100% rename from apps/website/public/translations/vi/translation.json rename to apps/website/src/translations/vi/translation.json diff --git a/apps/website/public/translations/zh-Hans/translation.json b/apps/website/src/translations/zh-Hans/translation.json similarity index 100% rename from apps/website/public/translations/zh-Hans/translation.json rename to apps/website/src/translations/zh-Hans/translation.json diff --git a/apps/website/public/translations/zh-Hant/translation.json b/apps/website/src/translations/zh-Hant/translation.json similarity index 100% rename from apps/website/public/translations/zh-Hant/translation.json rename to apps/website/src/translations/zh-Hant/translation.json diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json index 48581e18c..4d67635d7 100644 --- a/apps/website/tsconfig.json +++ b/apps/website/tsconfig.json @@ -9,6 +9,9 @@ "jsx": "react-jsx", "jsxImportSource": "preact", "skipLibCheck": true, + "types": [ + "vite/client" + ], "paths": { "react": ["../../node_modules/preact/compat/"], "react-dom": ["../../node_modules/preact/compat/"]