From 350fdcf29ac4fa2b63220f53e88e9c4532146500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Sz=C3=BCcs?= <127139797+balazs-szucs@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:57:14 +0100 Subject: [PATCH 1/4] [V2] feat(merge): implement natural sorting for filenames in merge tool (#4888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes TLDR: - Added `naturalCompare` function to handle alphanumeric sorting - Updated `sortFiles` logic to use `naturalCompare` for filename sorting - Passed `naturalCompare` as dependency in sorting callback This pull request improves the file sorting logic in the `Merge` tool to provide a more natural, human-friendly ordering of filenames (e.g., "file2" now comes before "file10" instead of after). The main change is the introduction of a custom `naturalCompare` function that is used when sorting files by filename. File sorting improvements: * Added a `naturalCompare` function to sort filenames in a way that handles numeric portions naturally, ensuring files like "file2" are ordered before "file10" (`frontend/src/core/tools/Merge.tsx`). * Updated the file sorting logic to use `naturalCompare` instead of the default `localeCompare` when sorting by filename (`frontend/src/core/tools/Merge.tsx`). * Ensured the `sortFiles` callback properly depends on the new `naturalCompare` function (`frontend/src/core/tools/Merge.tsx`). Note: the sort on upload is natural sort (at least I think so I haven't checked the code), this is only relevant after upload, and you click the sort button again ### Before: image ### After: image --- ## Checklist ### General - [X] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [X] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/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) - [X] I have performed a self-review of my own code - [X] 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) - [X] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [X] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Signed-off-by: Balázs Szücs --- frontend/src/core/tools/Merge.tsx | 55 +++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/frontend/src/core/tools/Merge.tsx b/frontend/src/core/tools/Merge.tsx index e725291db..05d358f70 100644 --- a/frontend/src/core/tools/Merge.tsx +++ b/frontend/src/core/tools/Merge.tsx @@ -26,6 +26,57 @@ const Merge = (props: BaseToolProps) => { props, { minFiles: 2 } ); + const naturalCompare = useCallback((a: string, b: string): number => { + const isDigit = (char: string) => char >= '0' && char <= '9'; + + const getChunk = (s: string, length: number, marker: number): { chunk: string; newMarker: number } => { + let chunk = ''; + const c = s.charAt(marker); + chunk += c; + marker++; + + if (isDigit(c)) { + while (marker < length && isDigit(s.charAt(marker))) { + chunk += s.charAt(marker); + marker++; + } + } else { + while (marker < length && !isDigit(s.charAt(marker))) { + chunk += s.charAt(marker); + marker++; + } + } + return { chunk, newMarker: marker }; + }; + + const len1 = a.length; + const len2 = b.length; + let marker1 = 0; + let marker2 = 0; + + while (marker1 < len1 && marker2 < len2) { + const { chunk: chunk1, newMarker: newMarker1 } = getChunk(a, len1, marker1); + marker1 = newMarker1; + + const { chunk: chunk2, newMarker: newMarker2 } = getChunk(b, len2, marker2); + marker2 = newMarker2; + + let result: number; + if (isDigit(chunk1.charAt(0)) && isDigit(chunk2.charAt(0))) { + const num1 = parseInt(chunk1, 10); + const num2 = parseInt(chunk2, 10); + result = num1 - num2; + } else { + result = chunk1.localeCompare(chunk2); + } + + if (result !== 0) { + return result; + } + } + + return len1 - len2; + }, []); // Custom file sorting logic for merge tool const sortFiles = useCallback((sortType: 'filename' | 'dateModified', ascending: boolean = true) => { @@ -33,7 +84,7 @@ const Merge = (props: BaseToolProps) => { let comparison = 0; switch (sortType) { case 'filename': - comparison = stubA.name.localeCompare(stubB.name); + comparison = naturalCompare(stubA.name, stubB.name); break; case 'dateModified': comparison = stubA.lastModified - stubB.lastModified; @@ -45,7 +96,7 @@ const Merge = (props: BaseToolProps) => { const selectedIds = sortedStubs.map(record => record.id); const deselectedIds = fileIds.filter(id => !selectedIds.includes(id)); reorderFiles([...selectedIds, ...deselectedIds]); - }, [selectedFileStubs, fileIds, reorderFiles]); + }, [selectedFileStubs, fileIds, reorderFiles, naturalCompare]); return createToolFlow({ files: { From 052a3ae653348ee0db7f766e611a856ae848a13e Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Thu, 13 Nov 2025 17:29:53 +0000 Subject: [PATCH 2/4] api (#4892) # Description of Changes image --- ## 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/devGuide/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 tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .../public/locales/en-GB/translation.json | 6 + .../core/components/shared/AppConfigModal.tsx | 8 +- .../shared/config/configNavSections.tsx | 3 +- frontend/src/core/styles/theme.css | 18 +++ .../shared/config/configNavSections.tsx | 23 +++- .../shared/config/configSections/ApiKeys.tsx | 126 ++++++++++++++++++ .../configSections/apiKeys/ApiKeySection.tsx | 75 +++++++++++ .../configSections/apiKeys/RefreshModal.tsx | 50 +++++++ .../configSections/apiKeys/hooks/useApiKey.ts | 64 +++++++++ 9 files changed, 368 insertions(+), 5 deletions(-) create mode 100644 frontend/src/proprietary/components/shared/config/configSections/ApiKeys.tsx create mode 100644 frontend/src/proprietary/components/shared/config/configSections/apiKeys/ApiKeySection.tsx create mode 100644 frontend/src/proprietary/components/shared/config/configSections/apiKeys/RefreshModal.tsx create mode 100644 frontend/src/proprietary/components/shared/config/configSections/apiKeys/hooks/useApiKey.ts diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 2665b3420..9cfe5380b 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -4650,6 +4650,12 @@ } }, "apiKeys": { + "intro": "Use your API key to programmatically access Stirling PDF's processing capabilities.", + "docsTitle": "API Documentation", + "docsDescription": "Learn more about integrating with Stirling PDF:", + "docsLink": "API Documentation", + "schemaLink": "API Schema Reference", + "usage": "Include this key in the X-API-KEY header with all API requests.", "description": "Your API key for accessing Stirling's suite of PDF tools. Copy it to your project or refresh to generate a new one.", "publicKeyAriaLabel": "Public API key", "copyKeyAriaLabel": "Copy API key", diff --git a/frontend/src/core/components/shared/AppConfigModal.tsx b/frontend/src/core/components/shared/AppConfigModal.tsx index e69aa97e8..f9937f775 100644 --- a/frontend/src/core/components/shared/AppConfigModal.tsx +++ b/frontend/src/core/components/shared/AppConfigModal.tsx @@ -64,17 +64,19 @@ const AppConfigModal: React.FC = ({ opened, onClose }) => { headerBorder: 'var(--modal-header-border)', }), []); - // Get isAdmin and runningEE from app config + // Get isAdmin, runningEE, and loginEnabled from app config const isAdmin = config?.isAdmin ?? false; const runningEE = config?.runningEE ?? false; + const loginEnabled = config?.enableLogin ?? false; // Left navigation structure and icons const configNavSections = useMemo(() => createConfigNavSections( isAdmin, - runningEE + runningEE, + loginEnabled ), - [isAdmin, runningEE] + [isAdmin, runningEE, loginEnabled] ); const activeLabel = useMemo(() => { diff --git a/frontend/src/core/components/shared/config/configNavSections.tsx b/frontend/src/core/components/shared/config/configNavSections.tsx index cbef8d27e..a1f66480b 100644 --- a/frontend/src/core/components/shared/config/configNavSections.tsx +++ b/frontend/src/core/components/shared/config/configNavSections.tsx @@ -41,7 +41,8 @@ export interface ConfigColors { export const createConfigNavSections = ( isAdmin: boolean = false, - runningEE: boolean = false + runningEE: boolean = false, + _loginEnabled: boolean = false ): ConfigNavSection[] => { const sections: ConfigNavSection[] = [ { diff --git a/frontend/src/core/styles/theme.css b/frontend/src/core/styles/theme.css index 3051ced06..5e8d4087b 100644 --- a/frontend/src/core/styles/theme.css +++ b/frontend/src/core/styles/theme.css @@ -279,6 +279,15 @@ --modal-content-bg: #ffffff; --modal-header-border: rgba(0, 0, 0, 0.06); + /* API Keys section colors (light mode) */ + --api-keys-card-bg: #ffffff; + --api-keys-card-border: #e0e0e0; + --api-keys-card-shadow: rgba(0, 0, 0, 0.06); + --api-keys-input-bg: #f8f8f8; + --api-keys-input-border: #e0e0e0; + --api-keys-button-bg: #f5f5f5; + --api-keys-button-color: #333333; + /* PDF Report Colors (always light) */ --pdf-light-header-bg: 239 246 255; --pdf-light-accent: 59 130 246; @@ -539,6 +548,15 @@ --modal-content-bg: #2A2F36; --modal-header-border: rgba(255, 255, 255, 0.08); + /* API Keys section colors (dark mode) */ + --api-keys-card-bg: #2A2F36; + --api-keys-card-border: #3A4047; + --api-keys-card-shadow: none; + --api-keys-input-bg: #1F2329; + --api-keys-input-border: #3A4047; + --api-keys-button-bg: #3A4047; + --api-keys-button-color: #D0D6DC; + /* Code token colors (dark mode - Cursor-like) */ --code-kw-color: #C792EA; /* purple */ --code-str-color: #C3E88D; /* green */ diff --git a/frontend/src/proprietary/components/shared/config/configNavSections.tsx b/frontend/src/proprietary/components/shared/config/configNavSections.tsx index 1df66b409..9be7640cc 100644 --- a/frontend/src/proprietary/components/shared/config/configNavSections.tsx +++ b/frontend/src/proprietary/components/shared/config/configNavSections.tsx @@ -2,13 +2,15 @@ import React from 'react'; import { createConfigNavSections as createCoreConfigNavSections, ConfigNavSection } from '@core/components/shared/config/configNavSections'; import PeopleSection from '@app/components/shared/config/configSections/PeopleSection'; import TeamsSection from '@app/components/shared/config/configSections/TeamsSection'; +import ApiKeys from '@app/components/shared/config/configSections/ApiKeys'; /** * Proprietary extension of createConfigNavSections that adds workspace sections */ export const createConfigNavSections = ( isAdmin: boolean = false, - runningEE: boolean = false + runningEE: boolean = false, + loginEnabled: boolean = false ): ConfigNavSection[] => { // Get the core sections const sections = createCoreConfigNavSections(isAdmin, runningEE); @@ -37,6 +39,25 @@ export const createConfigNavSections = ( sections.splice(1, 0, workspaceSection); } + // Add Developer section if login is enabled + if (loginEnabled) { + const developerSection: ConfigNavSection = { + title: 'Developer', + items: [ + { + key: 'api-keys', + label: 'API Keys', + icon: 'key-rounded', + component: + }, + ], + }; + + // Add Developer section after Preferences (or Workspace if it exists) + const insertIndex = isAdmin ? 2 : 1; + sections.splice(insertIndex, 0, developerSection); + } + return sections; }; diff --git a/frontend/src/proprietary/components/shared/config/configSections/ApiKeys.tsx b/frontend/src/proprietary/components/shared/config/configSections/ApiKeys.tsx new file mode 100644 index 000000000..fcc910887 --- /dev/null +++ b/frontend/src/proprietary/components/shared/config/configSections/ApiKeys.tsx @@ -0,0 +1,126 @@ +import React, { useState } from "react"; +import { Anchor, Group, Stack, Text, Paper, Skeleton } from "@mantine/core"; +// eslint-disable-next-line no-restricted-imports +import ApiKeySection from "./apiKeys/ApiKeySection"; +// eslint-disable-next-line no-restricted-imports +import RefreshModal from "./apiKeys/RefreshModal"; +// eslint-disable-next-line no-restricted-imports +import useApiKey from "./apiKeys/hooks/useApiKey"; +import { useTranslation } from "react-i18next"; +import LocalIcon from "@app/components/shared/LocalIcon"; + +export default function ApiKeys() { + const [copied, setCopied] = useState(null); + const [showRefreshModal, setShowRefreshModal] = useState(false); + const { t } = useTranslation(); + + const { apiKey, isLoading: apiKeyLoading, refresh, isRefreshing, error: apiKeyError, refetch } = useApiKey(); + + const copy = async (text: string, tag: string) => { + try { + await navigator.clipboard.writeText(text); + setCopied(tag); + setTimeout(() => setCopied(null), 1600); + } catch (e) { + console.error(e); + } + }; + + const refreshKeys = async () => { + try { + await refresh(); + } finally { + setShowRefreshModal(false); + } + }; + + return ( + + + {t('config.apiKeys.intro', 'Use your API key to programmatically access Stirling PDF\'s processing capabilities.')} + + + + + + + + {t('config.apiKeys.docsTitle', 'API Documentation')} + + + {t('config.apiKeys.docsDescription', 'Learn more about integrating with Stirling PDF:')} + + + + + {t('config.apiKeys.docsLink', 'API Documentation')} + + + + + + {t('config.apiKeys.schemaLink', 'API Schema Reference')} + + + + + + + + + {apiKeyError && ( + + {t('config.apiKeys.generateError', "We couldn't generate your API key.")} {" "} + + {t('common.retry', 'Retry')} + + + )} + + {apiKeyLoading ? ( +
+ + + + + +
+ ) : ( + setShowRefreshModal(true)} + disabled={isRefreshing} + /> + )} + + + {t('config.apiKeys.usage', 'Include this key in the X-API-KEY header with all API requests.')} + + + setShowRefreshModal(false)} + onConfirm={refreshKeys} + /> +
+ ); +} diff --git a/frontend/src/proprietary/components/shared/config/configSections/apiKeys/ApiKeySection.tsx b/frontend/src/proprietary/components/shared/config/configSections/apiKeys/ApiKeySection.tsx new file mode 100644 index 000000000..e258dc6dc --- /dev/null +++ b/frontend/src/proprietary/components/shared/config/configSections/apiKeys/ApiKeySection.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { + Box, + Button, + Group, + Paper, +} from "@mantine/core"; +import LocalIcon from "@app/components/shared/LocalIcon"; +import FitText from "@app/components/shared/FitText"; +import { useTranslation } from "react-i18next"; + +interface ApiKeySectionProps { + publicKey: string; + copied: string | null; + onCopy: (text: string, tag: string) => void; + onRefresh: () => void; + disabled?: boolean; +} + +export default function ApiKeySection({ + publicKey, + copied, + onCopy, + onRefresh, + disabled, +}: ApiKeySectionProps) { + const { t } = useTranslation(); + return ( + <> + + + + + + + + + + + + + ); +} diff --git a/frontend/src/proprietary/components/shared/config/configSections/apiKeys/RefreshModal.tsx b/frontend/src/proprietary/components/shared/config/configSections/apiKeys/RefreshModal.tsx new file mode 100644 index 000000000..faa6e60b4 --- /dev/null +++ b/frontend/src/proprietary/components/shared/config/configSections/apiKeys/RefreshModal.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { + Modal, + Stack, + Text, + Group, + Button, +} from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import { Z_INDEX_OVER_CONFIG_MODAL } from "@app/styles/zIndex"; + +interface RefreshModalProps { + opened: boolean; + onClose: () => void; + onConfirm: () => void; +} + +export default function RefreshModal({ opened, onClose, onConfirm }: RefreshModalProps) { + const { t } = useTranslation(); + return ( + + + + {t('config.apiKeys.refreshModal.warning', '⚠️ Warning: This action will generate new API keys and make your previous keys invalid.')} + + + {t('config.apiKeys.refreshModal.impact', 'Any applications or services currently using these keys will stop working until you update them with the new keys.')} + + + {t('config.apiKeys.refreshModal.confirmPrompt', 'Are you sure you want to continue?')} + + + + + + + + ); +} diff --git a/frontend/src/proprietary/components/shared/config/configSections/apiKeys/hooks/useApiKey.ts b/frontend/src/proprietary/components/shared/config/configSections/apiKeys/hooks/useApiKey.ts new file mode 100644 index 000000000..c8efe73f4 --- /dev/null +++ b/frontend/src/proprietary/components/shared/config/configSections/apiKeys/hooks/useApiKey.ts @@ -0,0 +1,64 @@ +import { useCallback, useEffect, useState } from "react"; +import apiClient from "@app/services/apiClient"; + +export function useApiKey() { + const [apiKey, setApiKey] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [isRefreshing, setIsRefreshing] = useState(false); + const [error, setError] = useState(null); + const [hasAttempted, setHasAttempted] = useState(false); + + const fetchKey = useCallback(async () => { + setIsLoading(true); + setError(null); + try { + // Backend is POST for get and update + const res = await apiClient.post("/api/v1/user/get-api-key"); + const value = typeof res.data === "string" ? res.data : res.data?.apiKey; + if (typeof value === "string") setApiKey(value); + } catch (e: any) { + // If not found, try to create one by calling update endpoint + if (e?.response?.status === 404) { + try { + const createRes = await apiClient.post("/api/v1/user/update-api-key"); + const created = + typeof createRes.data === "string" + ? createRes.data + : createRes.data?.apiKey; + if (typeof created === "string") setApiKey(created); + } catch (createErr: any) { + setError(createErr); + } + } else { + setError(e); + } + } finally { + setIsLoading(false); + setHasAttempted(true); + } + }, []); + + const refresh = useCallback(async () => { + setIsRefreshing(true); + setError(null); + try { + const res = await apiClient.post("/api/v1/user/update-api-key"); + const value = typeof res.data === "string" ? res.data : res.data?.apiKey; + if (typeof value === "string") setApiKey(value); + } catch (e: any) { + setError(e); + } finally { + setIsRefreshing(false); + } + }, []); + + useEffect(() => { + if (!hasAttempted) { + fetchKey(); + } + }, [hasAttempted, fetchKey]); + + return { apiKey, isLoading, isRefreshing, error, refetch: fetchKey, refresh, hasAttempted } as const; +} + +export default useApiKey; From edba81ff9dd1dc4c2cd9f3f1ba3bb146ad3fd710 Mon Sep 17 00:00:00 2001 From: "stirlingbot[bot]" <195170888+stirlingbot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 17:46:51 +0000 Subject: [PATCH 3/4] :globe_with_meridians: [V2] Sync Translations + Update README Progress Table (#4754) ### Description of Changes This Pull Request was automatically generated to synchronize updates to translation files and documentation for the **V2 branch**. Below are the details of the changes made: #### **1. Synchronization of Translation Files** - Updated translation files (`frontend/public/locales/*/translation.json`) to reflect changes in the reference file `en-GB/translation.json`. - Ensured consistency and synchronization across all supported language files. - Highlighted any missing or incomplete translations. #### **2. Update README.md** - Generated the translation progress table in `README.md`. - Added a summary of the current translation status for all supported languages. - Included up-to-date statistics on translation coverage. #### **Why these changes are necessary** - Keeps translation files aligned with the latest reference updates. - Ensures the documentation reflects the current translation progress. --- Auto-generated by [create-pull-request][1]. [1]: https://github.com/peter-evans/create-pull-request Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com> --- README.md | 74 ++++++++++++++++----------------- scripts/ignore_translation.toml | 1 - 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index f8058d906..211301b99 100644 --- a/README.md +++ b/README.md @@ -115,46 +115,46 @@ Stirling-PDF currently supports 40 languages! | Language | Progress | | -------------------------------------------- | -------------------------------------- | -| Arabic (العربية) (ar_AR) | ![83%](https://geps.dev/progress/83) | -| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![32%](https://geps.dev/progress/32) | -| Basque (Euskara) (eu_ES) | ![18%](https://geps.dev/progress/18) | -| Bulgarian (Български) (bg_BG) | ![35%](https://geps.dev/progress/35) | -| Catalan (Català) (ca_CA) | ![34%](https://geps.dev/progress/34) | -| Croatian (Hrvatski) (hr_HR) | ![31%](https://geps.dev/progress/31) | -| Czech (Česky) (cs_CZ) | ![34%](https://geps.dev/progress/34) | -| Danish (Dansk) (da_DK) | ![30%](https://geps.dev/progress/30) | -| Dutch (Nederlands) (nl_NL) | ![30%](https://geps.dev/progress/30) | +| Arabic (العربية) (ar_AR) | ![64%](https://geps.dev/progress/64) | +| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![24%](https://geps.dev/progress/24) | +| Basque (Euskara) (eu_ES) | ![14%](https://geps.dev/progress/14) | +| Bulgarian (Български) (bg_BG) | ![26%](https://geps.dev/progress/26) | +| Catalan (Català) (ca_CA) | ![26%](https://geps.dev/progress/26) | +| Croatian (Hrvatski) (hr_HR) | ![24%](https://geps.dev/progress/24) | +| Czech (Česky) (cs_CZ) | ![26%](https://geps.dev/progress/26) | +| Danish (Dansk) (da_DK) | ![23%](https://geps.dev/progress/23) | +| Dutch (Nederlands) (nl_NL) | ![23%](https://geps.dev/progress/23) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) | -| French (Français) (fr_FR) | ![82%](https://geps.dev/progress/82) | -| German (Deutsch) (de_DE) | ![84%](https://geps.dev/progress/84) | -| Greek (Ελληνικά) (el_GR) | ![34%](https://geps.dev/progress/34) | -| Hindi (हिंदी) (hi_IN) | ![34%](https://geps.dev/progress/34) | -| Hungarian (Magyar) (hu_HU) | ![38%](https://geps.dev/progress/38) | -| Indonesian (Bahasa Indonesia) (id_ID) | ![31%](https://geps.dev/progress/31) | -| Irish (Gaeilge) (ga_IE) | ![34%](https://geps.dev/progress/34) | -| Italian (Italiano) (it_IT) | ![84%](https://geps.dev/progress/84) | -| Japanese (日本語) (ja_JP) | ![62%](https://geps.dev/progress/62) | -| Korean (한국어) (ko_KR) | ![34%](https://geps.dev/progress/34) | -| Norwegian (Norsk) (no_NB) | ![32%](https://geps.dev/progress/32) | -| Persian (فارسی) (fa_IR) | ![34%](https://geps.dev/progress/34) | -| Polish (Polski) (pl_PL) | ![36%](https://geps.dev/progress/36) | -| Portuguese (Português) (pt_PT) | ![34%](https://geps.dev/progress/34) | -| Portuguese Brazilian (Português) (pt_BR) | ![83%](https://geps.dev/progress/83) | -| Romanian (Română) (ro_RO) | ![28%](https://geps.dev/progress/28) | -| Russian (Русский) (ru_RU) | ![83%](https://geps.dev/progress/83) | -| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![37%](https://geps.dev/progress/37) | -| Simplified Chinese (简体中文) (zh_CN) | ![85%](https://geps.dev/progress/85) | -| Slovakian (Slovensky) (sk_SK) | ![26%](https://geps.dev/progress/26) | -| Slovenian (Slovenščina) (sl_SI) | ![36%](https://geps.dev/progress/36) | -| Spanish (Español) (es_ES) | ![84%](https://geps.dev/progress/84) | -| Swedish (Svenska) (sv_SE) | ![33%](https://geps.dev/progress/33) | -| Thai (ไทย) (th_TH) | ![31%](https://geps.dev/progress/31) | +| French (Français) (fr_FR) | ![63%](https://geps.dev/progress/63) | +| German (Deutsch) (de_DE) | ![64%](https://geps.dev/progress/64) | +| Greek (Ελληνικά) (el_GR) | ![26%](https://geps.dev/progress/26) | +| Hindi (हिंदी) (hi_IN) | ![26%](https://geps.dev/progress/26) | +| Hungarian (Magyar) (hu_HU) | ![29%](https://geps.dev/progress/29) | +| Indonesian (Bahasa Indonesia) (id_ID) | ![24%](https://geps.dev/progress/24) | +| Irish (Gaeilge) (ga_IE) | ![26%](https://geps.dev/progress/26) | +| Italian (Italiano) (it_IT) | ![64%](https://geps.dev/progress/64) | +| Japanese (日本語) (ja_JP) | ![47%](https://geps.dev/progress/47) | +| Korean (한국어) (ko_KR) | ![26%](https://geps.dev/progress/26) | +| Norwegian (Norsk) (no_NB) | ![24%](https://geps.dev/progress/24) | +| Persian (فارسی) (fa_IR) | ![26%](https://geps.dev/progress/26) | +| Polish (Polski) (pl_PL) | ![27%](https://geps.dev/progress/27) | +| Portuguese (Português) (pt_PT) | ![26%](https://geps.dev/progress/26) | +| Portuguese Brazilian (Português) (pt_BR) | ![64%](https://geps.dev/progress/64) | +| Romanian (Română) (ro_RO) | ![22%](https://geps.dev/progress/22) | +| Russian (Русский) (ru_RU) | ![63%](https://geps.dev/progress/63) | +| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![28%](https://geps.dev/progress/28) | +| Simplified Chinese (简体中文) (zh_CN) | ![65%](https://geps.dev/progress/65) | +| Slovakian (Slovensky) (sk_SK) | ![19%](https://geps.dev/progress/19) | +| Slovenian (Slovenščina) (sl_SI) | ![27%](https://geps.dev/progress/27) | +| Spanish (Español) (es_ES) | ![64%](https://geps.dev/progress/64) | +| Swedish (Svenska) (sv_SE) | ![25%](https://geps.dev/progress/25) | +| Thai (ไทย) (th_TH) | ![23%](https://geps.dev/progress/23) | | Tibetan (བོད་ཡིག་) (bo_CN) | ![65%](https://geps.dev/progress/65) | -| Traditional Chinese (繁體中文) (zh_TW) | ![38%](https://geps.dev/progress/38) | -| Turkish (Türkçe) (tr_TR) | ![37%](https://geps.dev/progress/37) | -| Ukrainian (Українська) (uk_UA) | ![36%](https://geps.dev/progress/36) | -| Vietnamese (Tiếng Việt) (vi_VN) | ![28%](https://geps.dev/progress/28) | +| Traditional Chinese (繁體中文) (zh_TW) | ![29%](https://geps.dev/progress/29) | +| Turkish (Türkçe) (tr_TR) | ![28%](https://geps.dev/progress/28) | +| Ukrainian (Українська) (uk_UA) | ![28%](https://geps.dev/progress/28) | +| Vietnamese (Tiếng Việt) (vi_VN) | ![21%](https://geps.dev/progress/21) | | Malayalam (മലയാളം) (ml_IN) | ![73%](https://geps.dev/progress/73) | ## Stirling PDF Enterprise diff --git a/scripts/ignore_translation.toml b/scripts/ignore_translation.toml index f0d1d7d2b..83e2df925 100644 --- a/scripts/ignore_translation.toml +++ b/scripts/ignore_translation.toml @@ -242,7 +242,6 @@ ignore = [ 'team.status', 'text', 'update.version', - 'validateSignature.cert.bits', 'validateSignature.cert.version', 'validateSignature.status', 'watermark.type.1', From 50760b5302251821913ee872b9a10db2683c14c2 Mon Sep 17 00:00:00 2001 From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:04:09 +0000 Subject: [PATCH 4/4] quick fix to compare (#4893) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes - Compare fix to stop the pages from getting stuck in loading state. This code tracks which page images have actually finished loading in a Map stored in a ref, and then uses a throwaway useState value (setImageLoadedTick) just to force a re-render when that ref changes. When an fires onLoad, it marks that page as loaded in imageLoadedRef and bumps the tick, causing React to re-render and re-evaluate the condition for the loader overlay. Because the overlay is only shown when imageLoadedRef.current.get(page.pageNumber) is false, that re-render hides the blur/loader once the page’s image has loaded and is in view, instead of the UI getting stuck in the “loading” state. Screenshot 2025-11-13 at 5 57 42 PM --- ## 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/devGuide/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 tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .../core/components/tools/compare/CompareDocumentPane.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/core/components/tools/compare/CompareDocumentPane.tsx b/frontend/src/core/components/tools/compare/CompareDocumentPane.tsx index 78f9a4b75..18adf6ac6 100644 --- a/frontend/src/core/components/tools/compare/CompareDocumentPane.tsx +++ b/frontend/src/core/components/tools/compare/CompareDocumentPane.tsx @@ -1,5 +1,5 @@ import { Group, Loader, Stack, Text } from '@mantine/core'; -import { useMemo, useRef, useEffect } from 'react'; +import { useMemo, useRef, useEffect, useState } from 'react'; import type { PagePreview } from '@app/types/compare'; import type { TokenBoundingBox, CompareDocumentPaneProps } from '@app/types/compare'; import { mergeConnectedRects, normalizeRotation, groupWordRects, computePageLayoutMetrics } from '@app/components/tools/compare/compare'; @@ -53,6 +53,8 @@ const CompareDocumentPane = ({ // Track which page images have finished loading to avoid flashing between states const imageLoadedRef = useRef>(new Map()); + // Force a re-render when an image load state changes (refs don't trigger renders) + const [, setImageLoadedTick] = useState(0); const visiblePageRafRef = useRef(null); const lastReportedVisiblePageRef = useRef(null); const pageNodesRef = useRef(null); @@ -252,6 +254,7 @@ const CompareDocumentPane = ({ onLoad={() => { if (!imageLoadedRef.current.get(page.pageNumber)) { imageLoadedRef.current.set(page.pageNumber, true); + setImageLoadedTick((v) => v + 1); // refs don't trigger renders } }} />