Chore/v2/translation fixes (#5011)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## 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.
This commit is contained in:
Reece Browne
2025-11-25 20:03:47 +00:00
committed by GitHub
parent d74856f675
commit 53d167eda5
11 changed files with 369 additions and 80 deletions
@@ -2,7 +2,7 @@ import React, { useMemo, useState, useEffect, useCallback } from 'react';
import { Modal, Text, ActionIcon, Tooltip, Group } from '@mantine/core';
import { useNavigate, useLocation } from 'react-router-dom';
import LocalIcon from '@app/components/shared/LocalIcon';
import { createConfigNavSections } from '@app/components/shared/config/configNavSections';
import { useConfigNavSections } from '@app/components/shared/config/configNavSections';
import { NavKey, VALID_NAV_KEYS } from '@app/components/shared/config/types';
import { useAppConfig } from '@app/contexts/AppConfigContext';
import '@app/components/shared/AppConfigModal.css';
@@ -74,13 +74,10 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({ opened, onClose })
const loginEnabled = config?.enableLogin ?? false;
// Left navigation structure and icons
const configNavSections = useMemo(() =>
createConfigNavSections(
isAdmin,
runningEE,
loginEnabled
),
[isAdmin, runningEE, loginEnabled]
const configNavSections = useConfigNavSections(
isAdmin,
runningEE,
loginEnabled
);
const activeLabel = useMemo(() => {
@@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { NavKey } from '@app/components/shared/config/types';
import HotkeysSection from '@app/components/shared/config/configSections/HotkeysSection';
import GeneralSection from '@app/components/shared/config/configSections/GeneralSection';
@@ -27,11 +28,43 @@ export interface ConfigColors {
headerBorder: string;
}
export const useConfigNavSections = (
_isAdmin: boolean = false,
_runningEE: boolean = false,
_loginEnabled: boolean = false
): ConfigNavSection[] => {
const { t } = useTranslation();
const sections: ConfigNavSection[] = [
{
title: t('settings.preferences.title', 'Preferences'),
items: [
{
key: 'general',
label: t('settings.general.title', 'General'),
icon: 'settings-rounded',
component: <GeneralSection />
},
{
key: 'hotkeys',
label: t('settings.hotkeys.title', 'Keyboard Shortcuts'),
icon: 'keyboard-rounded',
component: <HotkeysSection />
},
],
},
];
return sections;
};
// Deprecated: Use useConfigNavSections hook instead
export const createConfigNavSections = (
_isAdmin: boolean = false,
_runningEE: boolean = false,
_loginEnabled: boolean = false
): ConfigNavSection[] => {
console.warn('createConfigNavSections is deprecated. Use useConfigNavSections hook instead for proper i18n support.');
const sections: ConfigNavSection[] = [
{
title: 'Preferences',
@@ -73,10 +73,7 @@ const HotkeysSection: React.FC = () => {
const binding = eventToBinding(event as KeyboardEvent);
if (!binding) {
const osKey = isMac ? 'mac' : 'windows';
const fallbackText = isMac
? 'Include ⌘ (Command), ⌥ (Option), or another modifier in your shortcut.'
: 'Include Ctrl, Alt, or another modifier in your shortcut.';
setError(t(`settings.hotkeys.errorModifier.${osKey}`, fallbackText));
setError(t(`settings.hotkeys.errorModifier.${osKey}`));
return;
}
@@ -112,9 +109,9 @@ const HotkeysSection: React.FC = () => {
return (
<Stack gap="lg">
<div>
<Text fw={600} size="lg">Keyboard Shortcuts</Text>
<Text fw={600} size="lg">{t('settings.hotkeys.title', 'Keyboard Shortcuts')}</Text>
<Text size="sm" c="dimmed">
Customize keyboard shortcuts for quick tool access. Click "Change shortcut" and press a new key combination. Press Esc to cancel.
{t('settings.hotkeys.description', 'Customize keyboard shortcuts for quick tool access. Click "Change shortcut" and press a new key combination. Press Esc to cancel.')}
</Text>
</div>
@@ -1,9 +1,11 @@
import React from 'react';
import { Stack, Text, Code, Group, Badge, Alert, Loader } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { useAppConfig } from '@app/contexts/AppConfigContext';
import { OverviewHeader } from '@app/components/shared/config/OverviewHeader';
const Overview: React.FC = () => {
const { t } = useTranslation();
const { config, loading, error } = useAppConfig();
const renderConfigSection = (title: string, data: any) => {
@@ -58,14 +60,14 @@ const Overview: React.FC = () => {
return (
<Stack align="center" py="md">
<Loader size="sm" />
<Text size="sm" c="dimmed">Loading configuration...</Text>
<Text size="sm" c="dimmed">{t('config.overview.loading', 'Loading configuration...')}</Text>
</Stack>
);
}
if (error) {
return (
<Alert color="red" title="Error">
<Alert color="red" title={t('config.overview.error', 'Error')}>
{error}
</Alert>
);
@@ -77,13 +79,13 @@ const Overview: React.FC = () => {
{config && (
<>
{renderConfigSection('Basic Configuration', basicConfig)}
{renderConfigSection('Security Configuration', securityConfig)}
{renderConfigSection('System Configuration', systemConfig)}
{renderConfigSection('Integration Configuration', integrationConfig)}
{renderConfigSection(t('config.overview.sections.basic', 'Basic Configuration'), basicConfig)}
{renderConfigSection(t('config.overview.sections.security', 'Security Configuration'), securityConfig)}
{renderConfigSection(t('config.overview.sections.system', 'System Configuration'), systemConfig)}
{renderConfigSection(t('config.overview.sections.integration', 'Integration Configuration'), integrationConfig)}
{config.error && (
<Alert color="yellow" title="Configuration Warning">
<Alert color="yellow" title={t('config.overview.warning', 'Configuration Warning')}>
{config.error}
</Alert>
)}
@@ -1,8 +1,39 @@
import { createConfigNavSections as createProprietaryConfigNavSections } from '@proprietary/components/shared/config/configNavSections';
import { useTranslation } from 'react-i18next';
import { useConfigNavSections as useProprietaryConfigNavSections, createConfigNavSections as createProprietaryConfigNavSections } from '@proprietary/components/shared/config/configNavSections';
import { ConfigNavSection } from '@core/components/shared/config/configNavSections';
import { ConnectionSettings } from '@app/components/ConnectionSettings';
/**
* Hook version of desktop config nav sections with proper i18n support
*/
export const useConfigNavSections = (
isAdmin: boolean = false,
runningEE: boolean = false,
loginEnabled: boolean = false
): ConfigNavSection[] => {
const { t } = useTranslation();
// Get the proprietary sections (includes core Preferences + admin sections)
const sections = useProprietaryConfigNavSections(isAdmin, runningEE, loginEnabled);
// Add Connection section at the beginning (after Preferences)
sections.splice(1, 0, {
title: t('settings.connection.title', 'Connection Mode'),
items: [
{
key: 'connectionMode',
label: t('settings.connection.title', 'Connection Mode'),
icon: 'cloud-rounded',
component: <ConnectionSettings />,
},
],
});
return sections;
};
/**
* Deprecated: Use useConfigNavSections hook instead
* Desktop extension of createConfigNavSections that adds connection settings
*/
export const createConfigNavSections = (
@@ -10,6 +41,8 @@ export const createConfigNavSections = (
runningEE: boolean = false,
loginEnabled: boolean = false
): ConfigNavSection[] => {
console.warn('createConfigNavSections is deprecated. Use useConfigNavSections hook instead for proper i18n support.');
// Get the proprietary sections (includes core Preferences + admin sections)
const sections = createProprietaryConfigNavSections(isAdmin, runningEE, loginEnabled);
@@ -1,5 +1,6 @@
import React from 'react';
import { createConfigNavSections as createCoreConfigNavSections, ConfigNavSection } from '@core/components/shared/config/configNavSections';
import { useTranslation } from 'react-i18next';
import { useConfigNavSections as useCoreConfigNavSections, 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 AdminGeneralSection from '@app/components/shared/config/configSections/AdminGeneralSection';
@@ -17,6 +18,196 @@ import AdminUsageSection from '@app/components/shared/config/configSections/Admi
import ApiKeys from '@app/components/shared/config/configSections/ApiKeys';
/**
* Hook version of proprietary config nav sections with proper i18n support
*/
export const useConfigNavSections = (
isAdmin: boolean = false,
runningEE: boolean = false,
loginEnabled: boolean = false
): ConfigNavSection[] => {
const { t } = useTranslation();
// Get the core sections (just Preferences)
const sections = useCoreConfigNavSections(isAdmin, runningEE, loginEnabled);
// Add Admin sections if user is admin OR if login is disabled (but mark as disabled)
if (isAdmin || !loginEnabled) {
const requiresLogin = !loginEnabled;
const enableLoginTooltip = t('settings.tooltips.enableLoginFirst', 'Enable login mode first');
const requiresEnterpriseTooltip = t('settings.tooltips.requiresEnterprise', 'Requires Enterprise license');
// Workspace
sections.push({
title: t('settings.workspace.title', 'Workspace'),
items: [
{
key: 'people',
label: t('settings.workspace.people', 'People'),
icon: 'group-rounded',
component: <PeopleSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
{
key: 'teams',
label: t('settings.workspace.teams', 'Teams'),
icon: 'groups-rounded',
component: <TeamsSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
],
});
// Configuration
sections.push({
title: t('settings.configuration.title', 'Configuration'),
items: [
{
key: 'adminGeneral',
label: t('settings.configuration.systemSettings', 'System Settings'),
icon: 'settings-rounded',
component: <AdminGeneralSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
{
key: 'adminFeatures',
label: t('settings.configuration.features', 'Features'),
icon: 'extension-rounded',
component: <AdminFeaturesSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
{
key: 'adminEndpoints',
label: t('settings.configuration.endpoints', 'Endpoints'),
icon: 'api-rounded',
component: <AdminEndpointsSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
{
key: 'adminDatabase',
label: t('settings.configuration.database', 'Database'),
icon: 'storage-rounded',
component: <AdminDatabaseSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
{
key: 'adminAdvanced',
label: t('settings.configuration.advanced', 'Advanced'),
icon: 'tune-rounded',
component: <AdminAdvancedSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
],
});
// Security & Authentication
sections.push({
title: t('settings.securityAuth.title', 'Security & Authentication'),
items: [
{
key: 'adminSecurity',
label: t('settings.securityAuth.security', 'Security'),
icon: 'shield-rounded',
component: <AdminSecuritySection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
{
key: 'adminConnections',
label: t('settings.securityAuth.connections', 'Connections'),
icon: 'link-rounded',
component: <AdminConnectionsSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
],
});
// Licensing & Analytics
sections.push({
title: t('settings.licensingAnalytics.title', 'Licensing & Analytics'),
items: [
{
key: 'adminPlan',
label: t('settings.licensingAnalytics.plan', 'Plan'),
icon: 'star-rounded',
component: <AdminPlanSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
{
key: 'adminAudit',
label: t('settings.licensingAnalytics.audit', 'Audit'),
icon: 'fact-check-rounded',
component: <AdminAuditSection />,
disabled: !runningEE || requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : requiresEnterpriseTooltip
},
{
key: 'adminUsage',
label: t('settings.licensingAnalytics.usageAnalytics', 'Usage Analytics'),
icon: 'analytics-rounded',
component: <AdminUsageSection />,
disabled: !runningEE || requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : requiresEnterpriseTooltip
},
],
});
// Policies & Privacy
sections.push({
title: t('settings.policiesPrivacy.title', 'Policies & Privacy'),
items: [
{
key: 'adminLegal',
label: t('settings.policiesPrivacy.legal', 'Legal'),
icon: 'gavel-rounded',
component: <AdminLegalSection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
{
key: 'adminPrivacy',
label: t('settings.policiesPrivacy.privacy', 'Privacy'),
icon: 'visibility-rounded',
component: <AdminPrivacySection />,
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined
},
],
});
}
// Add Developer section if login is enabled
if (loginEnabled) {
const developerSection: ConfigNavSection = {
title: t('settings.developer.title', 'Developer'),
items: [
{
key: 'api-keys',
label: t('settings.developer.apiKeys', 'API Keys'),
icon: 'key-rounded',
component: <ApiKeys />
},
],
};
// Add Developer section after Preferences (or Workspace if it exists)
const insertIndex = isAdmin ? 2 : 1;
sections.splice(insertIndex, 0, developerSection);
}
return sections;
};
/**
* Deprecated: Use useConfigNavSections hook instead
* Proprietary extension of createConfigNavSections that adds all admin and workspace sections
*/
export const createConfigNavSections = (
@@ -24,6 +215,8 @@ export const createConfigNavSections = (
runningEE: boolean = false,
loginEnabled: boolean = false
): ConfigNavSection[] => {
console.warn('createConfigNavSections is deprecated. Use useConfigNavSections hook instead for proper i18n support.');
// Get the core sections (just Preferences)
const sections = createCoreConfigNavSections(isAdmin, runningEE, loginEnabled);
@@ -336,7 +336,7 @@ export default function AdminGeneralSection() {
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', padding: '0.25rem 0' }}>
<img
src="/classic-logo/favicon.ico"
alt="Classic logo"
alt={t('admin.settings.general.logoStyle.classicAlt', 'Classic logo')}
style={{ width: '24px', height: '24px' }}
/>
<span>{t('admin.settings.general.logoStyle.classic', 'Classic')}</span>
@@ -349,7 +349,7 @@ export default function AdminGeneralSection() {
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', padding: '0.25rem 0' }}>
<img
src="/modern-logo/StirlingPDFLogoNoTextLight.svg"
alt="Modern logo"
alt={t('admin.settings.general.logoStyle.modernAlt', 'Modern logo')}
style={{ width: '24px', height: '24px' }}
/>
<span>{t('admin.settings.general.logoStyle.modern', 'Modern')}</span>
@@ -385,7 +385,7 @@ export default function AdminGeneralSection() {
]}
searchable
clearable
placeholder="Select languages"
placeholder={t('admin.settings.general.languages.placeholder', 'Select languages')}
comboboxProps={{ zIndex: 1400 }}
disabled={!loginEnabled}
/>
@@ -72,7 +72,7 @@ export default function TeamDetailsSection({ teamId, onBack }: TeamDetailsSectio
});
} catch (error) {
console.error('Failed to fetch team details:', error);
alert({ alertType: 'error', title: 'Failed to load team details' });
alert({ alertType: 'error', title: t('workspace.teams.loadError', 'Failed to load team details') });
onBack();
} finally {
setLoading(false);