From c500c2fae7a9c1252e0ac0fef6d91c50edf6aeff Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 12 Jul 2026 07:33:09 +0200 Subject: [PATCH 1/2] fix(desktop): preserve RGBA format for Tauri app icon (#6990) # Description of Changes - Replaced the Tauri application icon with an RGBA-formatted PNG. - Added a root `.imgbotconfig` that excludes the Tauri icon from automatic image optimization. - Fixed the `desktop:test` compilation failure caused by `tauri::generate_context!()` rejecting the previous non-RGBA icon. - Prevented ImgBot from potentially converting the icon back to an unsupported indexed PNG while optimizing its file size. - Verified that the current icon uses PNG Color Type 6 (`Truecolour with alpha`). ```sh [desktop:test] error: proc macro panicked [desktop:test] --> src/lib.rs:202:12 [desktop:test] | [desktop:test] 202 | .build(tauri::generate_context!()) [desktop:test] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ [desktop:test] | [desktop:test] = help: message: icon /Users/runner/work/Stirling-PDF/Stirling-PDF/frontend/editor/src-tauri/icons/icon.png is not RGBA [desktop:test] [desktop:test] error: could not compile `***-pdf` (lib) due to 1 previous error [desktop:test] warning: build failed, waiting for other jobs to finish... [desktop:test] error: could not compile `***-pdf` (lib test) due to 1 previous error task: Failed to run task "desktop:test": exit status 101 Error: exit status 101 ``` --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --- .imgbotconfig | 5 +++++ frontend/editor/src-tauri/icons/icon.png | Bin 8987 -> 19631 bytes 2 files changed, 5 insertions(+) create mode 100644 .imgbotconfig diff --git a/.imgbotconfig b/.imgbotconfig new file mode 100644 index 0000000000..720b938475 --- /dev/null +++ b/.imgbotconfig @@ -0,0 +1,5 @@ +{ + "ignoredFiles": [ + "frontend/editor/src-tauri/icons/icon.png" + ] +} diff --git a/frontend/editor/src-tauri/icons/icon.png b/frontend/editor/src-tauri/icons/icon.png index 05069d088cf0471cfeea501ba1b424246343973b..5819d1b89dfa00cc6642d88cdafda58a8fc641d1 100644 GIT binary patch literal 19631 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 literal 8987 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 SD>5)JFnGH9xvX Date: Sun, 12 Jul 2026 11:16:55 +0200 Subject: [PATCH 2/2] fix(i18n): localize hardcoded frontend text in English and German (#6993) --- .../public/locales/de-DE/translation.toml | 42 ++++++++++++ .../public/locales/en-US/translation.toml | 42 ++++++++++++ .../onboarding/slides/MFASetupSlide.tsx | 67 ++++++++++++++----- .../tools/addStamp/StampPreview.tsx | 6 +- .../components/viewer/BookmarkSidebar.tsx | 32 +++++++-- .../core/components/viewer/LayerSidebar.tsx | 6 +- .../components/viewer/ThumbnailSidebar.tsx | 5 +- frontend/editor/src/core/tools/SwaggerUI.tsx | 8 ++- .../src/core/tools/formFill/FormFill.tsx | 18 +++-- frontend/editor/src/core/ui/CodeBlock.tsx | 4 +- .../proprietary/routes/AuthCallback.test.tsx | 2 +- .../src/proprietary/routes/AuthCallback.tsx | 45 +++++++++++-- .../editor/src/proprietary/routes/Landing.tsx | 4 +- .../saas/components/shared/AppConfigModal.tsx | 13 ++-- .../shared/charts/StackedBarChart.tsx | 4 +- .../shared/config/ProfilePictureCropper.tsx | 2 +- 16 files changed, 245 insertions(+), 55 deletions(-) diff --git a/frontend/editor/public/locales/de-DE/translation.toml b/frontend/editor/public/locales/de-DE/translation.toml index 2d50d8aa53..29efeca137 100644 --- a/frontend/editor/public/locales/de-DE/translation.toml +++ b/frontend/editor/public/locales/de-DE/translation.toml @@ -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 {{freeTierLimit}} 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" diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index 3c25732878..9663478000 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -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 {{freeTierLimit}} 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" diff --git a/frontend/editor/src/core/components/onboarding/slides/MFASetupSlide.tsx b/frontend/editor/src/core/components/onboarding/slides/MFASetupSlide.tsx index 4abd09b0e6..41c8308f2b 100644 --- a/frontend/editor/src/core/components/onboarding/slides/MFASetupSlide.tsx +++ b/frontend/editor/src/core/components/onboarding/slides/MFASetupSlide.tsx @@ -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) { - Step-by-step + {t("onboarding.mfa.stepByStep", "Step-by-step")}
    -
  1. Open Google Authenticator, Authy, or 1Password.
  2. -
  3. Scan the QR code or enter the setup key below.
  4. -
  5. Enter the 6-digit code from your app.
  6. +
  7. + {t( + "onboarding.mfa.openAuthenticator", + "Open Google Authenticator, Authy, or 1Password.", + )} +
  8. +
  9. + {t( + "onboarding.mfa.scanQrCode", + "Scan the QR code or enter the setup key below.", + )} +
  10. +
  11. + {t( + "onboarding.mfa.enterCode", + "Enter the 6-digit code from your app.", + )} +
- Setup key (manual entry) + {t("onboarding.mfa.setupKey", "Setup key (manual entry)")} - 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.", + )} {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")} {setupComplete && ( - MFA has been enabled. You can now continue. + {t( + "onboarding.mfa.success", + "MFA has been enabled. You can now continue.", + )} )} @@ -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: , background: { gradientStops: ["#059669", "#0891B2"], // Green to teal - security/trust colors diff --git a/frontend/editor/src/core/components/tools/addStamp/StampPreview.tsx b/frontend/editor/src/core/components/tools/addStamp/StampPreview.tsx index 88f2d72f6c..9311392beb 100644 --- a/frontend/editor/src/core/components/tools/addStamp/StampPreview.tsx +++ b/frontend/editor/src/core/components/tools/addStamp/StampPreview.tsx @@ -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(null); const [containerSize, setContainerSize] = useState<{ width: number; @@ -366,7 +368,9 @@ export default function StampPreview({
-
Preview Stamp
+
+ {t("addStamp.preview", "Preview Stamp")} +
{ 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", + )} > @@ -746,7 +760,10 @@ export const BookmarkSidebar = ({ > setSearchTerm(event.currentTarget.value)} leftSection={ @@ -839,7 +856,10 @@ export const BookmarkSidebar = ({ @@ -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")} > @@ -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")} > diff --git a/frontend/editor/src/core/components/viewer/ThumbnailSidebar.tsx b/frontend/editor/src/core/components/viewer/ThumbnailSidebar.tsx index 3fb62df970..6b195c4485 100644 --- a/frontend/editor/src/core/components/viewer/ThumbnailSidebar.tsx +++ b/frontend/editor/src/core/components/viewer/ThumbnailSidebar.tsx @@ -193,7 +193,10 @@ export function ThumbnailSidebar({ "viewer.thumbnails.closeSidebar", "Close thumbnails sidebar", )} - title="Close thumbnails" + title={t( + "viewer.thumbnails.closeSidebar", + "Close thumbnails sidebar", + )} > diff --git a/frontend/editor/src/core/tools/SwaggerUI.tsx b/frontend/editor/src/core/tools/SwaggerUI.tsx index 08a878b4c5..4ee6faf11d 100644 --- a/frontend/editor/src/core/tools/SwaggerUI.tsx +++ b/frontend/editor/src/core/tools/SwaggerUI.tsx @@ -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 = () => { + 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 = () => { return (
-

Opening Swagger UI in a new tab...

+

{t("swagger.opening", "Opening Swagger UI in a new tab...")}

- If it didn't open automatically,{" "} + {t("swagger.fallback", "If it didn't open automatically,")}{" "} - click here + {t("swagger.clickHere", "click here")}

diff --git a/frontend/editor/src/core/tools/formFill/FormFill.tsx b/frontend/editor/src/core/tools/formFill/FormFill.tsx index a99f990f33..bf1a8f5fe6 100644 --- a/frontend/editor/src/core/tools/formFill/FormFill.tsx +++ b/frontend/editor/src/core/tools/formFill/FormFill.tsx @@ -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} {field.required && ( - req + + {t("formFill.requiredAbbreviation", "req")} + )}
@@ -683,10 +687,10 @@ const FormFill = (_props: BaseToolProps) => { )} {formState.isDirty || flattenChanged - ? "Unsaved changes" - : "All saved"} + ? t("formFill.unsavedChanges", "Unsaved changes") + : t("formFill.allSaved", "All saved")} - Ctrl+S to save + {t("formFill.saveShortcut", "Ctrl+S to save")}
)} diff --git a/frontend/editor/src/core/ui/CodeBlock.tsx b/frontend/editor/src/core/ui/CodeBlock.tsx index 1a05b23e4f..cff078057d 100644 --- a/frontend/editor/src/core/ui/CodeBlock.tsx +++ b/frontend/editor/src/core/ui/CodeBlock.tsx @@ -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")} )}
diff --git a/frontend/editor/src/proprietary/routes/AuthCallback.test.tsx b/frontend/editor/src/proprietary/routes/AuthCallback.test.tsx index 61b676e786..53b838a0d3 100644 --- a/frontend/editor/src/proprietary/routes/AuthCallback.test.tsx +++ b/frontend/editor/src/proprietary/routes/AuthCallback.test.tsx @@ -262,6 +262,6 @@ describe("AuthCallback", () => { , ); - expect(getByText("Completing authentication")).toBeInTheDocument(); + expect(getByText("auth.callback.completing")).toBeInTheDocument(); }); }); diff --git a/frontend/editor/src/proprietary/routes/AuthCallback.tsx b/frontend/editor/src/proprietary/routes/AuthCallback.tsx index f0e90f5ed0..5d09776df4 100644 --- a/frontend/editor/src/proprietary/routes/AuthCallback.tsx +++ b/frontend/editor/src/proprietary/routes/AuthCallback.tsx @@ -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() {
...
-
Completing authentication
+
+ {t("auth.callback.completing", "Completing authentication")} +
- Please wait while we finish signing you in. + {t( + "auth.callback.pleaseWait", + "Please wait while we finish signing you in.", + )}
- You can close this window once it completes. + {t( + "auth.callback.windowMayClose", + "You can close this window once it completes.", + )}
diff --git a/frontend/editor/src/proprietary/routes/Landing.tsx b/frontend/editor/src/proprietary/routes/Landing.tsx index d22acbba56..c377df1794 100644 --- a/frontend/editor/src/proprietary/routes/Landing.tsx +++ b/frontend/editor/src/proprietary/routes/Landing.tsx @@ -109,7 +109,9 @@ export default function Landing() { >
-
Loading...
+
+ {t("common.loading", "Loading...")} +
); diff --git a/frontend/editor/src/saas/components/shared/AppConfigModal.tsx b/frontend/editor/src/saas/components/shared/AppConfigModal.tsx index 7b579f3c43..32def0b196 100644 --- a/frontend/editor/src/saas/components/shared/AppConfigModal.tsx +++ b/frontend/editor/src/saas/components/shared/AppConfigModal.tsx @@ -302,15 +302,20 @@ const AppConfigModal: React.FC = ({ setConfirmOpen(false)} - title="Sign out" + title={t("settings.signOut.title", "Sign out")} centered zIndex={Z_INDEX_OVER_SETTINGS_MODAL} >
- Are you sure you want to sign out? + + {t( + "settings.signOut.confirm", + "Are you sure you want to sign out?", + )} +
diff --git a/frontend/editor/src/saas/components/shared/charts/StackedBarChart.tsx b/frontend/editor/src/saas/components/shared/charts/StackedBarChart.tsx index 589eda3d0f..b1ba0ed3ac 100644 --- a/frontend/editor/src/saas/components/shared/charts/StackedBarChart.tsx +++ b/frontend/editor/src/saas/components/shared/charts/StackedBarChart.tsx @@ -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(null); const tooltipRef = useRef(null); const hasAnimatedRef = useRef(false); @@ -309,7 +311,7 @@ export default function StackedBarChart({ outline: "1px solid var(--api-keys-card-border)", }} /> - Remaining + {t("common.remaining", "Remaining")} )} diff --git a/frontend/editor/src/saas/components/shared/config/ProfilePictureCropper.tsx b/frontend/editor/src/saas/components/shared/config/ProfilePictureCropper.tsx index 8230184a2c..0604307b32 100644 --- a/frontend/editor/src/saas/components/shared/config/ProfilePictureCropper.tsx +++ b/frontend/editor/src/saas/components/shared/config/ProfilePictureCropper.tsx @@ -144,7 +144,7 @@ export const ProfilePictureCropper: React.FC = ({ > {error && ( - + {error} )}