From a8903e72790e6b4686e8b42dc8316d7c4290a9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20N=C3=BDvlt?= Date: Tue, 6 Feb 2024 23:03:26 +0100 Subject: [PATCH] Add translations --- src/i18n.ts | 66 ++++++++++++++++++- src/lang.ts | 4 +- src/layouts/Layout.astro | 11 +++- src/letter/LetterBodyForm.astro | 3 +- src/letter/LetterOptionsForm.astro | 3 +- src/lib/postal-client.ts | 2 +- src/mails/confirm-email.ts | 6 +- src/middleware.ts | 14 ++++ src/pages/[lang]/index.astro | 3 +- src/pages/[lang]/preview/confirm-email.txt.ts | 2 +- 10 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 src/middleware.ts diff --git a/src/i18n.ts b/src/i18n.ts index e1eabc2..9f57b33 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1,3 +1,65 @@ -export function t(key: string) { - return key; +import { DEFAULT_LANG } from "./config"; +import type { Lang } from "./lang"; + +type AnyString = string & {}; + +// Reference +const en = { + website_name: "Let's stop dirty money", + website_description: undefined, + switch_country: "Switch country", + "form.first_name": "First name", + "form.last_name": "Last name", + "form.variant": "I'm most concerned that", + "form.variant.1": "EPH destroys global climate", + "form.variant.2": "EPH fuels energy crisis", + "form.variant.3": "EPH undermines democracy", + "form.gender": undefined, + "form.gender.f": undefined, + "form.gender.m": undefined, + "form.branch": "I'm client of", + "form.letter": "Letter body", + "form.email": "E-mail", + "form.phone": "Phone number", + "form.send": "Send", + "confirm_email.title": "Confirm your e-mail", + "confirm_email.body": "Click the link below to verify your e-mail address.", + "confirm_email.link": "Confirm", +} as const; + +type TranslationKey = keyof typeof en; +type Translation = Partial>; + +const cs: Translation = { + website_name: "Zastavme špinavé prachy", + switch_country: "Změnit zemi", + "form.first_name": "Jméno", + "form.last_name": "Příjmení", + "form.variant": "Nejvíc mi vadí, že", + "form.variant.1": "EPH ničí klima", + "form.variant.2": "EPH způsobuje energetickou krizi", + "form.variant.3": "EPH ohrožuje demokracii", + "form.gender": "Preferuju být oslován/a v", + "form.gender.f": "ženském rodě", + "form.gender.m": "mužském rodě", + "form.branch": "Jsem klient/ka pobočky", + "form.letter": "Text dopisu", + "form.email": "E-mail", + "form.phone": "Telefonní číslo", + "form.send": "Odeslat", + "confirm_email.title": "Potvrď svůj e-mail", + "confirm_email.body": "Kliknutím na následující odkaz potvrdíš svou e-mailovou adresu.", + "confirm_email.link": "Potvrdit e-mail", +}; + +const de: Translation = {}; + +const translations: Record = { en, cs, de }; + +export type TranslateFn = (key: TranslationKey | AnyString) => string; + +export function makeT(lang: Lang = DEFAULT_LANG): TranslateFn { + return function t(key) { + return translations[lang]?.[key] ?? key; + }; } diff --git a/src/lang.ts b/src/lang.ts index fdd3313..349d85a 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -2,6 +2,6 @@ import { LANGUAGES } from "./config.ts"; export type Lang = (typeof LANGUAGES)[number]; -export function isLang(str: string): str is Lang { - return (LANGUAGES as ReadonlyArray).includes(str); +export function isLang(str: string | undefined): str is Lang { + return !!str && (LANGUAGES as ReadonlyArray).includes(str); } diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index db01f7d..9959e35 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,5 +1,14 @@ --- -import { t } from "../i18n"; +import { DEFAULT_LANG } from "../config"; +import { makeT } from "../i18n"; +import type { Lang } from "../lang"; + +interface Props { + lang?: Lang; +} + +const { lang = DEFAULT_LANG } = Astro.props; +const t = makeT(lang); --- diff --git a/src/letter/LetterBodyForm.astro b/src/letter/LetterBodyForm.astro index d53f00c..23850e1 100644 --- a/src/letter/LetterBodyForm.astro +++ b/src/letter/LetterBodyForm.astro @@ -1,5 +1,5 @@ --- -import { t } from "../i18n"; +import { makeT } from "../i18n"; import Button from "../ui/Button.astro"; import Dialog from "../ui/Dialog.astro"; import Checkbox from "../ui/Checkbox.astro"; @@ -14,6 +14,7 @@ interface Props { } const { lang, options = {} } = Astro.props; +const t = makeT(lang); const result = letterOptionsSchema.safeParse(options); --- diff --git a/src/letter/LetterOptionsForm.astro b/src/letter/LetterOptionsForm.astro index f417453..5afde03 100644 --- a/src/letter/LetterOptionsForm.astro +++ b/src/letter/LetterOptionsForm.astro @@ -2,7 +2,7 @@ import Field from "../ui/Field.astro"; import Radio from "../ui/Radio.astro"; import RadioGroup from "../ui/RadioGroup.astro"; -import { t } from "../i18n"; +import { makeT } from "../i18n"; import Label from "../ui/Label.astro"; import Select from "../ui/Select.astro"; import type { Lang } from "../lang"; @@ -12,6 +12,7 @@ interface Props { } const { lang } = Astro.props; +const t = makeT(lang); interface Branch { id: number; diff --git a/src/lib/postal-client.ts b/src/lib/postal-client.ts index e98f3e6..c972e45 100644 --- a/src/lib/postal-client.ts +++ b/src/lib/postal-client.ts @@ -27,7 +27,7 @@ const responseSchema = z.object({ data: z.record(z.any()), }); -function convertObjectKeys( +function convertObjectKeys( obj: Record, fn: (key: string) => string ): Record { diff --git a/src/mails/confirm-email.ts b/src/mails/confirm-email.ts index 291fe97..dd63e00 100644 --- a/src/mails/confirm-email.ts +++ b/src/mails/confirm-email.ts @@ -1,12 +1,14 @@ import html from "html-template-tag"; -import { t } from "../i18n"; +import { makeT } from "../i18n"; +import type { Lang } from "../lang"; interface Params { - lang: string; + lang: Lang; href: string; } export default function renderMail({ lang, href }: Params) { + const t = makeT(lang); const htmlBody = html` diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..fa33ad7 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,14 @@ +import type { MiddlewareHandler } from "astro"; +import { isLang } from "./lang"; + +export const onRequest: MiddlewareHandler = ({ params }, next) => { + // Return 404 on unknown languages + // On statically generated routes, this behavior is automatically handled by getStaticPaths. + if (params.lang && !isLang(params.lang)) { + return new Response(null, { + status: 404, + statusText: "Not found", + }); + } + return next(); +}; diff --git a/src/pages/[lang]/index.astro b/src/pages/[lang]/index.astro index ce839e9..ca53ea7 100644 --- a/src/pages/[lang]/index.astro +++ b/src/pages/[lang]/index.astro @@ -1,5 +1,5 @@ --- -import { t } from "../../i18n"; +import { makeT } from "../../i18n"; import Layout from "../../layouts/Layout.astro"; import LetterBodyForm from "../../letter/LetterBodyForm.astro"; import LetterOptionsForm from "../../letter/LetterOptionsForm.astro"; @@ -7,6 +7,7 @@ import LetterOptionsForm from "../../letter/LetterOptionsForm.astro"; export { getStaticPaths } from "../../static-paths"; const { lang } = Astro.params; +const t = makeT(lang); --- diff --git a/src/pages/[lang]/preview/confirm-email.txt.ts b/src/pages/[lang]/preview/confirm-email.txt.ts index 9aa2ef3..963201f 100644 --- a/src/pages/[lang]/preview/confirm-email.txt.ts +++ b/src/pages/[lang]/preview/confirm-email.txt.ts @@ -12,7 +12,7 @@ export async function GET({ params: { lang } }: APIContext) { const { plainBody } = renderMail({ lang, href: "" }); return new Response(plainBody, { headers: { - "Content-Type": "text/plain", + "Content-Type": "text/plain;charset=utf-8", }, }); }