fix(i18n): localize hardcoded frontend text in English and German (#6993)

This commit is contained in:
Ludy
2026-07-12 10:16:55 +01:00
committed by GitHub
parent c500c2fae7
commit 80febc9993
16 changed files with 245 additions and 55 deletions
@@ -356,6 +356,7 @@ text = "Diese Berechtigungen kontrollieren, was Benutzer mit der PDF machen kön
title = "Berechtigungen ändern"
[addStamp]
preview = "Stempelvorschau"
tags = "stamp,mark,seal,approved,rejected,confidential,stamp tool,rubber stamp,date stamp,approval stamp,received,void,copy,original"
[AddStampRequest]
@@ -1842,6 +1843,15 @@ export = "Export"
accessDenied = "Zugriff verweigert"
insufficientPermissions = "Sie haben keine Berechtigung, diese Aktion auszuführen."
[auth.callback]
completing = "Authentifizierung wird abgeschlossen"
invalidToken = "OAuth-Anmeldung fehlgeschlagen ungültiges Token."
missingToken = "OAuth-Anmeldung fehlgeschlagen kein Token empfangen."
oauthFailed = "OAuth-Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut."
pleaseWait = "Bitte warten Sie, während wir Ihre Anmeldung abschließen."
signedOut = "Sie wurden abgemeldet. Bitte melden Sie sich erneut an."
windowMayClose = "Sie können dieses Fenster schließen, sobald der Vorgang abgeschlossen ist."
[auth.displayName]
guest = "Gast"
user = "Benutzer"
@@ -2824,6 +2834,7 @@ next = "Weiter"
preview = "Vorschau"
previous = "Zurück"
refresh = "Aktualisieren"
remaining = "Verbleibend"
retry = "Wiederholen"
save = "Save"
@@ -4072,10 +4083,17 @@ discord = "Discord"
issues = "GitHub"
[formFill]
allSaved = "Alles gespeichert"
extractCsvError = "CSV konnte nicht extrahiert werden"
extractXlsxError = "XLSX konnte nicht extrahiert werden"
flattenAfterFilling = "Nach dem Ausfüllen ebnen"
requiredAbbreviation = "erf."
requiredFieldsError = "Bitte füllen Sie alle Pflichtfelder aus"
rescanFields = "Felder erneut scannen"
rescanFormFields = "Formularfelder erneut scannen"
save = "Speichern"
saveShortcut = "Strg+S zum Speichern"
unsavedChanges = "Nicht gespeicherte Änderungen"
[formFill.sidebar]
close = "Seitenleiste schließen"
@@ -4996,7 +5014,21 @@ titleWithOs = "Herunterladen für {{osLabel}}"
[onboarding.mfa]
authenticationCode = "Authentifizierungscode"
description = "Schützen Sie Ihr Konto, indem Sie eine Authenticator-App verknüpfen. Scannen Sie den QR-Code oder geben Sie den Einrichtungsschlüssel ein und bestätigen Sie anschließend den sechsstelligen Code."
enable = "MFA aktivieren"
enableError = "Die Multi-Faktor-Authentifizierung konnte nicht aktiviert werden. Prüfen Sie den Code und versuchen Sie es erneut."
enterCode = "Geben Sie den sechsstelligen Code aus Ihrer App ein."
enterCodeError = "Geben Sie den Authentifizierungscode ein, um fortzufahren."
logout = "Abmelden"
openAuthenticator = "Öffnen Sie Google Authenticator, Authy oder 1Password."
qrCodeLoading = "Ihr QR-Code wird erstellt…"
regenerateQrCode = "QR-Code neu erstellen"
scanQrCode = "Scannen Sie den QR-Code oder geben Sie den unten stehenden Einrichtungsschlüssel ein."
setupError = "Die Einrichtung der Zwei-Faktor-Authentifizierung konnte nicht gestartet werden. Bitte versuchen Sie es erneut."
setupKey = "Einrichtungsschlüssel (manuelle Eingabe)"
stepByStep = "Schritt für Schritt"
success = "MFA wurde aktiviert. Sie können jetzt fortfahren."
title = "Einrichtung der Multi-Faktor-Authentifizierung"
[onboarding.planOverview]
adminBodyLoginDisabled = "Sobald Sie den Login-Modus aktivieren, können Sie Nutzer verwalten, Einstellungen konfigurieren und die Servergesundheit überwachen. Die ersten <strong>{{freeTierLimit}}</strong> Personen auf Ihrem Server nutzen Stirling kostenlos."
@@ -9110,6 +9142,11 @@ security = "Sicherheit"
telegram = "Telegram"
title = "Sicherheit & Authentifizierung"
[settings.signOut]
confirm = "Möchten Sie sich wirklich abmelden?"
submit = "Abmelden"
title = "Abmelden"
[settings.team]
title = "Team"
@@ -9726,6 +9763,9 @@ uploadButton = "Auf Server hochladen"
title = "Stirling-PDF Survey"
[swagger]
clickHere = "hier klicken"
fallback = "Falls die Seite nicht automatisch geöffnet wurde,"
opening = "Swagger UI wird in einem neuen Tab geöffnet..."
tags = "API,Dokumentation,Swagger,Endpunkte,Entwicklung"
title = "API-Dokumentation"
@@ -10281,9 +10321,11 @@ title = "Attachments"
[viewer.bookmarks]
bookmarkTitle = "Lesezeichentitel"
bookmarkTitleRequired = "Ein Lesezeichentitel ist erforderlich"
closeSidebar = "Seitenleiste für Lesezeichen schließen"
collapseAll = "Alle Lesezeichen einklappen"
expandAll = "Alle Lesezeichen ausklappen"
searchPlaceholder = "Lesezeichen durchsuchen"
[viewer.comments]
addComment = "Kommentar hinzufügen"
@@ -356,6 +356,7 @@ text = "These permissions control what users can do with the PDF. Most effective
title = "Change Permissions"
[addStamp]
preview = "Preview Stamp"
tags = "stamp,mark,seal,approved,rejected,confidential,stamp tool,rubber stamp,date stamp,approval stamp,received,void,copy,original"
[AddStampRequest]
@@ -1842,6 +1843,15 @@ export = "Export"
accessDenied = "Access Denied"
insufficientPermissions = "You do not have permission to perform this action."
[auth.callback]
completing = "Completing authentication"
invalidToken = "OAuth login failed - invalid token."
missingToken = "OAuth login failed - no token received."
oauthFailed = "OAuth login failed. Please try again."
pleaseWait = "Please wait while we finish signing you in."
signedOut = "You have been signed out. Please sign in again."
windowMayClose = "You can close this window once it completes."
[auth.displayName]
guest = "Guest"
user = "User"
@@ -2824,6 +2834,7 @@ next = "Next"
preview = "Preview"
previous = "Previous"
refresh = "Refresh"
remaining = "Remaining"
retry = "Retry"
save = "Save"
@@ -4072,10 +4083,17 @@ discord = "Discord"
issues = "GitHub"
[formFill]
allSaved = "All saved"
extractCsvError = "Failed to extract CSV"
extractXlsxError = "Failed to extract XLSX"
flattenAfterFilling = "Flatten after filling"
requiredAbbreviation = "req"
requiredFieldsError = "Please fill in all required fields"
rescanFields = "Re-scan fields"
rescanFormFields = "Re-scan form fields"
save = "Save"
saveShortcut = "Ctrl+S to save"
unsavedChanges = "Unsaved changes"
[formFill.sidebar]
close = "Close sidebar"
@@ -5016,7 +5034,21 @@ titleWithOs = "Download for {{osLabel}}"
[onboarding.mfa]
authenticationCode = "Authentication code"
description = "Secure your account by linking an authenticator app. Scan the QR code or enter the setup key, then confirm the 6-digit code to finish."
enable = "Enable MFA"
enableError = "Unable to enable two-factor authentication. Check the code and try again."
enterCode = "Enter the 6-digit code from your app."
enterCodeError = "Enter the authentication code to continue."
logout = "Logout"
openAuthenticator = "Open Google Authenticator, Authy, or 1Password."
qrCodeLoading = "Generating your QR code…"
regenerateQrCode = "Regenerate QR code"
scanQrCode = "Scan the QR code or enter the setup key below."
setupError = "Unable to start two-factor setup. Please try again."
setupKey = "Setup key (manual entry)"
stepByStep = "Step-by-step"
success = "MFA has been enabled. You can now continue."
title = "Multi-Factor Authentication Setup"
[onboarding.planOverview]
adminBodyLoginDisabled = "Once you enable login mode, you can manage users, configure settings, and monitor server health. The first <strong>{{freeTierLimit}}</strong> people on your server get to use Stirling free of charge."
@@ -9178,6 +9210,11 @@ security = "Security"
telegram = "Telegram"
title = "Security & Authentication"
[settings.signOut]
confirm = "Are you sure you want to sign out?"
submit = "Sign out"
title = "Sign out"
[settings.team]
title = "Team"
@@ -9794,6 +9831,9 @@ uploadButton = "Upload to Server"
title = "Stirling-PDF Survey"
[swagger]
clickHere = "click here"
fallback = "If it didn't open automatically,"
opening = "Opening Swagger UI in a new tab..."
tags = "api,documentation,swagger,endpoints,development"
title = "API Documentation"
@@ -10362,9 +10402,11 @@ title = "Attachments"
[viewer.bookmarks]
bookmarkTitle = "Bookmark title"
bookmarkTitleRequired = "Bookmark title is required"
closeSidebar = "Close bookmarks sidebar"
collapseAll = "Collapse all bookmarks"
expandAll = "Expand all bookmarks"
searchPlaceholder = "Search bookmarks"
[viewer.comments]
addComment = "Add comment"
@@ -26,6 +26,7 @@ import LocalIcon from "@app/components/shared/LocalIcon";
import { BASE_PATH, withBasePath } from "@app/constants/app";
import styles from "@app/components/onboarding/InitialOnboardingModal/InitialOnboardingModal.module.css";
import { MfaSetupResponse } from "@app/responses/Mfa/MfaResponse";
import i18n from "@app/i18n";
interface MFASetupSlideProps {
onMfaSetupComplete?: () => void;
@@ -62,12 +63,15 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
const axiosError = err as { response?: { data?: { error?: string } } };
setMfaError(
axiosError.response?.data?.error ||
"Unable to start two-factor setup. Please try again.",
t(
"onboarding.mfa.setupError",
"Unable to start two-factor setup. Please try again.",
),
);
} finally {
setMfaLoading(false);
}
}, []);
}, [t]);
useEffect(() => {
setupCompleteRef.current = setupComplete;
@@ -96,7 +100,12 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
event.preventDefault();
if (!mfaSetupCode.trim()) {
setMfaError("Enter the authentication code to continue.");
setMfaError(
t(
"onboarding.mfa.enterCodeError",
"Enter the authentication code to continue.",
),
);
return;
}
@@ -110,13 +119,16 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
const axiosError = err as { response?: { data?: { error?: string } } };
setMfaError(
axiosError.response?.data?.error ||
"Unable to enable two-factor authentication. Check the code and try again.",
t(
"onboarding.mfa.enableError",
"Unable to enable two-factor authentication. Check the code and try again.",
),
);
} finally {
setSubmitting(false);
}
},
[mfaSetupCode, onMfaSetupComplete],
[mfaSetupCode, onMfaSetupComplete, t],
);
const isReady = Boolean(mfaSetupData);
@@ -138,15 +150,30 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
<Stack gap="xs">
<Text size="sm" fw={600}>
Step-by-step
{t("onboarding.mfa.stepByStep", "Step-by-step")}
</Text>
<ol className={styles.mfaSteps}>
<li>Open Google Authenticator, Authy, or 1Password.</li>
<li>Scan the QR code or enter the setup key below.</li>
<li>Enter the 6-digit code from your app.</li>
<li>
{t(
"onboarding.mfa.openAuthenticator",
"Open Google Authenticator, Authy, or 1Password.",
)}
</li>
<li>
{t(
"onboarding.mfa.scanQrCode",
"Scan the QR code or enter the setup key below.",
)}
</li>
<li>
{t(
"onboarding.mfa.enterCode",
"Enter the 6-digit code from your app.",
)}
</li>
</ol>
<Text size="xs" c="dimmed">
Setup key (manual entry)
{t("onboarding.mfa.setupKey", "Setup key (manual entry)")}
</Text>
<TextInput
value={mfaSetupData.secret ?? ""}
@@ -163,9 +190,10 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
<div className={styles.mfaCard}>
<Stack gap="md">
<Text size="sm" c="dimmed">
Secure your account by linking an authenticator app. Scan the QR
code or enter the setup key, then confirm the 6-digit code to
finish.
{t(
"onboarding.mfa.description",
"Secure your account by linking an authenticator app. Scan the QR code or enter the setup key, then confirm the 6-digit code to finish.",
)}
</Text>
{mfaError && (
@@ -215,7 +243,7 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
onClick={fetchMfaSetup}
disabled={mfaLoading || submitting || setupComplete}
>
Regenerate QR code
{t("onboarding.mfa.regenerateQrCode", "Regenerate QR code")}
</Button>
<Button
type="submit"
@@ -224,16 +252,19 @@ function MFASetupContent({ onMfaSetupComplete }: MFASetupSlideProps) {
!isReady || setupComplete || mfaSetupCode.length < 6
}
>
Enable MFA
{t("onboarding.mfa.enable", "Enable MFA")}
</Button>
<Button variant="secondary" type="button" onClick={onLogout}>
Logout
{t("onboarding.mfa.logout", "Logout")}
</Button>
</Group>
{setupComplete && (
<Alert color="green" variant="light">
MFA has been enabled. You can now continue.
{t(
"onboarding.mfa.success",
"MFA has been enabled. You can now continue.",
)}
</Alert>
)}
</Stack>
@@ -249,7 +280,7 @@ export default function MFASetupSlide({
}: MFASetupSlideProps = {}): SlideConfig {
return {
key: "mfa-setup-slide",
title: "Multi-Factor Authentication Setup",
title: i18n.t("onboarding.mfa.title", "Multi-Factor Authentication Setup"),
body: <MFASetupContent onMfaSetupComplete={onMfaSetupComplete} />,
background: {
gradientStops: ["#059669", "#0891B2"], // Green to teal - security/trust colors
@@ -12,6 +12,7 @@ import {
import styles from "@app/components/tools/addStamp/StampPreview.module.css";
import { PrivateContent } from "@app/components/shared/PrivateContent";
import { Button } from "@app/ui/Button";
import { useTranslation } from "react-i18next";
type Props = {
parameters: AddStampParameters;
@@ -29,6 +30,7 @@ export default function StampPreview({
file,
showQuickGrid,
}: Props) {
const { t } = useTranslation();
const containerRef = useRef<HTMLDivElement>(null);
const [containerSize, setContainerSize] = useState<{
width: number;
@@ -366,7 +368,9 @@ export default function StampPreview({
<div>
<div className={styles.previewHeader}>
<div className={styles.divider} />
<div className={styles.previewLabel}>Preview Stamp</div>
<div className={styles.previewLabel}>
{t("addStamp.preview", "Preview Stamp")}
</div>
</div>
<div
ref={containerRef}
@@ -328,7 +328,12 @@ export const BookmarkSidebar = ({
const handleSubmitAddBookmark = useCallback(async () => {
const title = newBookmarkTitle.trim();
if (!title) {
setAddBookmarkError("Bookmark title is required");
setAddBookmarkError(
t(
"viewer.bookmarks.bookmarkTitleRequired",
"Bookmark title is required",
),
);
return;
}
// Resolve the file the viewer is currently displaying. activeFileId
@@ -695,7 +700,10 @@ export const BookmarkSidebar = ({
"viewer.bookmarks.expandAll",
"Expand all bookmarks",
)}
title="Expand all"
title={t(
"viewer.bookmarks.expandAll",
"Expand all bookmarks",
)}
>
<LocalIcon
icon="unfold-more"
@@ -712,7 +720,10 @@ export const BookmarkSidebar = ({
"viewer.bookmarks.collapseAll",
"Collapse all bookmarks",
)}
title="Collapse all"
title={t(
"viewer.bookmarks.collapseAll",
"Collapse all bookmarks",
)}
>
<LocalIcon
icon="unfold-less"
@@ -732,7 +743,10 @@ export const BookmarkSidebar = ({
"viewer.bookmarks.closeSidebar",
"Close bookmarks sidebar",
)}
title="Close bookmarks"
title={t(
"viewer.bookmarks.closeSidebar",
"Close bookmarks sidebar",
)}
>
<LocalIcon icon="close-rounded" width="1.1rem" height="1.1rem" />
</ActionIcon>
@@ -746,7 +760,10 @@ export const BookmarkSidebar = ({
>
<TextInput
value={searchTerm}
placeholder="Search bookmarks"
placeholder={t(
"viewer.bookmarks.searchPlaceholder",
"Search bookmarks",
)}
onChange={(event) => setSearchTerm(event.currentTarget.value)}
leftSection={
<LocalIcon icon="search" width="1.1rem" height="1.1rem" />
@@ -839,7 +856,10 @@ export const BookmarkSidebar = ({
</Text>
<TextInput
size="xs"
placeholder="Bookmark title"
placeholder={t(
"viewer.bookmarks.bookmarkTitle",
"Bookmark title",
)}
aria-label={t(
"viewer.bookmarks.bookmarkTitle",
"Bookmark title",
@@ -350,7 +350,7 @@ export function LayerSidebar({
onClick={showAll}
disabled={allVisible || isApplying}
aria-label={t("viewer.layers.showAll", "Show all layers")}
title="Show all"
title={t("viewer.layers.showAll", "Show all layers")}
>
<VisibilityIcon sx={{ fontSize: "1rem" }} />
</ActionIcon>
@@ -360,7 +360,7 @@ export function LayerSidebar({
onClick={hideAll}
disabled={allHidden || isApplying}
aria-label={t("viewer.layers.hideAll", "Hide all layers")}
title="Hide all"
title={t("viewer.layers.hideAll", "Hide all layers")}
>
<VisibilityOffIcon sx={{ fontSize: "1rem" }} />
</ActionIcon>
@@ -372,7 +372,7 @@ export function LayerSidebar({
size="sm"
onClick={toggleLayerSidebar}
aria-label={t("viewer.layers.closeSidebar", "Close layers sidebar")}
title="Close layers"
title={t("viewer.layers.closeSidebar", "Close layers sidebar")}
>
<LocalIcon icon="close-rounded" width="1.1rem" height="1.1rem" />
</ActionIcon>
@@ -193,7 +193,10 @@ export function ThumbnailSidebar({
"viewer.thumbnails.closeSidebar",
"Close thumbnails sidebar",
)}
title="Close thumbnails"
title={t(
"viewer.thumbnails.closeSidebar",
"Close thumbnails sidebar",
)}
>
<LocalIcon icon="close-rounded" width="1.1rem" height="1.1rem" />
</ActionIcon>
+5 -3
View File
@@ -1,8 +1,10 @@
import React, { useEffect } from "react";
import { BaseToolProps } from "@app/types/tool";
import { withBasePath } from "@app/constants/app";
import { useTranslation } from "react-i18next";
const SwaggerUI: React.FC<BaseToolProps> = () => {
const { t } = useTranslation();
useEffect(() => {
// Redirect to Swagger UI
window.open(withBasePath("/swagger-ui/5.21.0/index.html"), "_blank");
@@ -10,15 +12,15 @@ const SwaggerUI: React.FC<BaseToolProps> = () => {
return (
<div style={{ textAlign: "center", padding: "2rem" }}>
<p>Opening Swagger UI in a new tab...</p>
<p>{t("swagger.opening", "Opening Swagger UI in a new tab...")}</p>
<p>
If it didn't open automatically,{" "}
{t("swagger.fallback", "If it didn't open automatically,")}{" "}
<a
href={withBasePath("/swagger-ui/5.21.0/index.html")}
target="_blank"
rel="noopener noreferrer"
>
click here
{t("swagger.clickHere", "click here")}
</a>
</p>
</div>
@@ -204,7 +204,7 @@ const FormFill = (_props: BaseToolProps) => {
setTimeout(() => URL.revokeObjectURL(url), 250);
} catch (err) {
console.error("[FormFill] CSV extraction failed:", err);
setSaveError("Failed to extract CSV");
setSaveError(t("formFill.extractCsvError", "Failed to extract CSV"));
} finally {
setExtracting(false);
}
@@ -223,7 +223,7 @@ const FormFill = (_props: BaseToolProps) => {
setTimeout(() => URL.revokeObjectURL(url), 250);
} catch (err) {
console.error("[FormFill] XLSX extraction failed:", err);
setSaveError("Failed to extract XLSX");
setSaveError(t("formFill.extractXlsxError", "Failed to extract XLSX"));
} finally {
setExtracting(false);
}
@@ -246,7 +246,9 @@ const FormFill = (_props: BaseToolProps) => {
if (!currentFile || !isStirlingFile(currentFile)) return;
if (!validateForm()) {
setSaveError("Please fill in all required fields");
setSaveError(
t("formFill.requiredFieldsError", "Please fill in all required fields"),
);
return;
}
@@ -641,7 +643,9 @@ const FormFill = (_props: BaseToolProps) => {
{field.label || field.name}
</span>
{field.required && (
<span className={styles.fieldRequired}>req</span>
<span className={styles.fieldRequired}>
{t("formFill.requiredAbbreviation", "req")}
</span>
)}
</div>
@@ -683,10 +687,10 @@ const FormFill = (_props: BaseToolProps) => {
<span className={styles.unsavedDot} />
)}
{formState.isDirty || flattenChanged
? "Unsaved changes"
: "All saved"}
? t("formFill.unsavedChanges", "Unsaved changes")
: t("formFill.allSaved", "All saved")}
</span>
<span>Ctrl+S to save</span>
<span>{t("formFill.saveShortcut", "Ctrl+S to save")}</span>
</div>
)}
</>
+3 -1
View File
@@ -1,5 +1,6 @@
import { useState, type ReactNode } from "react";
import { Button } from "@app/ui/Button";
import { useTranslation } from "react-i18next";
import "@app/ui/CodeBlock.css";
export type CodeLang =
@@ -33,6 +34,7 @@ export function CodeBlock({
maxHeight = 400,
className,
}: CodeBlockProps) {
const { t } = useTranslation();
const [copied, setCopied] = useState(false);
async function copy() {
@@ -64,7 +66,7 @@ export function CodeBlock({
onClick={copy}
aria-label="Copy code"
>
{copied ? "Copied" : "Copy"}
{copied ? t("common.copied", "Copied!") : t("common.copy", "Copy")}
</Button>
)}
</div>
@@ -262,6 +262,6 @@ describe("AuthCallback", () => {
</BrowserRouter>,
);
expect(getByText("Completing authentication")).toBeInTheDocument();
expect(getByText("auth.callback.completing")).toBeInTheDocument();
});
});
@@ -1,5 +1,6 @@
import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {
consumePostLoginRedirectPath,
springAuth,
@@ -7,6 +8,7 @@ import {
import { markLoginLandingPending } from "@app/utils/loginLanding";
import { handleAuthCallbackSuccess } from "@app/extensions/authCallback";
import styles from "@app/routes/AuthCallback.module.css";
import i18n from "@app/i18n";
/**
* OAuth Callback Handler
@@ -16,6 +18,7 @@ import styles from "@app/routes/AuthCallback.module.css";
* We extract it, store in localStorage, and redirect to the home page.
*/
export default function AuthCallback() {
const { t } = useTranslation();
const navigate = useNavigate();
const processingRef = useRef(false);
@@ -31,7 +34,12 @@ export default function AuthCallback() {
) {
navigate("/login", {
replace: true,
state: { error: "You have been signed out. Please sign in again." },
state: {
error: i18n.t(
"auth.callback.signedOut",
"You have been signed out. Please sign in again.",
),
},
});
return;
}
@@ -50,7 +58,12 @@ export default function AuthCallback() {
);
navigate("/login", {
replace: true,
state: { error: "OAuth login failed - no token received." },
state: {
error: i18n.t(
"auth.callback.missingToken",
"OAuth login failed - no token received.",
),
},
});
return;
}
@@ -67,7 +80,12 @@ export default function AuthCallback() {
localStorage.removeItem("stirling_jwt");
navigate("/login", {
replace: true,
state: { error: "OAuth login failed - invalid token." },
state: {
error: i18n.t(
"auth.callback.invalidToken",
"OAuth login failed - invalid token.",
),
},
});
return;
}
@@ -93,7 +111,12 @@ export default function AuthCallback() {
);
navigate("/login", {
replace: true,
state: { error: "OAuth login failed. Please try again." },
state: {
error: i18n.t(
"auth.callback.oauthFailed",
"OAuth login failed. Please try again.",
),
},
});
}
};
@@ -105,12 +128,20 @@ export default function AuthCallback() {
<div className={styles.page}>
<div className={styles.card}>
<div className={`${styles.icon} ${styles.iconNeutral}`}>...</div>
<div className={styles.title}>Completing authentication</div>
<div className={styles.title}>
{t("auth.callback.completing", "Completing authentication")}
</div>
<div className={styles.message}>
Please wait while we finish signing you in.
{t(
"auth.callback.pleaseWait",
"Please wait while we finish signing you in.",
)}
</div>
<div className={styles.loadingExtra}>
You can close this window once it completes.
{t(
"auth.callback.windowMayClose",
"You can close this window once it completes.",
)}
</div>
</div>
</div>
@@ -109,7 +109,9 @@ export default function Landing() {
>
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-3"></div>
<div className="text-gray-600">Loading...</div>
<div className="text-gray-600">
{t("common.loading", "Loading...")}
</div>
</div>
</div>
);
@@ -302,15 +302,20 @@ const AppConfigModal: React.FC<AppConfigModalProps> = ({
<Modal
opened={confirmOpen}
onClose={() => setConfirmOpen(false)}
title="Sign out"
title={t("settings.signOut.title", "Sign out")}
centered
zIndex={Z_INDEX_OVER_SETTINGS_MODAL}
>
<div className="confirm-modal-content">
<Text>Are you sure you want to sign out?</Text>
<Text>
{t(
"settings.signOut.confirm",
"Are you sure you want to sign out?",
)}
</Text>
<div className="confirm-modal-buttons">
<Button variant="secondary" onClick={() => setConfirmOpen(false)}>
Cancel
{t("common.cancel", "Cancel")}
</Button>
<Button
accent="danger"
@@ -324,7 +329,7 @@ const AppConfigModal: React.FC<AppConfigModalProps> = ({
}
}}
>
Sign out
{t("settings.signOut.submit", "Sign out")}
</Button>
</div>
</div>
@@ -13,6 +13,7 @@ import {
createScale,
} from "@app/components/shared/charts/utils/d3Utils";
import "@app/components/shared/charts/StackedBarChart.css";
import { useTranslation } from "react-i18next";
export default function StackedBarChart({
fractions,
@@ -26,6 +27,7 @@ export default function StackedBarChart({
animationDurationMs = 900,
ariaLabel,
}: StackedBarChartProps) {
const { t } = useTranslation();
const containerRef = useRef<HTMLDivElement | null>(null);
const tooltipRef = useRef<HTMLDivElement | null>(null);
const hasAnimatedRef = useRef(false);
@@ -309,7 +311,7 @@ export default function StackedBarChart({
outline: "1px solid var(--api-keys-card-border)",
}}
/>
<Text size="sm">Remaining</Text>
<Text size="sm">{t("common.remaining", "Remaining")}</Text>
</Group>
</Group>
)}
@@ -144,7 +144,7 @@ export const ProfilePictureCropper: React.FC<ProfilePictureCropperProps> = ({
>
<Stack gap="md">
{error && (
<Alert color="red" title="Error">
<Alert color="red" title={t("common.error", "Error")}>
{error}
</Alert>
)}