mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
fix(i18n): localize hardcoded frontend text in English and German (#6993)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user