diff --git a/scripts/i18n/scan-hardcoded.ts b/scripts/i18n/scan-hardcoded.ts index 7dc5711a..f7617247 100755 --- a/scripts/i18n/scan-hardcoded.ts +++ b/scripts/i18n/scan-hardcoded.ts @@ -80,7 +80,8 @@ function isHuman(value: string | undefined): value is string { if (!/[A-Za-z]{3}/.test(s)) return false; if (/^[a-z0-9\-_./:]+$/.test(s)) return false; if (/^(https?:|\/|#|data:|application\/|image\/)/.test(s)) return false; - if (/^[A-Z0-9_]+$/.test(s)) return false; + // Allows . and - so credential-format examples like `GOCSPX-...` fall under the same rule. + if (/^[A-Z0-9_.-]+$/.test(s)) return false; // `Bearer ${token}` is an Authorization header value, not a sentence. Caught by the template-string // pattern because it starts with a capital and interpolates; excluded here because an auth scheme // followed by a credential is never something a person reads. @@ -121,6 +122,18 @@ const stripComments = (source: string): string => const stripCodeElements = (source: string): string => source.replace(/<(code|kbd|pre|samp)\b[^>]*>[\s\S]*?<\/\1>/g, '<$1/>'); +/** + * `` marks text that must stay in one language — today, a label quoted from + * another product's UI, which docs/localization.md classifies as an identifier. + * + * Skipped for the same reason `neverTranslate` categories are excluded from `--check`: counting work the + * doc FORBIDS doing against a target of zero makes the target unreachable. The marker is explicit and + * carries a reason, so this cannot become a quiet catch-all — unlike a heuristic, somebody has to write + * it and say why. + */ +const stripUntranslated = (source: string): string => + source.replace(/]*>[\s\S]*?<\/Untranslated>/g, ''); + function* walk(dir: string): Generator { let entries: string[]; try { @@ -152,7 +165,7 @@ function scan(root: string): { files: number; hits: Hit[] } { files++; let source: string; try { - source = stripCodeElements(stripComments(readFileSync(file, 'utf8'))); + source = stripUntranslated(stripCodeElements(stripComments(readFileSync(file, 'utf8')))); } catch { continue; } diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/IntegrationsSettings/ApifyConfig.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/IntegrationsSettings/ApifyConfig.tsx index d9d8c9e1..5b2e6fcd 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/IntegrationsSettings/ApifyConfig.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/IntegrationsSettings/ApifyConfig.tsx @@ -5,6 +5,7 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { useClient } from 'hooks/useClient'; +import { useTranslation } from '@/lib/i18n'; type ApifyConfigData = { apiToken: string; @@ -15,6 +16,7 @@ type ApifyStatus = { }; export const ApifyConfig = () => { + const { t } = useTranslation(); const client = useClient(); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); @@ -38,10 +40,10 @@ export const ApifyConfig = () => { setIsSaving(true); try { await client.put('/integrations/apify/config', { apiToken: apiToken.trim() }); - toast.success('Apify API token saved'); + toast.success(t('settings.apify.saved')); setStatus({ configured: !!apiToken.trim() }); } catch { - toast.error('Failed to save Apify API token'); + toast.error(t('settings.apify.saveFailed')); } finally { setIsSaving(false); } @@ -64,7 +66,7 @@ export const ApifyConfig = () => {