Website static fixes (#7525)

This commit is contained in:
Elian Doran 2025-10-27 18:39:20 +02:00 committed by GitHub
commit 0fa52907b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 69 additions and 28 deletions

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" /> <link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />

View File

@ -19,6 +19,7 @@ describe("swapLocale", () => {
expect(swapLocaleInUrl("/ro/get-started", "ro")).toStrictEqual("/ro/get-started"); expect(swapLocaleInUrl("/ro/get-started", "ro")).toStrictEqual("/ro/get-started");
expect(swapLocaleInUrl("/en/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/");
expect(swapLocaleInUrl("/ro", "en")).toStrictEqual("/en");
}); });
}); });

View File

@ -1,9 +1,36 @@
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
interface Locale { interface Locale {
id: string; id: string;
name: string; name: string;
rtl?: boolean; rtl?: boolean;
} }
i18next.use(initReactI18next);
const localeFiles = import.meta.glob("./translations/*/translation.json", { eager: true });
const resources: Record<string, Record<string, Record<string, string>>> = {};
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[] = [ export const LOCALES: Locale[] = [
{ id: "en", name: "English" }, { id: "en", name: "English" },
{ id: "ro", name: "Română" }, { id: "ro", name: "Română" },
@ -35,7 +62,13 @@ export function mapLocale(locale: string) {
export function swapLocaleInUrl(url: string, newLocale: string) { export function swapLocaleInUrl(url: string, newLocale: string) {
const components = url.split("/"); const components = url.split("/");
if (components.length === 2) { 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 { } else {
components[1] = newLocale; components[1] = newLocale;
return components.join("/"); return components.join("/");

View File

@ -8,11 +8,9 @@ import Footer from './components/Footer.js';
import GetStarted from './pages/GetStarted/get-started.js'; import GetStarted from './pages/GetStarted/get-started.js';
import SupportUs from './pages/SupportUs/SupportUs.js'; import SupportUs from './pages/SupportUs/SupportUs.js';
import { createContext } from 'preact'; import { createContext } from 'preact';
import { useLayoutEffect, useState } from 'preact/hooks'; import { useLayoutEffect, useRef } from 'preact/hooks';
import { default as i18next, changeLanguage } from 'i18next'; import { changeLanguage } from 'i18next';
import { extractLocaleFromUrl, LOCALES, mapLocale } from './i18n'; import { extractLocaleFromUrl, initTranslations, LOCALES, mapLocale } from './i18n';
import HttpApi from 'i18next-http-backend';
import { initReactI18next } from "react-i18next";
export const LocaleContext = createContext('en'); export const LocaleContext = createContext('en');
@ -42,34 +40,26 @@ export function App(props: {repoStargazersCount: number}) {
export function LocaleProvider({ children }) { export function LocaleProvider({ children }) {
const { path } = useLocation(); const { path } = useLocation();
const localeId = mapLocale(extractLocaleFromUrl(path) || navigator.language); const localeId = getLocaleId(path);
const [ loaded, setLoaded ] = useState(false); const loadedRef = useRef(false);
useLayoutEffect(() => { if (!loadedRef.current) {
i18next initTranslations(localeId);
.use(HttpApi) loadedRef.current = true;
.use(initReactI18next); } else {
i18next.init({
lng: localeId,
fallbackLng: "en",
backend: {
loadPath: "/translations/{{lng}}/{{ns}}.json",
},
returnEmptyString: false
}).then(() => setLoaded(true))
}, []);
useLayoutEffect(() => {
if (!loaded) return;
changeLanguage(localeId); changeLanguage(localeId);
}
// Update html lang and dir attributes
useLayoutEffect(() => {
const correspondingLocale = LOCALES.find(l => l.id === localeId); const correspondingLocale = LOCALES.find(l => l.id === localeId);
document.documentElement.lang = localeId; document.documentElement.lang = localeId;
document.documentElement.dir = correspondingLocale?.rtl ? "rtl" : "ltr"; document.documentElement.dir = correspondingLocale?.rtl ? "rtl" : "ltr";
}, [ loaded, localeId ]); }, [localeId]);
return ( return (
<LocaleContext.Provider value={localeId}> <LocaleContext.Provider value={localeId}>
{loaded && children} {children}
</LocaleContext.Provider> </LocaleContext.Provider>
); );
} }
@ -78,12 +68,26 @@ if (typeof window !== 'undefined') {
hydrate(<App repoStargazersCount={FALLBACK_STARGAZERS_COUNT} />, document.getElementById('app')!); hydrate(<App repoStargazersCount={FALLBACK_STARGAZERS_COUNT} />, 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) { export async function prerender(data) {
// Fetch the stargazer count of the Trilium's GitHub repo on prerender to pass // Fetch the stargazer count of the Trilium's GitHub repo on prerender to pass
// it to the App component for SSR. // it to the App component for SSR.
// This ensures the GitHub API is not called on every page load in the client. // This ensures the GitHub API is not called on every page load in the client.
const stargazersCount = await getRepoStargazersCount(); const stargazersCount = await getRepoStargazersCount();
return await ssr(<App repoStargazersCount={stargazersCount} {...data} />); const { html, links } = await ssr(<App repoStargazersCount={stargazersCount} {...data} />);
return {
html,
links,
head: {
lang: extractLocaleFromUrl(data.url) ?? "en"
}
}
} }

View File

@ -9,6 +9,9 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact", "jsxImportSource": "preact",
"skipLibCheck": true, "skipLibCheck": true,
"types": [
"vite/client"
],
"paths": { "paths": {
"react": ["../../node_modules/preact/compat/"], "react": ["../../node_modules/preact/compat/"],
"react-dom": ["../../node_modules/preact/compat/"] "react-dom": ["../../node_modules/preact/compat/"]