import { useEffect, useMemo, useState } from "react"; import type { ApiSettings, PlatformWebModule } from "@govoplan/core-webui"; import { AdminPageLayout, Button, Card, DocumentationHelpLink, FormField, FormGrid, StatusBadge, adminErrorMessage, i18nMessage, useDeltaWatermarks, usePlatformModules, useUnsavedDraftGuard } from "@govoplan/core-webui"; import { fetchSystemSettingsDelta, updateSystemSettings, type LanguagePackage, type SystemSettingsItem } from "../../api/admin"; import { ADMIN_GOVERNANCE_DOCUMENTATION, ADMIN_INTERFACE_I18N, mutationDisabledReason } from "./interfacePatterns"; import { SYSTEM_SETTINGS_FALLBACK, applySystemSettingsSections, languageOptionLabel, normalizeLanguageCode } from "./systemSettingsModel"; const DELTA_KEY = "admin:language-packages"; const BUILT_IN_LANGUAGE_CODES = new Set(["de", "en"]); type LanguageLifecycle = { code: string; package: LanguagePackage | null; catalogCount: number; installed: boolean; active: boolean; uninstallable: boolean; }; export default function LanguagePackagesPanel({ settings, canWrite }: { settings: ApiSettings; canWrite: boolean; }) { const modules = usePlatformModules(); const [draft, setDraft] = useState(SYSTEM_SETTINGS_FALLBACK); const [savedDraft, setSavedDraft] = useState(SYSTEM_SETTINGS_FALLBACK); const [packageDraft, setPackageDraft] = useState({ code: "", label: "", nativeLabel: "" }); const [selectedCode, setSelectedCode] = useState("de"); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks(); const dirty = languageDraftKey(draft) !== languageDraftKey(savedDraft); const lifecycle = useMemo( () => languageLifecycle(draft, modules), [draft, modules] ); const selectedLifecycle = lifecycle.find((item) => item.code === selectedCode) ?? lifecycle[0]; useUnsavedDraftGuard({ dirty, onSave: save, onDiscard: () => { setDraft(savedDraft); setPackageDraft({ code: "", label: "", nativeLabel: "" }); } }); async function load() { setLoading(true); setError(""); try { const wasDirty = languageDraftKey(draft) !== languageDraftKey(savedDraft); const response = await fetchSystemSettingsDelta(settings, { since: getDeltaWatermark(DELTA_KEY) }); setDeltaWatermark(DELTA_KEY, response.watermark); if (response.full && response.item) { setSavedDraft(response.item); if (!wasDirty) setDraft(response.item); } else if (response.changed_sections.length) { setSavedDraft((current) => applySystemSettingsSections(current, response.sections)); if (!wasDirty) { setDraft((current) => applySystemSettingsSections(current, response.sections)); } } } catch (err) { setError(adminErrorMessage(err)); } finally { setLoading(false); } } useEffect(() => { resetDeltaWatermark(DELTA_KEY); void load(); }, [settings.accessToken, settings.apiBaseUrl, resetDeltaWatermark]); useEffect(() => { if (lifecycle.length && !lifecycle.some((item) => item.code === selectedCode)) { setSelectedCode(lifecycle[0].code); } }, [lifecycle, selectedCode]); async function save(): Promise { setBusy(true); setError(""); setSuccess(""); try { const saved = await updateSystemSettings(settings, { default_locale: draft.default_locale, allow_tenant_custom_groups: draft.allow_tenant_custom_groups, allow_tenant_custom_roles: draft.allow_tenant_custom_roles, allow_tenant_api_keys: draft.allow_tenant_api_keys, available_languages: draft.available_languages, enabled_language_codes: draft.enabled_language_codes }); setDraft(saved); setSavedDraft(saved); resetDeltaWatermark(DELTA_KEY); setSuccess("i18n:govoplan-admin.language_package_settings_saved.lp001"); return true; } catch (err) { setError(adminErrorMessage(err)); return false; } finally { setBusy(false); } } function installPackage(code: string, label?: string, nativeLabel?: string) { const normalized = normalizeLanguageCode(code); if (!normalized || draft.available_languages.some((item) => item.code === normalized)) return; const resolvedLabel = label?.trim() || normalized.toUpperCase(); setDraft({ ...draft, available_languages: [ ...draft.available_languages, { code: normalized, label: resolvedLabel, native_label: nativeLabel?.trim() || resolvedLabel } ] }); setSelectedCode(normalized); } function installDraftPackage() { const code = normalizeLanguageCode(packageDraft.code); if (!code || !packageDraft.label.trim()) return; installPackage(code, packageDraft.label, packageDraft.nativeLabel); setPackageDraft({ code: "", label: "", nativeLabel: "" }); } function setPackageActive(code: string, active: boolean) { const enabled = new Set(draft.enabled_language_codes); if (active) enabled.add(code); else enabled.delete(code); setDraft({ ...draft, enabled_language_codes: draft.available_languages .map((item) => item.code) .filter((item) => enabled.has(item)) }); } function uninstallPackage(code: string) { if (BUILT_IN_LANGUAGE_CODES.has(code) || code === draft.default_locale) return; setDraft({ ...draft, available_languages: draft.available_languages.filter((item) => item.code !== code), enabled_language_codes: draft.enabled_language_codes.filter((item) => item !== code) }); } const enabledDefaultOptions = draft.available_languages.filter((item) => draft.enabled_language_codes.includes(item.code) ); return ( } >

i18n:govoplan-admin.default_language_inheritance_help.lp001

{lifecycle.map((item) => (
{item.package ? languageOptionLabel(item.package) : item.code.toUpperCase()} {item.code}
{item.installed && } {item.uninstallable && }
{i18nMessage("i18n:govoplan-admin.module_version_catalogs.lp001", { value0: item.catalogCount })}
{!item.installed && } {item.installed && } {item.uninstallable && }
))}
setPackageDraft({ ...packageDraft, code: event.target.value })} /> setPackageDraft({ ...packageDraft, label: event.target.value })} /> setPackageDraft({ ...packageDraft, nativeLabel: event.target.value })} />

i18n:govoplan-admin.language_packages_help

{selectedLifecycle && } {selectedLifecycle && modules.length > 0 ? (
{modules.map((module) => )}
) :

i18n:govoplan-admin.no_module_language_catalogs.lp001

}
); } function ModuleLanguageState({ module, language }: { module: PlatformWebModule; language: LanguageLifecycle }) { const compatible = Boolean(module.translations?.[language.code]); const state = language.installed && compatible ? "i18n:govoplan-admin.installed.7bb4405c" : compatible ? "i18n:govoplan-admin.available.7c8f1005" : "i18n:govoplan-admin.incompatible.lp001"; return (
{module.label} {module.id} v{module.version}
{language.installed && compatible && }
); } function languageLifecycle(item: SystemSettingsItem, modules: PlatformWebModule[]): LanguageLifecycle[] { const installed = new Map(item.available_languages.map((language) => [language.code, language])); const codes = new Set(installed.keys()); modules.forEach((module) => Object.keys(module.translations ?? {}).forEach((code) => codes.add(code))); return [...codes].sort().map((code) => { const languagePackage = installed.get(code) ?? null; const catalogCount = modules.filter((module) => Boolean(module.translations?.[code])).length; return { code, package: languagePackage, catalogCount, installed: Boolean(languagePackage), active: item.enabled_language_codes.includes(code), uninstallable: Boolean( languagePackage && !BUILT_IN_LANGUAGE_CODES.has(code) && code !== item.default_locale ) }; }); } function languageDraftKey(item: SystemSettingsItem): string { return JSON.stringify({ default_locale: item.default_locale, available_languages: item.available_languages, enabled_language_codes: item.enabled_language_codes }); }