mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Translation overrides
This commit is contained in:
parent
11bec361f0
commit
0a3a962780
@ -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>
|
||||
);
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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()} />
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user