feat(web): i18n with react-i18next (sv/en)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"app": { "name": "Collection" },
|
||||
"nav": { "objects": "Objects", "vocabularies": "Vocabularies", "authorities": "Authorities", "fields": "Fields", "search": "Search", "soon": "Coming soon" },
|
||||
"auth": { "email": "Email", "password": "Password", "signIn": "Sign in", "signOut": "Sign out", "invalid": "Invalid email or password", "networkError": "Could not reach the server" },
|
||||
"objects": { "title": "Objects", "empty": "No objects yet", "loadError": "Could not load objects", "selectPrompt": "Select an object to view its details", "notFound": "Object not found", "prev": "Previous", "next": "Next", "of": "of" },
|
||||
"fieldsLabels": { "objectNumber": "Object number", "objectName": "Name", "count": "Number of objects", "briefDescription": "Brief description", "currentLocation": "Current location", "currentOwner": "Current owner", "recorder": "Recorder", "recordingDate": "Recording date", "visibility": "Visibility", "flexible": "Catalogue fields" },
|
||||
"visibility": { "draft": "Draft", "internal": "Internal", "public": "Public" }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { expect, test, beforeEach } from "vitest";
|
||||
import { render, screen, act } from "@testing-library/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "./index";
|
||||
import { useLocale } from "./use-locale";
|
||||
|
||||
beforeEach(async () => {
|
||||
await i18n.changeLanguage("en");
|
||||
});
|
||||
|
||||
function Probe() {
|
||||
const { t } = useTranslation();
|
||||
const { setLocale } = useLocale();
|
||||
return (
|
||||
<div>
|
||||
<span data-testid="title">{t("objects.title")}</span>
|
||||
<button onClick={() => setLocale("sv")}>sv</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
test("switches language at runtime", async () => {
|
||||
render(<Probe />);
|
||||
expect(screen.getByTestId("title")).toHaveTextContent("Objects");
|
||||
await act(async () => {
|
||||
screen.getByRole("button", { name: "sv" }).click();
|
||||
});
|
||||
expect(screen.getByTestId("title")).toHaveTextContent("Föremål");
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import en from "./en.json";
|
||||
import sv from "./sv.json";
|
||||
|
||||
export const LOCALE_KEY = "locale";
|
||||
const stored =
|
||||
typeof localStorage !== "undefined" ? localStorage.getItem(LOCALE_KEY) : null;
|
||||
const fallback = "en";
|
||||
|
||||
void i18n.use(initReactI18next).init({
|
||||
resources: { en: { translation: en }, sv: { translation: sv } },
|
||||
lng: stored ?? fallback,
|
||||
fallbackLng: fallback,
|
||||
interpolation: { escapeValue: false },
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"app": { "name": "Samling" },
|
||||
"nav": { "objects": "Föremål", "vocabularies": "Vokabulär", "authorities": "Auktoriteter", "fields": "Fält", "search": "Sök", "soon": "Kommer snart" },
|
||||
"auth": { "email": "E-post", "password": "Lösenord", "signIn": "Logga in", "signOut": "Logga ut", "invalid": "Fel e-post eller lösenord", "networkError": "Kunde inte nå servern" },
|
||||
"objects": { "title": "Föremål", "empty": "Inga föremål ännu", "loadError": "Kunde inte ladda föremål", "selectPrompt": "Välj ett föremål för att se detaljer", "notFound": "Föremålet hittades inte", "prev": "Föregående", "next": "Nästa", "of": "av" },
|
||||
"fieldsLabels": { "objectNumber": "Föremålsnummer", "objectName": "Namn", "count": "Antal föremål", "briefDescription": "Kort beskrivning", "currentLocation": "Nuvarande plats", "currentOwner": "Nuvarande ägare", "recorder": "Registrerad av", "recordingDate": "Registreringsdatum", "visibility": "Synlighet", "flexible": "Katalogfält" },
|
||||
"visibility": { "draft": "Utkast", "internal": "Intern", "public": "Publik" }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n, { LOCALE_KEY } from "./index";
|
||||
|
||||
export function useLocale() {
|
||||
const { i18n: instance } = useTranslation();
|
||||
|
||||
const setLocale = (lng: "en" | "sv") => {
|
||||
localStorage.setItem(LOCALE_KEY, lng);
|
||||
void i18n.changeLanguage(lng);
|
||||
};
|
||||
|
||||
return { locale: instance.language, setLocale };
|
||||
}
|
||||
Reference in New Issue
Block a user