Translation overrides

This commit is contained in:
Konstantin Wohlwend 2024-11-19 00:15:58 +01:00
parent 11bec361f0
commit 0a3a962780
8 changed files with 42 additions and 17 deletions

View File

@ -13,6 +13,7 @@ For detailed usage instructions, please refer to the manual section of the [setu
- `children`: `React.ReactNode` - The child components to be wrapped by the StackProvider.
- `app`: `StackClientApp | StackServerApp` - The Stack app instance to be used.
- `lang` (optional): `"en-US" | "de-DE" | "es-419" | "es-ES" | "fr-CA" | "fr-FR" | "it-IT" | "pt-BR" | "pt-PT"` - The language to be used for translations.
- `translationOverrides` (optional): `Record<string, string>` - A mapping of English translations to translated equivalents. These will take priority over the translations from the language specified in the `lang` property. Note that the keys are case-sensitive.
## Example
@ -22,9 +23,16 @@ import { stackServerApp } from '@/stack';
function App() {
return (
<StackProvider app={stackServerApp} lang="de-DE">
<StackProvider
app={stackServerApp}
lang="de-DE"
translationOverrides={{
"Sign in": "Einloggen",
"Sign In": "Einloggen",
}}
>
{/* Your app content */}
</StackProvider>
);
}
```
```

View File

@ -1,9 +1,9 @@
import React from "react";
import { Metadata } from "next";
import { StackProvider, StackTheme } from "@stackframe/stack";
import { stackServerApp } from "src/stack";
import Provider from "src/components/provider";
import { Metadata } from "next";
import React from "react";
import Header from "src/components/header";
import Provider from "src/components/provider";
import { stackServerApp } from "src/stack";
import './global.css';
export const metadata: Metadata = {

View File

@ -595,7 +595,7 @@ function usePasswordSection() {
<Input
id="old-password"
type="password"
autocomplete="current-password"
autoComplete="current-password"
{...register("oldPassword")}
/>
<FormWarningText text={errors.oldPassword?.message?.toString()} />
@ -605,7 +605,7 @@ function usePasswordSection() {
<Label htmlFor="new-password" className="mt-4 mb-1">{t("New password")}</Label>
<PasswordInput
id="new-password"
autocomplete="new-password"
autoComplete="new-password"
{...registerPassword}
onChange={(e) => {
clearErrors('newPassword');
@ -618,7 +618,7 @@ function usePasswordSection() {
<Label htmlFor="repeat-password" className="mt-4 mb-1">{t("Repeat new password")}</Label>
<PasswordInput
id="repeat-password"
autocomplete="new-password"
autoComplete="new-password"
{...registerPasswordRepeat}
onChange={(e) => {
clearErrors('newPassword');

View File

@ -93,7 +93,7 @@ export default function PasswordResetForm(props: {
<Label htmlFor="password" className="mb-1">{t("New Password")}</Label>
<PasswordInput
id="password"
autocomplete="new-password"
autoComplete="new-password"
{...register('password')}
onChange={() => {
clearErrors('password');
@ -105,7 +105,7 @@ export default function PasswordResetForm(props: {
<Label htmlFor="repeat-password" className="mt-4 mb-1">{t("Repeat New Password")}</Label>
<PasswordInput
id="repeat-password"
autocomplete="new-password"
autoComplete="new-password"
{...register('passwordRepeat')}
onChange={() => {
clearErrors('password');

View File

@ -59,7 +59,7 @@ export function CredentialSignIn() {
<Label htmlFor="password" className="mt-4 mb-1">{t('Password')}</Label>
<PasswordInput
id="password"
autocomplete="current-password"
autoComplete="current-password"
{...register('password')}
/>
<FormWarningText text={errors.password?.message?.toString()} />

View File

@ -68,7 +68,7 @@ export function CredentialSignUp(props: { noPasswordRepeat?: boolean }) {
<Label htmlFor="password" className="mt-4 mb-1">{t('Password')}</Label>
<PasswordInput
id="password"
autocomplete="new-password"
autoComplete="new-password"
{...registerPassword}
onChange={(e) => {
clearErrors('password');

View File

@ -8,8 +8,16 @@ export default function StackProvider({
children,
app,
lang,
translationOverrides,
}: {
lang?: React.ComponentProps<typeof TranslationProvider>['lang'],
/**
* A mapping of English translations to translated equivalents.
*
* These will take priority over the translations from the language specified in the `lang` property. Note that the
* keys are case-sensitive.
*/
translationOverrides?: Record<string, string>,
children: React.ReactNode,
// list all three types of apps even though server and admin are subclasses of client so it's clear that you can pass any
app: StackClientApp<true> | StackServerApp<true> | StackAdminApp<true>,
@ -19,7 +27,7 @@ export default function StackProvider({
<Suspense fallback={null}>
<UserFetcher app={app} />
</Suspense>
<TranslationProvider lang={lang}>
<TranslationProvider lang={lang} translationOverrides={translationOverrides}>
{children}
</TranslationProvider>
</StackProviderClient>

View File

@ -1,12 +1,21 @@
import { quetzalLocales, quetzalKeys } from "../generated/quetzal-translations";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { quetzalKeys, quetzalLocales } from "../generated/quetzal-translations";
import { TranslationProviderClient } from "./translation-provider-client";
export async function TranslationProvider({ lang, children }: {
export async function TranslationProvider({ lang, translationOverrides, children }: {
lang: Parameters<typeof quetzalLocales.get>[0] | undefined,
translationOverrides?: Record<string, string>,
children: React.ReactNode,
}) {
const locale = quetzalLocales.get(lang ?? (undefined as never));
return <TranslationProviderClient quetzalKeys={quetzalKeys} quetzalLocale={locale ?? new Map()}>
const localeWithOverrides = new Map<string, string>(locale);
for (const [orig, override] of Object.entries(translationOverrides ?? {})) {
const key = quetzalKeys.get(orig as never) ?? throwErr(new Error(`Invalid translation override: Original key ${JSON.stringify(orig)} not found. Make sure you are passing the correct values into the translationOverrides property of the component.`));
localeWithOverrides.set(key, override);
}
return <TranslationProviderClient quetzalKeys={quetzalKeys} quetzalLocale={localeWithOverrides}>
{children}
</TranslationProviderClient>;
}