/** * @file Example demonstrating runtime i18n configuration for the Node Editor */ import * as React from "react"; import { NodeEditor } from "../../../../../NodeEditor"; import { enMessages } from "../../../../../i18n/en"; import type { I18nDictionaries, I18nMessages, Locale } from "../../../../../i18n/types"; import type { NodeEditorData } from "../../../../../types/core"; import { StandardNodeDefinition } from "../../../../../node-definitions/standard"; import { asNodeDefinition } from "../../../../../types/NodeDefinition"; import classes from "./I18nPlaygroundExample.module.css"; const playgroundInitialData: NodeEditorData = { nodes: { "welcome-node": { id: "welcome-node", type: "standard", position: { x: 160, y: 140 }, size: { width: 240, height: 160 }, data: { title: "Locale Playground", content: "Switch locales and overrides to preview translation changes live.", }, }, "workflow-node": { id: "workflow-node", type: "standard", position: { x: 520, y: 240 }, size: { width: 240, height: 160 }, data: { title: "Workflow Step", content: "Customize labels like “Add Node” or the inspector title.", }, }, }, connections: { "welcome-to-workflow": { id: "welcome-to-workflow", fromNodeId: "welcome-node", fromPortId: "output", toNodeId: "workflow-node", toPortId: "input", }, }, }; type I18nOverrideKey = | "addNode" | "inspectorTitle" | "statusSelection" | "inspectorTabProperties" | "alignmentTitle" | "alignmentSelectPrompt" | "alignmentCountLabel"; const overrideLabels: Record = { addNode: "Add node action label", inspectorTitle: "Inspector panel title", statusSelection: "Status bar selection label", inspectorTabProperties: "Inspector properties tab label", alignmentTitle: "Alignment section title", alignmentSelectPrompt: "Alignment prompt when disabled", alignmentCountLabel: "Alignment count label", }; const overrideKeys: readonly I18nOverrideKey[] = [ "addNode", "inspectorTitle", "statusSelection", "inspectorTabProperties", "alignmentTitle", "alignmentSelectPrompt", "alignmentCountLabel", ] as const; const SUPPORTED_LOCALE_OPTIONS = [ { value: "en", label: "English" }, { value: "ja", label: "日本語" }, { value: "zh", label: "中文 (繁體)" }, { value: "zh-CN", label: "中文 (简体)" }, { value: "ko", label: "한국어" }, { value: "es", label: "Español" }, { value: "fr", label: "Français" }, { value: "de", label: "Deutsch" }, ] as const satisfies ReadonlyArray<{ value: Locale; label: string }>; const LOCALE_DICTIONARY_CODES = ["ja", "zh", "zh-CN", "ko", "es", "fr", "de"] as const; type LocaleWithDictionary = (typeof LOCALE_DICTIONARY_CODES)[number]; type LocaleDictionaryLoader = () => Promise; const LOCALE_DICTIONARY_LOADERS: Record = { ja: async () => (await import("../../../../../i18n/ja")).jaMessages, zh: async () => (await import("../../../../../i18n/zh")).zhMessages, "zh-CN": async () => (await import("../../../../../i18n/zh-cn")).zhCNMessages, ko: async () => (await import("../../../../../i18n/ko")).koMessages, es: async () => (await import("../../../../../i18n/es")).esMessages, fr: async () => (await import("../../../../../i18n/fr")).frMessages, de: async () => (await import("../../../../../i18n/de")).deMessages, } as const; const hasDictionaryLoader = (locale: Locale): locale is LocaleWithDictionary => { return (LOCALE_DICTIONARY_CODES as readonly string[]).includes(locale as LocaleWithDictionary); }; /** * Renders the internationalization playground example with runtime configuration controls. */ export function I18nPlaygroundExample(): React.ReactElement { const supportedLocaleOptions = React.useMemo(() => SUPPORTED_LOCALE_OPTIONS.slice(), []); const [dictionaries, setDictionaries] = React.useState({ en: enMessages }); const [locale, setLocale] = React.useState("en"); const [fallbackLocale, setFallbackLocale] = React.useState("en"); const [overrideLocale, setOverrideLocale] = React.useState("ja"); const [messageOverrides, setMessageOverrides] = React.useState>>>({}); React.useEffect(() => { const localesToEnsure = Array.from(new Set([locale, fallbackLocale, overrideLocale].filter(Boolean) as Locale[])); let cancelled = false; const ensureLocaleDictionary = async (targetLocale: Locale) => { if (targetLocale === "en") { return; } if (dictionaries[targetLocale]) { return; } if (!hasDictionaryLoader(targetLocale)) { return; } const loadDictionary = LOCALE_DICTIONARY_LOADERS[targetLocale]; const messages = await loadDictionary(); if (cancelled) { return; } setDictionaries((prev) => { if (prev[targetLocale]) { return prev; } return { ...prev, [targetLocale]: messages, }; }); }; localesToEnsure.forEach((targetLocale) => { void ensureLocaleDictionary(targetLocale); }); return () => { cancelled = true; }; }, [dictionaries, fallbackLocale, locale, overrideLocale]); const resolveDictionary = React.useCallback( (targetLocale: Locale): I18nMessages => { const fallbackMessages = (dictionaries[fallbackLocale] ?? dictionaries.en ?? enMessages) as I18nMessages; const baseMessages = (dictionaries[targetLocale] ?? fallbackMessages) as I18nMessages; const overrides = messageOverrides[targetLocale]; if (!overrides) { return baseMessages; } return { ...baseMessages, ...overrides, } as I18nMessages; }, [dictionaries, fallbackLocale, messageOverrides], ); const overridePreviewMessages = React.useMemo( () => resolveDictionary(overrideLocale), [overrideLocale, resolveDictionary], ); const editorDictionaries = React.useMemo(() => { const result: I18nDictionaries = {}; (Object.entries(dictionaries) as Array<[Locale, I18nMessages]>).forEach(([key, value]) => { if (key === "en") { return; } result[key] = value; }); return result; }, [dictionaries]); const handleOverrideChange = React.useCallback( (key: I18nOverrideKey, value: string) => { setMessageOverrides((prev) => { const overridesForLocale = { ...(prev[overrideLocale] ?? {}), } as Partial; if (value.trim() === "") { delete overridesForLocale[key]; } else { overridesForLocale[key] = value; } const nextOverrides = { ...prev, } as Partial>>; if (Object.keys(overridesForLocale).length === 0) { delete nextOverrides[overrideLocale]; return nextOverrides; } return { ...nextOverrides, [overrideLocale]: overridesForLocale, }; }); }, [overrideLocale], ); return (
); }