@_linked/translation 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/authorization.js +8 -2
- package/lib/esm/authorization.js.map +1 -1
- package/lib/esm/exchange.js +12 -2
- package/lib/esm/exchange.js.map +1 -1
- package/lib/esm/mt.js +6 -1
- package/lib/esm/mt.js.map +1 -1
- package/lib/esm/react.js +6 -1
- package/lib/esm/react.js.map +1 -1
- package/package.json +2 -2
- package/src/authorization.ts +9 -2
- package/src/exchange.ts +11 -2
- package/src/mt.ts +6 -1
- package/src/react.tsx +6 -1
package/lib/esm/authorization.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { sharedState } from '@_linked/core/utils/sharedState';
|
|
2
|
+
// On globalThis so a second copy of this module sees the resolver the host configured on the
|
|
3
|
+
// first; a copy that cannot see it would deny every author.
|
|
4
|
+
const state = sharedState('@_linked/translation', 'authorization', () => ({
|
|
5
|
+
resolver: undefined,
|
|
6
|
+
}));
|
|
2
7
|
/**
|
|
3
8
|
* Host hook: the host decides who may read and author an app's translations
|
|
4
9
|
* (for example by evaluating its own translator assignments and project roles).
|
|
@@ -8,13 +13,14 @@ let resolver;
|
|
|
8
13
|
* public runtime fetch (`getMessages`) keeps working.
|
|
9
14
|
*/
|
|
10
15
|
export function configureTranslationAuthorization(next) {
|
|
11
|
-
resolver = next;
|
|
16
|
+
state.resolver = next;
|
|
12
17
|
}
|
|
13
18
|
/**
|
|
14
19
|
* Whether the host's resolver grants `request`. Denies when no resolver is
|
|
15
20
|
* configured, and when the resolver throws or answers anything but `true`.
|
|
16
21
|
*/
|
|
17
22
|
export async function canAuthorTranslation(request) {
|
|
23
|
+
const resolver = state.resolver;
|
|
18
24
|
if (!resolver)
|
|
19
25
|
return false;
|
|
20
26
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization.js","sourceRoot":"","sources":["../../src/authorization.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"authorization.js","sourceRoot":"","sources":["../../src/authorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAoB9D,6FAA6F;AAC7F,4DAA4D;AAC5D,MAAM,KAAK,GAAG,WAAW,CAAC,sBAAsB,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IACxE,QAAQ,EAAE,SAAyD;CACpE,CAAC,CAAC,CAAC;AAEJ;;;;;;;GAOG;AACH,MAAM,UAAU,iCAAiC,CAC/C,IAAuC;IAEvC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAwC;IAExC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAChC,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,uDAAuD,EAAE,KAAK,CAAC,CAAC;QAC7E,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC","sourcesContent":["import { sharedState } from '@_linked/core/utils/sharedState';\n\nexport type TranslationAuthoringAction =\n | 'manage'\n | 'review'\n | 'propose'\n | 'run-mt'\n | 'read';\n\nexport interface TranslationAuthorizationRequest {\n appId: string;\n actorWebId: string;\n action: TranslationAuthoringAction;\n language?: string;\n}\n\nexport type TranslationAuthorizationResolver = (\n request: TranslationAuthorizationRequest,\n) => Promise<boolean>;\n\n// On globalThis so a second copy of this module sees the resolver the host configured on the\n// first; a copy that cannot see it would deny every author.\nconst state = sharedState('@_linked/translation', 'authorization', () => ({\n resolver: undefined as TranslationAuthorizationResolver | undefined,\n}));\n\n/**\n * Host hook: the host decides who may read and author an app's translations\n * (for example by evaluating its own translator assignments and project roles).\n *\n * The resolver is the only source of a \"yes\". A host that does not configure\n * one gets no authoring or read access over the translation provider; only the\n * public runtime fetch (`getMessages`) keeps working.\n */\nexport function configureTranslationAuthorization(\n next?: TranslationAuthorizationResolver,\n): void {\n state.resolver = next;\n}\n\n/**\n * Whether the host's resolver grants `request`. Denies when no resolver is\n * configured, and when the resolver throws or answers anything but `true`.\n */\nexport async function canAuthorTranslation(\n request: TranslationAuthorizationRequest,\n): Promise<boolean> {\n const resolver = state.resolver;\n if (!resolver) return false;\n try {\n return (await resolver(request)) === true;\n } catch (error) {\n console.warn('[translation] authorization resolver failed; denying.', error);\n return false;\n }\n}\n"]}
|
package/lib/esm/exchange.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sharedState } from '@_linked/core/utils/sharedState';
|
|
1
2
|
import { canonicalLanguageTag } from './key-version.js';
|
|
2
3
|
const EXCHANGE_STATES = new Set([
|
|
3
4
|
'untranslated',
|
|
@@ -5,7 +6,11 @@ const EXCHANGE_STATES = new Set([
|
|
|
5
6
|
'reviewed',
|
|
6
7
|
'stale',
|
|
7
8
|
]);
|
|
8
|
-
|
|
9
|
+
// On globalThis so an adapter the host registers on one copy of this module is found by
|
|
10
|
+
// another copy.
|
|
11
|
+
const { adapters } = sharedState('@_linked/translation', 'exchange', () => ({
|
|
12
|
+
adapters: new Map(),
|
|
13
|
+
}));
|
|
9
14
|
function record(value, label) {
|
|
10
15
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
11
16
|
throw new Error(`${label} must be an object.`);
|
|
@@ -205,7 +210,12 @@ export function isConfirmedBuildDeclaration(declaration) {
|
|
|
205
210
|
declaration.authoritative !== false);
|
|
206
211
|
}
|
|
207
212
|
export function registerTranslationFormatAdapter(adapter) {
|
|
208
|
-
|
|
213
|
+
const registered = adapters.get(adapter.format);
|
|
214
|
+
// The registry is process-wide, so the same adapter registered again (by a second copy of
|
|
215
|
+
// the host module that registers it) is not a conflict; a different adapter for the format is.
|
|
216
|
+
if (registered === adapter)
|
|
217
|
+
return;
|
|
218
|
+
if (registered) {
|
|
209
219
|
throw new Error(`Translation format adapter "${adapter.format}" is already registered.`);
|
|
210
220
|
}
|
|
211
221
|
adapters.set(adapter.format, adapter);
|
package/lib/esm/exchange.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../../src/exchange.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageFormat } from './core/messages.js';\nimport { canonicalLanguageTag } from './key-version.js';\nimport type { TranslationState } from './records.js';\n\nexport type TranslationExchangeKind = 'ui' | 'semantic';\nexport type TranslationDeclarationKind =\n | TranslationExchangeKind\n | 'content';\nexport type TranslationDeclarationStatus = 'confirmed' | 'pending';\n\n/**\n * The build-facing subset of the later multi-source discovery contract.\n * Static extraction omits the optional fields and retains its existing\n * confirmed/simple behavior.\n */\nexport interface TranslationBuildDeclaration {\n key: string;\n sourceText: string;\n format?: MessageFormat;\n kind?: TranslationDeclarationKind;\n status?: TranslationDeclarationStatus;\n authoritative?: boolean;\n namespace?: string;\n file?: string;\n line?: number;\n}\n\nexport interface TranslationExchangeValue {\n text: string;\n state?: TranslationState;\n note?: string;\n}\n\nexport interface TranslationExchangeEntry {\n key: string;\n namespace?: string;\n kind: TranslationExchangeKind;\n sourceText: string;\n keyVersionId: string;\n sourceHash: string;\n contractHash: string;\n argumentSignature: string;\n description?: string;\n format: MessageFormat;\n translations: Record<string, TranslationExchangeValue>;\n}\n\nexport interface TranslationExchangeDocument {\n schemaVersion: 1;\n appId?: string;\n branchId?: string;\n sourceLanguage: string;\n targetLanguages: string[];\n exportedAt?: string;\n revisionWatermark?: string;\n contractSetHash?: string;\n entries: TranslationExchangeEntry[];\n}\n\nexport type ParsedTranslationExchangeEntry = Omit<\n TranslationExchangeEntry,\n | 'sourceText'\n | 'keyVersionId'\n | 'sourceHash'\n | 'contractHash'\n | 'argumentSignature'\n> & {\n sourceText?: string;\n keyVersionId?: string;\n sourceHash?: string;\n contractHash?: string;\n argumentSignature?: string;\n};\n\nexport interface ParsedTranslationExchangeDocument\n extends Omit<TranslationExchangeDocument, 'entries'> {\n entries: ParsedTranslationExchangeEntry[];\n}\n\nexport type TranslationFormat =\n | 'xliff-1.2'\n | 'xliff-2.0'\n | 'csv'\n | 'json';\n\nexport interface SerializedTranslationDocument {\n fileName: string;\n mediaType: string;\n body: Uint8Array;\n}\n\nexport interface TranslationFormatAdapter {\n readonly format: TranslationFormat;\n sniff(input: Uint8Array, fileName?: string): boolean;\n parse(\n input: Uint8Array,\n options?: unknown,\n ): Promise<ParsedTranslationExchangeDocument>;\n serialize(\n document: TranslationExchangeDocument,\n options?: unknown,\n ): Promise<SerializedTranslationDocument>;\n}\n\nexport type TranslationImportDisposition =\n | 'import'\n | 'carry-forward'\n | 'unverified-source'\n | 'conflict'\n | 'skip';\n\nexport interface TranslationImportDryRunEntry {\n key: string;\n language: string;\n disposition: TranslationImportDisposition;\n expectedKeyVersionId?: string;\n expectedUnitContentHash?: string;\n}\n\nexport interface TranslationImportDryRun {\n sessionId: string;\n documentHash: string;\n revisionWatermark: string;\n expiresAt: string;\n entries: TranslationImportDryRunEntry[];\n}\n\nconst EXCHANGE_STATES = new Set<TranslationState>([\n 'untranslated',\n 'machine',\n 'reviewed',\n 'stale',\n]);\nconst adapters = new Map<TranslationFormat, TranslationFormatAdapter>();\n\nfunction record(value: unknown, label: string): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${label} must be an object.`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction requiredString(value: unknown, label: string): string {\n if (typeof value !== 'string' || !value.trim()) {\n throw new Error(`${label} is required.`);\n }\n return value.trim();\n}\n\nfunction optionalString(value: unknown, label: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || !value.trim()) {\n throw new Error(`${label} must be a non-empty string when supplied.`);\n }\n return value.trim();\n}\n\nfunction messageFormat(value: unknown, label: string): MessageFormat {\n if (value !== 'simple' && value !== 'icu') {\n throw new Error(`${label} must be \"simple\" or \"icu\".`);\n }\n return value;\n}\n\nfunction exchangeKind(value: unknown, label: string): TranslationExchangeKind {\n if (value !== 'ui' && value !== 'semantic') {\n throw new Error(`${label} must be \"ui\" or \"semantic\".`);\n }\n return value;\n}\n\nfunction canonicalLanguages(value: unknown, label: string): string[] {\n if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);\n const canonical: string[] = [];\n const seen = new Set<string>();\n for (const item of value) {\n if (typeof item !== 'string') {\n throw new Error(`${label} must contain only BCP-47 strings.`);\n }\n const language = canonicalLanguageTag(item);\n if (!seen.has(language)) {\n seen.add(language);\n canonical.push(language);\n }\n }\n return canonical;\n}\n\nfunction normalizeTranslations(\n value: unknown,\n label: string,\n): Record<string, TranslationExchangeValue> {\n const input = record(value, label);\n const translations: Record<string, TranslationExchangeValue> = {};\n for (const [rawLanguage, rawValue] of Object.entries(input)) {\n const language = canonicalLanguageTag(rawLanguage);\n if (translations[language]) {\n throw new Error(\n `${label} contains duplicate canonical language \"${language}\".`,\n );\n }\n const item = record(rawValue, `${label}.${rawLanguage}`);\n if (typeof item.text !== 'string') {\n throw new Error(`${label}.${rawLanguage}.text must be a string.`);\n }\n const state = item.state;\n if (state !== undefined && !EXCHANGE_STATES.has(state as TranslationState)) {\n throw new Error(`${label}.${rawLanguage}.state is invalid.`);\n }\n translations[language] = {\n text: item.text,\n ...(state ? { state: state as TranslationState } : {}),\n ...(item.note === undefined\n ? {}\n : {\n note: requiredString(\n item.note,\n `${label}.${rawLanguage}.note`,\n ),\n }),\n };\n }\n return translations;\n}\n\nfunction normalizeEntry(\n value: unknown,\n index: number,\n): ParsedTranslationExchangeEntry {\n const item = record(value, `entries[${index}]`);\n const key = requiredString(item.key, `entries[${index}].key`);\n return {\n key,\n ...(item.namespace === undefined\n ? {}\n : {\n namespace: requiredString(\n item.namespace,\n `entries[${index}].namespace`,\n ),\n }),\n kind: exchangeKind(item.kind, `entries[${index}].kind`),\n format: messageFormat(item.format, `entries[${index}].format`),\n ...(item.sourceText === undefined\n ? {}\n : {\n sourceText:\n typeof item.sourceText === 'string'\n ? item.sourceText\n : requiredString(item.sourceText, `entries[${index}].sourceText`),\n }),\n ...(item.keyVersionId === undefined\n ? {}\n : {\n keyVersionId: requiredString(\n item.keyVersionId,\n `entries[${index}].keyVersionId`,\n ),\n }),\n ...(item.sourceHash === undefined\n ? {}\n : {\n sourceHash: requiredString(\n item.sourceHash,\n `entries[${index}].sourceHash`,\n ),\n }),\n ...(item.contractHash === undefined\n ? {}\n : {\n contractHash: requiredString(\n item.contractHash,\n `entries[${index}].contractHash`,\n ),\n }),\n ...(item.argumentSignature === undefined\n ? {}\n : {\n argumentSignature: requiredString(\n item.argumentSignature,\n `entries[${index}].argumentSignature`,\n ),\n }),\n ...(item.description === undefined\n ? {}\n : {\n description: requiredString(\n item.description,\n `entries[${index}].description`,\n ),\n }),\n translations: normalizeTranslations(\n item.translations,\n `entries[${index}].translations`,\n ),\n };\n}\n\n/**\n * Normalize adapter output without inventing evidence missing from a foreign\n * file. Import disposition handles those entries as unverified later.\n */\nexport function normalizeParsedTranslationExchangeDocument(\n value: unknown,\n): ParsedTranslationExchangeDocument {\n const input = record(value, 'Translation exchange document');\n if (input.schemaVersion !== 1) {\n throw new Error('Translation exchange schemaVersion must be 1.');\n }\n const sourceLanguage = canonicalLanguageTag(\n requiredString(input.sourceLanguage, 'sourceLanguage'),\n );\n const targetLanguages = canonicalLanguages(\n input.targetLanguages,\n 'targetLanguages',\n );\n if (!Array.isArray(input.entries)) {\n throw new Error('entries must be an array.');\n }\n const entries = input.entries.map(normalizeEntry);\n const identities = new Set<string>();\n for (const entry of entries) {\n for (const language of Object.keys(entry.translations)) {\n const identity = `${entry.namespace ?? ''}\\u0000${entry.key}\\u0000${language}`;\n if (identities.has(identity)) {\n throw new Error(\n `Duplicate translation entry for \"${entry.key}\" in ${language}.`,\n );\n }\n identities.add(identity);\n }\n }\n return {\n schemaVersion: 1,\n ...(input.appId === undefined\n ? {}\n : { appId: requiredString(input.appId, 'appId') }),\n ...(input.branchId === undefined\n ? {}\n : { branchId: requiredString(input.branchId, 'branchId') }),\n sourceLanguage,\n targetLanguages,\n ...(input.exportedAt === undefined\n ? {}\n : { exportedAt: requiredString(input.exportedAt, 'exportedAt') }),\n ...(input.revisionWatermark === undefined\n ? {}\n : {\n revisionWatermark: requiredString(\n input.revisionWatermark,\n 'revisionWatermark',\n ),\n }),\n ...(input.contractSetHash === undefined\n ? {}\n : {\n contractSetHash: requiredString(\n input.contractSetHash,\n 'contractSetHash',\n ),\n }),\n entries,\n };\n}\n\n/** Validate the stronger, version-evidenced document this package's own exports carry. */\nexport function validateTranslationExchangeDocument(\n value: unknown,\n): TranslationExchangeDocument {\n const parsed = normalizeParsedTranslationExchangeDocument(value);\n const entries = parsed.entries.map((entry, index): TranslationExchangeEntry => ({\n ...entry,\n sourceText:\n entry.sourceText ??\n requiredString(undefined, `entries[${index}].sourceText`),\n keyVersionId:\n entry.keyVersionId ??\n requiredString(undefined, `entries[${index}].keyVersionId`),\n sourceHash:\n entry.sourceHash ??\n requiredString(undefined, `entries[${index}].sourceHash`),\n contractHash:\n entry.contractHash ??\n requiredString(undefined, `entries[${index}].contractHash`),\n argumentSignature:\n entry.argumentSignature ??\n requiredString(undefined, `entries[${index}].argumentSignature`),\n }));\n return { ...parsed, entries };\n}\n\nexport function isConfirmedBuildDeclaration(\n declaration: TranslationBuildDeclaration,\n): boolean {\n return (\n (declaration.kind ?? 'ui') !== 'content' &&\n (declaration.status ?? 'confirmed') === 'confirmed' &&\n declaration.authoritative !== false\n );\n}\n\nexport function registerTranslationFormatAdapter(\n adapter: TranslationFormatAdapter,\n): void {\n if (adapters.has(adapter.format)) {\n throw new Error(\n `Translation format adapter \"${adapter.format}\" is already registered.`,\n );\n }\n adapters.set(adapter.format, adapter);\n}\n\nexport function getTranslationFormatAdapter(\n format: TranslationFormat,\n): TranslationFormatAdapter | undefined {\n return adapters.get(format);\n}\n\nexport function listTranslationFormatAdapters(): TranslationFormatAdapter[] {\n return [...adapters.values()].sort((left, right) =>\n left.format.localeCompare(right.format),\n );\n}\n\n/** Test/host reset hook; adapters are registered explicitly by the host. */\nexport function clearTranslationFormatAdapters(): void {\n adapters.clear();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../../src/exchange.ts"],"names":[],"mappings":"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","sourcesContent":["import { sharedState } from '@_linked/core/utils/sharedState';\nimport type { MessageFormat } from './core/messages.js';\nimport { canonicalLanguageTag } from './key-version.js';\nimport type { TranslationState } from './records.js';\n\nexport type TranslationExchangeKind = 'ui' | 'semantic';\nexport type TranslationDeclarationKind =\n | TranslationExchangeKind\n | 'content';\nexport type TranslationDeclarationStatus = 'confirmed' | 'pending';\n\n/**\n * The build-facing subset of the later multi-source discovery contract.\n * Static extraction omits the optional fields and retains its existing\n * confirmed/simple behavior.\n */\nexport interface TranslationBuildDeclaration {\n key: string;\n sourceText: string;\n format?: MessageFormat;\n kind?: TranslationDeclarationKind;\n status?: TranslationDeclarationStatus;\n authoritative?: boolean;\n namespace?: string;\n file?: string;\n line?: number;\n}\n\nexport interface TranslationExchangeValue {\n text: string;\n state?: TranslationState;\n note?: string;\n}\n\nexport interface TranslationExchangeEntry {\n key: string;\n namespace?: string;\n kind: TranslationExchangeKind;\n sourceText: string;\n keyVersionId: string;\n sourceHash: string;\n contractHash: string;\n argumentSignature: string;\n description?: string;\n format: MessageFormat;\n translations: Record<string, TranslationExchangeValue>;\n}\n\nexport interface TranslationExchangeDocument {\n schemaVersion: 1;\n appId?: string;\n branchId?: string;\n sourceLanguage: string;\n targetLanguages: string[];\n exportedAt?: string;\n revisionWatermark?: string;\n contractSetHash?: string;\n entries: TranslationExchangeEntry[];\n}\n\nexport type ParsedTranslationExchangeEntry = Omit<\n TranslationExchangeEntry,\n | 'sourceText'\n | 'keyVersionId'\n | 'sourceHash'\n | 'contractHash'\n | 'argumentSignature'\n> & {\n sourceText?: string;\n keyVersionId?: string;\n sourceHash?: string;\n contractHash?: string;\n argumentSignature?: string;\n};\n\nexport interface ParsedTranslationExchangeDocument\n extends Omit<TranslationExchangeDocument, 'entries'> {\n entries: ParsedTranslationExchangeEntry[];\n}\n\nexport type TranslationFormat =\n | 'xliff-1.2'\n | 'xliff-2.0'\n | 'csv'\n | 'json';\n\nexport interface SerializedTranslationDocument {\n fileName: string;\n mediaType: string;\n body: Uint8Array;\n}\n\nexport interface TranslationFormatAdapter {\n readonly format: TranslationFormat;\n sniff(input: Uint8Array, fileName?: string): boolean;\n parse(\n input: Uint8Array,\n options?: unknown,\n ): Promise<ParsedTranslationExchangeDocument>;\n serialize(\n document: TranslationExchangeDocument,\n options?: unknown,\n ): Promise<SerializedTranslationDocument>;\n}\n\nexport type TranslationImportDisposition =\n | 'import'\n | 'carry-forward'\n | 'unverified-source'\n | 'conflict'\n | 'skip';\n\nexport interface TranslationImportDryRunEntry {\n key: string;\n language: string;\n disposition: TranslationImportDisposition;\n expectedKeyVersionId?: string;\n expectedUnitContentHash?: string;\n}\n\nexport interface TranslationImportDryRun {\n sessionId: string;\n documentHash: string;\n revisionWatermark: string;\n expiresAt: string;\n entries: TranslationImportDryRunEntry[];\n}\n\nconst EXCHANGE_STATES = new Set<TranslationState>([\n 'untranslated',\n 'machine',\n 'reviewed',\n 'stale',\n]);\n// On globalThis so an adapter the host registers on one copy of this module is found by\n// another copy.\nconst { adapters } = sharedState('@_linked/translation', 'exchange', () => ({\n adapters: new Map<TranslationFormat, TranslationFormatAdapter>(),\n}));\n\nfunction record(value: unknown, label: string): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${label} must be an object.`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction requiredString(value: unknown, label: string): string {\n if (typeof value !== 'string' || !value.trim()) {\n throw new Error(`${label} is required.`);\n }\n return value.trim();\n}\n\nfunction optionalString(value: unknown, label: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || !value.trim()) {\n throw new Error(`${label} must be a non-empty string when supplied.`);\n }\n return value.trim();\n}\n\nfunction messageFormat(value: unknown, label: string): MessageFormat {\n if (value !== 'simple' && value !== 'icu') {\n throw new Error(`${label} must be \"simple\" or \"icu\".`);\n }\n return value;\n}\n\nfunction exchangeKind(value: unknown, label: string): TranslationExchangeKind {\n if (value !== 'ui' && value !== 'semantic') {\n throw new Error(`${label} must be \"ui\" or \"semantic\".`);\n }\n return value;\n}\n\nfunction canonicalLanguages(value: unknown, label: string): string[] {\n if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);\n const canonical: string[] = [];\n const seen = new Set<string>();\n for (const item of value) {\n if (typeof item !== 'string') {\n throw new Error(`${label} must contain only BCP-47 strings.`);\n }\n const language = canonicalLanguageTag(item);\n if (!seen.has(language)) {\n seen.add(language);\n canonical.push(language);\n }\n }\n return canonical;\n}\n\nfunction normalizeTranslations(\n value: unknown,\n label: string,\n): Record<string, TranslationExchangeValue> {\n const input = record(value, label);\n const translations: Record<string, TranslationExchangeValue> = {};\n for (const [rawLanguage, rawValue] of Object.entries(input)) {\n const language = canonicalLanguageTag(rawLanguage);\n if (translations[language]) {\n throw new Error(\n `${label} contains duplicate canonical language \"${language}\".`,\n );\n }\n const item = record(rawValue, `${label}.${rawLanguage}`);\n if (typeof item.text !== 'string') {\n throw new Error(`${label}.${rawLanguage}.text must be a string.`);\n }\n const state = item.state;\n if (state !== undefined && !EXCHANGE_STATES.has(state as TranslationState)) {\n throw new Error(`${label}.${rawLanguage}.state is invalid.`);\n }\n translations[language] = {\n text: item.text,\n ...(state ? { state: state as TranslationState } : {}),\n ...(item.note === undefined\n ? {}\n : {\n note: requiredString(\n item.note,\n `${label}.${rawLanguage}.note`,\n ),\n }),\n };\n }\n return translations;\n}\n\nfunction normalizeEntry(\n value: unknown,\n index: number,\n): ParsedTranslationExchangeEntry {\n const item = record(value, `entries[${index}]`);\n const key = requiredString(item.key, `entries[${index}].key`);\n return {\n key,\n ...(item.namespace === undefined\n ? {}\n : {\n namespace: requiredString(\n item.namespace,\n `entries[${index}].namespace`,\n ),\n }),\n kind: exchangeKind(item.kind, `entries[${index}].kind`),\n format: messageFormat(item.format, `entries[${index}].format`),\n ...(item.sourceText === undefined\n ? {}\n : {\n sourceText:\n typeof item.sourceText === 'string'\n ? item.sourceText\n : requiredString(item.sourceText, `entries[${index}].sourceText`),\n }),\n ...(item.keyVersionId === undefined\n ? {}\n : {\n keyVersionId: requiredString(\n item.keyVersionId,\n `entries[${index}].keyVersionId`,\n ),\n }),\n ...(item.sourceHash === undefined\n ? {}\n : {\n sourceHash: requiredString(\n item.sourceHash,\n `entries[${index}].sourceHash`,\n ),\n }),\n ...(item.contractHash === undefined\n ? {}\n : {\n contractHash: requiredString(\n item.contractHash,\n `entries[${index}].contractHash`,\n ),\n }),\n ...(item.argumentSignature === undefined\n ? {}\n : {\n argumentSignature: requiredString(\n item.argumentSignature,\n `entries[${index}].argumentSignature`,\n ),\n }),\n ...(item.description === undefined\n ? {}\n : {\n description: requiredString(\n item.description,\n `entries[${index}].description`,\n ),\n }),\n translations: normalizeTranslations(\n item.translations,\n `entries[${index}].translations`,\n ),\n };\n}\n\n/**\n * Normalize adapter output without inventing evidence missing from a foreign\n * file. Import disposition handles those entries as unverified later.\n */\nexport function normalizeParsedTranslationExchangeDocument(\n value: unknown,\n): ParsedTranslationExchangeDocument {\n const input = record(value, 'Translation exchange document');\n if (input.schemaVersion !== 1) {\n throw new Error('Translation exchange schemaVersion must be 1.');\n }\n const sourceLanguage = canonicalLanguageTag(\n requiredString(input.sourceLanguage, 'sourceLanguage'),\n );\n const targetLanguages = canonicalLanguages(\n input.targetLanguages,\n 'targetLanguages',\n );\n if (!Array.isArray(input.entries)) {\n throw new Error('entries must be an array.');\n }\n const entries = input.entries.map(normalizeEntry);\n const identities = new Set<string>();\n for (const entry of entries) {\n for (const language of Object.keys(entry.translations)) {\n const identity = `${entry.namespace ?? ''}\\u0000${entry.key}\\u0000${language}`;\n if (identities.has(identity)) {\n throw new Error(\n `Duplicate translation entry for \"${entry.key}\" in ${language}.`,\n );\n }\n identities.add(identity);\n }\n }\n return {\n schemaVersion: 1,\n ...(input.appId === undefined\n ? {}\n : { appId: requiredString(input.appId, 'appId') }),\n ...(input.branchId === undefined\n ? {}\n : { branchId: requiredString(input.branchId, 'branchId') }),\n sourceLanguage,\n targetLanguages,\n ...(input.exportedAt === undefined\n ? {}\n : { exportedAt: requiredString(input.exportedAt, 'exportedAt') }),\n ...(input.revisionWatermark === undefined\n ? {}\n : {\n revisionWatermark: requiredString(\n input.revisionWatermark,\n 'revisionWatermark',\n ),\n }),\n ...(input.contractSetHash === undefined\n ? {}\n : {\n contractSetHash: requiredString(\n input.contractSetHash,\n 'contractSetHash',\n ),\n }),\n entries,\n };\n}\n\n/** Validate the stronger, version-evidenced document this package's own exports carry. */\nexport function validateTranslationExchangeDocument(\n value: unknown,\n): TranslationExchangeDocument {\n const parsed = normalizeParsedTranslationExchangeDocument(value);\n const entries = parsed.entries.map((entry, index): TranslationExchangeEntry => ({\n ...entry,\n sourceText:\n entry.sourceText ??\n requiredString(undefined, `entries[${index}].sourceText`),\n keyVersionId:\n entry.keyVersionId ??\n requiredString(undefined, `entries[${index}].keyVersionId`),\n sourceHash:\n entry.sourceHash ??\n requiredString(undefined, `entries[${index}].sourceHash`),\n contractHash:\n entry.contractHash ??\n requiredString(undefined, `entries[${index}].contractHash`),\n argumentSignature:\n entry.argumentSignature ??\n requiredString(undefined, `entries[${index}].argumentSignature`),\n }));\n return { ...parsed, entries };\n}\n\nexport function isConfirmedBuildDeclaration(\n declaration: TranslationBuildDeclaration,\n): boolean {\n return (\n (declaration.kind ?? 'ui') !== 'content' &&\n (declaration.status ?? 'confirmed') === 'confirmed' &&\n declaration.authoritative !== false\n );\n}\n\nexport function registerTranslationFormatAdapter(\n adapter: TranslationFormatAdapter,\n): void {\n const registered = adapters.get(adapter.format);\n // The registry is process-wide, so the same adapter registered again (by a second copy of\n // the host module that registers it) is not a conflict; a different adapter for the format is.\n if (registered === adapter) return;\n if (registered) {\n throw new Error(\n `Translation format adapter \"${adapter.format}\" is already registered.`,\n );\n }\n adapters.set(adapter.format, adapter);\n}\n\nexport function getTranslationFormatAdapter(\n format: TranslationFormat,\n): TranslationFormatAdapter | undefined {\n return adapters.get(format);\n}\n\nexport function listTranslationFormatAdapters(): TranslationFormatAdapter[] {\n return [...adapters.values()].sort((left, right) =>\n left.format.localeCompare(right.format),\n );\n}\n\n/** Test/host reset hook; adapters are registered explicitly by the host. */\nexport function clearTranslationFormatAdapters(): void {\n adapters.clear();\n}\n"]}
|
package/lib/esm/mt.js
CHANGED
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
* SDK imports — concrete providers live with the host application and are
|
|
9
9
|
* registered at boot.
|
|
10
10
|
*/
|
|
11
|
+
import { sharedState } from '@_linked/core/utils/sharedState';
|
|
11
12
|
import { analyzeMessage, compareMessageArguments, } from './message-format.js';
|
|
12
|
-
|
|
13
|
+
// On globalThis so a provider the host registers on one copy of this module is found by
|
|
14
|
+
// another copy.
|
|
15
|
+
const { providers } = sharedState('@_linked/translation', 'mt', () => ({
|
|
16
|
+
providers: new Map(),
|
|
17
|
+
}));
|
|
13
18
|
export function registerMtProvider(provider) {
|
|
14
19
|
providers.set(provider.key, provider);
|
|
15
20
|
}
|
package/lib/esm/mt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mt.js","sourceRoot":"","sources":["../../src/mt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Machine-translation provider registry (Plan 014 AD-L / Plan 017 Slice B).\n * Providers implement one method — translate a batch of UI strings — and\n * register by key ('claude', 'deepl', …). Resolution is PER TARGET LANGUAGE\n * with a fallback chain: not every provider covers every language (DeepL has\n * no Swahili or Urdu), so the preferred provider is used where it supports\n * the target and the chain falls through otherwise. Pure module: no I/O, no\n * SDK imports — concrete providers live with the host application and are\n * registered at boot.\n */\nimport {\n analyzeMessage,\n compareMessageArguments,\n} from './message-format.js';\nimport type { MessageFormat } from './core/messages.js';\n\nexport interface MtItem {\n key: string;\n sourceText: string;\n /** Authoring context shown to the translator/model. */\n description?: string;\n format?: 'simple' | 'icu';\n}\n\nexport interface MtGlossaryEntry {\n term: string;\n /** prefer (default) = translate as `translation`; keep = do-not-translate; forbid = never use. */\n termType?: 'prefer' | 'require' | 'keep' | 'forbid';\n /** Preferred translation (prefer entries); empty/undefined = keep the term as-is. */\n translation?: string;\n /** Replacement wording (forbid entries). */\n useInstead?: string;\n /** Target language this applies to; empty = all languages. */\n language?: string;\n /** Exact-casing matching (proper nouns / brand styling). */\n caseSensitive?: boolean;\n description?: string;\n}\n\n/** Register/tone controls (Plan 017 §7.4b) — NOT sampling temperature. */\nexport interface MtStyle {\n formality?: 'formal' | 'informal';\n /** Free-text tone guidance, e.g. \"friendly, energetic, no bureaucratese\". */\n note?: string;\n}\n\n/** An established translation the provider should stay consistent with. */\nexport interface MtReference {\n sourceText: string;\n text: string;\n}\n\nexport interface MtBatchRequest {\n sourceLanguage: string;\n targetLanguage: string;\n items: MtItem[];\n glossary: MtGlossaryEntry[];\n style?: MtStyle;\n /** Translation-memory context: reviewed units for consistency (Plan 017 §7.4c). */\n references?: MtReference[];\n}\n\nexport interface MtBatchResult {\n key: string;\n text: string;\n}\n\nexport interface TranslationMtProvider {\n readonly key: string;\n /** Absent ⇒ the provider claims every language. */\n supportsLanguage?(language: string): boolean;\n translateBatch(request: MtBatchRequest): Promise<MtBatchResult[]>;\n}\n\nconst providers = new Map<string, TranslationMtProvider>();\n\nexport function registerMtProvider(provider: TranslationMtProvider): void {\n providers.set(provider.key, provider);\n}\n\nexport function getMtProvider(key: string): TranslationMtProvider | undefined {\n return providers.get(key);\n}\n\nexport function listMtProviders(): TranslationMtProvider[] {\n return [...providers.values()];\n}\n\n/** Test seam. */\nexport function clearMtProviders(): void {\n providers.clear();\n}\n\n/**\n * Ordered provider chain for one target language: the preferred provider\n * first when it exists AND supports the language, then every other provider\n * that supports it, registration order.\n */\nexport function resolveMtProviders(\n targetLanguage: string,\n preferred?: string,\n): TranslationMtProvider[] {\n const supports = (p: TranslationMtProvider) =>\n !p.supportsLanguage || p.supportsLanguage(targetLanguage);\n const chain = listMtProviders().filter(supports);\n if (!preferred) return chain;\n const first = chain.find((p) => p.key === preferred);\n if (!first) return chain;\n return [first, ...chain.filter((p) => p !== first)];\n}\n\n/** Unique `{placeholder}` tokens in a message, sorted for set comparison. */\nexport function extractPlaceholders(\n text: string,\n format: MessageFormat = 'simple',\n): string[] {\n return analyzeMessage(text, format).arguments.map(({ name }) => name);\n}\n\n/**\n * A translation is only usable if it preserves the source's placeholder SET\n * exactly — a dropped or renamed `{param}` breaks interpolation at runtime.\n */\nexport function placeholdersIntact(\n source: string,\n translated: string,\n format: MessageFormat = 'simple',\n): boolean {\n return compareMessageArguments(source, translated, format).valid;\n}\n\n/** Glossary entries applying to one target language (global + language-specific). */\nexport function glossaryForLanguage(\n glossary: MtGlossaryEntry[],\n language: string,\n): MtGlossaryEntry[] {\n return glossary.filter((g) => !g.language || g.language === language);\n}\n\nexport function chunkItems<T>(items: T[], size: number): T[][] {\n if (size < 1) throw new Error('chunk size must be >= 1');\n const chunks: T[][] = [];\n for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size));\n return chunks;\n}\n\n/**\n * Resolve the formality for one language from the encoded config entries\n * (\"formal\" = app default; \"de:informal\" = per-language override; override wins).\n */\nexport function formalityForLanguage(\n entries: string[],\n language: string,\n): 'formal' | 'informal' | undefined {\n let result: 'formal' | 'informal' | undefined;\n for (const entry of entries ?? []) {\n const i = entry.indexOf(':');\n const value = i === -1 ? entry : entry.slice(i + 1);\n if (value !== 'formal' && value !== 'informal') continue;\n if (i === -1) {\n if (!result) result = value;\n } else if (entry.slice(0, i) === language) {\n return value;\n }\n }\n return result;\n}\n\n// ── Consistency (Plan 017 §7.4c) ────────────────────────────────────────\n\n/** Minimal entry view the consistency helpers need (client-safe). */\nexport interface ConsistencyEntry {\n key: string;\n namespace?: string;\n sourceText: string;\n format?: 'simple' | 'icu';\n units: Record<string, { text: string; state: string } | undefined>;\n}\n\n/** Identical English source translated DIFFERENTLY within one language. */\nexport interface ConsistencyConflict {\n sourceText: string;\n language: string;\n /** One entry per distinct translation: the text + the keys carrying it. */\n variants: Array<{ text: string; keys: string[] }>;\n}\n\n/**\n * Find keys whose IDENTICAL source text has diverging translations in a\n * language — the core \"same words, same translation\" consistency check.\n * Empty/untranslated units are ignored; whitespace-trimmed comparison.\n */\nexport function findInconsistentTranslations(\n entries: ConsistencyEntry[],\n language: string,\n): ConsistencyConflict[] {\n const bySource = new Map<string, Map<string, string[]>>();\n for (const entry of entries) {\n const source = entry.sourceText.trim();\n const text = entry.units[language]?.text?.trim();\n if (!source || !text) continue;\n const variants = bySource.get(source) ?? new Map<string, string[]>();\n variants.set(text, [...(variants.get(text) ?? []), entry.key]);\n bySource.set(source, variants);\n }\n const conflicts: ConsistencyConflict[] = [];\n for (const [sourceText, variants] of bySource) {\n if (variants.size < 2) continue;\n conflicts.push({\n sourceText,\n language,\n variants: [...variants].map(([text, keys]) => ({ text, keys: keys.sort() })),\n });\n }\n return conflicts.sort((a, b) => a.sourceText.localeCompare(b.sourceText));\n}\n\n/**\n * Pick reviewed units as translation-memory REFERENCES for an MT batch:\n * same-namespace reviewed translations first (shared vocabulary), then the\n * shortest reviewed units overall (vocabulary-dense). Deterministic.\n */\nexport function pickReferenceUnits(\n entries: ConsistencyEntry[],\n language: string,\n batchNamespaces: string[],\n max = 20,\n): MtReference[] {\n const namespaces = new Set(batchNamespaces);\n const reviewed = entries\n .map((entry) => ({\n namespace: entry.namespace ?? '',\n sourceText: entry.sourceText.trim(),\n unit: entry.units[language],\n }))\n .filter(\n (row) =>\n row.sourceText &&\n row.unit?.state === 'reviewed' &&\n row.unit.text.trim(),\n );\n const score = (row: (typeof reviewed)[number]) =>\n (namespaces.has(row.namespace) ? 0 : 100000) + row.sourceText.length;\n return reviewed\n .sort((a, b) => score(a) - score(b) || a.sourceText.localeCompare(b.sourceText))\n .slice(0, max)\n .map((row) => ({ sourceText: row.sourceText, text: row.unit!.text.trim() }));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mt.js","sourceRoot":"","sources":["../../src/mt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Machine-translation provider registry (Plan 014 AD-L / Plan 017 Slice B).\n * Providers implement one method — translate a batch of UI strings — and\n * register by key ('claude', 'deepl', …). Resolution is PER TARGET LANGUAGE\n * with a fallback chain: not every provider covers every language (DeepL has\n * no Swahili or Urdu), so the preferred provider is used where it supports\n * the target and the chain falls through otherwise. Pure module: no I/O, no\n * SDK imports — concrete providers live with the host application and are\n * registered at boot.\n */\nimport { sharedState } from '@_linked/core/utils/sharedState';\nimport {\n analyzeMessage,\n compareMessageArguments,\n} from './message-format.js';\nimport type { MessageFormat } from './core/messages.js';\n\nexport interface MtItem {\n key: string;\n sourceText: string;\n /** Authoring context shown to the translator/model. */\n description?: string;\n format?: 'simple' | 'icu';\n}\n\nexport interface MtGlossaryEntry {\n term: string;\n /** prefer (default) = translate as `translation`; keep = do-not-translate; forbid = never use. */\n termType?: 'prefer' | 'require' | 'keep' | 'forbid';\n /** Preferred translation (prefer entries); empty/undefined = keep the term as-is. */\n translation?: string;\n /** Replacement wording (forbid entries). */\n useInstead?: string;\n /** Target language this applies to; empty = all languages. */\n language?: string;\n /** Exact-casing matching (proper nouns / brand styling). */\n caseSensitive?: boolean;\n description?: string;\n}\n\n/** Register/tone controls (Plan 017 §7.4b) — NOT sampling temperature. */\nexport interface MtStyle {\n formality?: 'formal' | 'informal';\n /** Free-text tone guidance, e.g. \"friendly, energetic, no bureaucratese\". */\n note?: string;\n}\n\n/** An established translation the provider should stay consistent with. */\nexport interface MtReference {\n sourceText: string;\n text: string;\n}\n\nexport interface MtBatchRequest {\n sourceLanguage: string;\n targetLanguage: string;\n items: MtItem[];\n glossary: MtGlossaryEntry[];\n style?: MtStyle;\n /** Translation-memory context: reviewed units for consistency (Plan 017 §7.4c). */\n references?: MtReference[];\n}\n\nexport interface MtBatchResult {\n key: string;\n text: string;\n}\n\nexport interface TranslationMtProvider {\n readonly key: string;\n /** Absent ⇒ the provider claims every language. */\n supportsLanguage?(language: string): boolean;\n translateBatch(request: MtBatchRequest): Promise<MtBatchResult[]>;\n}\n\n// On globalThis so a provider the host registers on one copy of this module is found by\n// another copy.\nconst { providers } = sharedState('@_linked/translation', 'mt', () => ({\n providers: new Map<string, TranslationMtProvider>(),\n}));\n\nexport function registerMtProvider(provider: TranslationMtProvider): void {\n providers.set(provider.key, provider);\n}\n\nexport function getMtProvider(key: string): TranslationMtProvider | undefined {\n return providers.get(key);\n}\n\nexport function listMtProviders(): TranslationMtProvider[] {\n return [...providers.values()];\n}\n\n/** Test seam. */\nexport function clearMtProviders(): void {\n providers.clear();\n}\n\n/**\n * Ordered provider chain for one target language: the preferred provider\n * first when it exists AND supports the language, then every other provider\n * that supports it, registration order.\n */\nexport function resolveMtProviders(\n targetLanguage: string,\n preferred?: string,\n): TranslationMtProvider[] {\n const supports = (p: TranslationMtProvider) =>\n !p.supportsLanguage || p.supportsLanguage(targetLanguage);\n const chain = listMtProviders().filter(supports);\n if (!preferred) return chain;\n const first = chain.find((p) => p.key === preferred);\n if (!first) return chain;\n return [first, ...chain.filter((p) => p !== first)];\n}\n\n/** Unique `{placeholder}` tokens in a message, sorted for set comparison. */\nexport function extractPlaceholders(\n text: string,\n format: MessageFormat = 'simple',\n): string[] {\n return analyzeMessage(text, format).arguments.map(({ name }) => name);\n}\n\n/**\n * A translation is only usable if it preserves the source's placeholder SET\n * exactly — a dropped or renamed `{param}` breaks interpolation at runtime.\n */\nexport function placeholdersIntact(\n source: string,\n translated: string,\n format: MessageFormat = 'simple',\n): boolean {\n return compareMessageArguments(source, translated, format).valid;\n}\n\n/** Glossary entries applying to one target language (global + language-specific). */\nexport function glossaryForLanguage(\n glossary: MtGlossaryEntry[],\n language: string,\n): MtGlossaryEntry[] {\n return glossary.filter((g) => !g.language || g.language === language);\n}\n\nexport function chunkItems<T>(items: T[], size: number): T[][] {\n if (size < 1) throw new Error('chunk size must be >= 1');\n const chunks: T[][] = [];\n for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size));\n return chunks;\n}\n\n/**\n * Resolve the formality for one language from the encoded config entries\n * (\"formal\" = app default; \"de:informal\" = per-language override; override wins).\n */\nexport function formalityForLanguage(\n entries: string[],\n language: string,\n): 'formal' | 'informal' | undefined {\n let result: 'formal' | 'informal' | undefined;\n for (const entry of entries ?? []) {\n const i = entry.indexOf(':');\n const value = i === -1 ? entry : entry.slice(i + 1);\n if (value !== 'formal' && value !== 'informal') continue;\n if (i === -1) {\n if (!result) result = value;\n } else if (entry.slice(0, i) === language) {\n return value;\n }\n }\n return result;\n}\n\n// ── Consistency (Plan 017 §7.4c) ────────────────────────────────────────\n\n/** Minimal entry view the consistency helpers need (client-safe). */\nexport interface ConsistencyEntry {\n key: string;\n namespace?: string;\n sourceText: string;\n format?: 'simple' | 'icu';\n units: Record<string, { text: string; state: string } | undefined>;\n}\n\n/** Identical English source translated DIFFERENTLY within one language. */\nexport interface ConsistencyConflict {\n sourceText: string;\n language: string;\n /** One entry per distinct translation: the text + the keys carrying it. */\n variants: Array<{ text: string; keys: string[] }>;\n}\n\n/**\n * Find keys whose IDENTICAL source text has diverging translations in a\n * language — the core \"same words, same translation\" consistency check.\n * Empty/untranslated units are ignored; whitespace-trimmed comparison.\n */\nexport function findInconsistentTranslations(\n entries: ConsistencyEntry[],\n language: string,\n): ConsistencyConflict[] {\n const bySource = new Map<string, Map<string, string[]>>();\n for (const entry of entries) {\n const source = entry.sourceText.trim();\n const text = entry.units[language]?.text?.trim();\n if (!source || !text) continue;\n const variants = bySource.get(source) ?? new Map<string, string[]>();\n variants.set(text, [...(variants.get(text) ?? []), entry.key]);\n bySource.set(source, variants);\n }\n const conflicts: ConsistencyConflict[] = [];\n for (const [sourceText, variants] of bySource) {\n if (variants.size < 2) continue;\n conflicts.push({\n sourceText,\n language,\n variants: [...variants].map(([text, keys]) => ({ text, keys: keys.sort() })),\n });\n }\n return conflicts.sort((a, b) => a.sourceText.localeCompare(b.sourceText));\n}\n\n/**\n * Pick reviewed units as translation-memory REFERENCES for an MT batch:\n * same-namespace reviewed translations first (shared vocabulary), then the\n * shortest reviewed units overall (vocabulary-dense). Deterministic.\n */\nexport function pickReferenceUnits(\n entries: ConsistencyEntry[],\n language: string,\n batchNamespaces: string[],\n max = 20,\n): MtReference[] {\n const namespaces = new Set(batchNamespaces);\n const reviewed = entries\n .map((entry) => ({\n namespace: entry.namespace ?? '',\n sourceText: entry.sourceText.trim(),\n unit: entry.units[language],\n }))\n .filter(\n (row) =>\n row.sourceText &&\n row.unit?.state === 'reviewed' &&\n row.unit.text.trim(),\n );\n const score = (row: (typeof reviewed)[number]) =>\n (namespaces.has(row.namespace) ? 0 : 100000) + row.sourceText.length;\n return reviewed\n .sort((a, b) => score(a) - score(b) || a.sourceText.localeCompare(b.sourceText))\n .slice(0, max)\n .map((row) => ({ sourceText: row.sourceText, text: row.unit!.text.trim() }));\n}\n"]}
|
package/lib/esm/react.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { sharedState } from '@_linked/core/utils/sharedState';
|
|
2
3
|
import { languageFallbacks, } from './languages.js';
|
|
3
4
|
import { createContext, startTransition, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore, } from 'react';
|
|
4
5
|
import { directionFor, pseudoExpand, pseudoLocalize, translate, } from './core/messages.js';
|
|
@@ -23,7 +24,11 @@ import { directionFor, pseudoExpand, pseudoLocalize, translate, } from './core/m
|
|
|
23
24
|
*/
|
|
24
25
|
export const TRANSLATION_PREVIEW_EVENT = 'linked:translation-preview';
|
|
25
26
|
const EMPTY_MESSAGES = {};
|
|
26
|
-
|
|
27
|
+
// The context object is kept on globalThis: a context created per copy of this module would
|
|
28
|
+
// leave a consumer in one copy blind to a provider from the other (it gets the null default).
|
|
29
|
+
const { TranslationContext } = sharedState('@_linked/translation', 'react.contexts', () => ({
|
|
30
|
+
TranslationContext: createContext(null),
|
|
31
|
+
}));
|
|
27
32
|
export function TranslationProvider({ children, languages: initialLanguages, loadLanguages, defaultLanguage = 'en', format = 'simple', loadMessages, storageKey = 'linked.lang', }) {
|
|
28
33
|
var _a, _b;
|
|
29
34
|
const [remoteLanguages, setRemoteLanguages] = useState();
|
package/lib/esm/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/react.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n languageFallbacks,\n type TranslationLanguageDefinition,\n} from './languages.js';\nimport React, {\n createContext,\n startTransition,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport {\n directionFor,\n type MessageFormat,\n pseudoExpand,\n pseudoLocalize,\n translate,\n type TranslationMessages,\n} from './core/messages.js';\n\n/**\n * Window event that previews a language, text direction or pseudo-locale in\n * every mounted `TranslationProvider`, without changing the user's language.\n *\n * Dispatch it with a {@link TranslationPreviewDetail}:\n *\n * ```ts\n * window.dispatchEvent(\n * new CustomEvent(TRANSLATION_PREVIEW_EVENT, {\n * detail: { language: 'ar', direction: 'ltr', pseudoLocale: 'expanded' },\n * })\n * );\n * ```\n *\n * Each event replaces the whole preview: a field that is missing or invalid\n * falls back to its default (no preview language, `'auto'`, `'off'`), so an\n * empty detail ends the preview. The preview language is never written to\n * storage, and a preview direction wins over the language's own direction.\n */\nexport const TRANSLATION_PREVIEW_EVENT = 'linked:translation-preview';\n\nexport type TranslationPreviewDirection = 'auto' | 'ltr' | 'rtl';\nexport type TranslationPreviewPseudoLocale = 'off' | 'accented' | 'expanded';\n\n/** The `detail` of a {@link TRANSLATION_PREVIEW_EVENT}. */\nexport interface TranslationPreviewDetail {\n /** BCP-47 tag to render in instead of the selected language. */\n language?: string;\n /** Force a text direction; `'auto'` uses the language's own. */\n direction?: TranslationPreviewDirection;\n /**\n * `'accented'` accents every message; `'expanded'` pads every message by\n * about a third to show controls that only fit their English copy.\n */\n pseudoLocale?: TranslationPreviewPseudoLocale;\n}\n\nconst EMPTY_MESSAGES: Record<string, TranslationMessages> = {};\n\n/**\n * React binding for the translation core (Plan 014 P1.5). Kept in a `/react`\n * subpath so non-React consumers use `./core/messages` without pulling React.\n * Message LOADING is caller-injected (`loadMessages`) so this stays transport-\n * agnostic: dev wires it to the host's API, prod to the bundled `{lang}.json` with a\n * CDN overlay (AD-J/AD-K) — the provider doesn't care which.\n *\n * The hook shape mirrors Tolgee (`useTranslate().t(key, default, params)`,\n * `useLanguage()`), so swapping serve's provider touches only its i18n file.\n */\n\nexport interface TranslationLanguage {\n tag: string; // BCP-47\n label: string;\n direction?: 'ltr' | 'rtl';\n fallback?: string;\n}\n\nexport interface TranslationCallOptions {\n /** Explicit syntax for an inline default before per-key catalog metadata loads. */\n format?: MessageFormat;\n}\n\ntype TranslateFn = (\n key: string,\n defaultValue?: string,\n params?: Record<string, unknown>,\n options?: TranslationCallOptions\n) => string;\n\ninterface TranslationContextValue {\n t: TranslateFn;\n /** `t` over no catalog: exactly what the server rendered. */\n tInline: TranslateFn;\n language: string;\n setLanguage: (tag: string) => void;\n languages: TranslationLanguage[];\n dir: 'ltr' | 'rtl';\n}\n\nconst TranslationContext = createContext<TranslationContextValue | null>(null);\n\nexport interface TranslationProviderProps {\n children: React.ReactNode;\n languages: TranslationLanguage[];\n /** Optional live catalog metadata, refreshed independently of an app build. */\n loadLanguages?: () => Promise<TranslationLanguageDefinition[]>;\n defaultLanguage?: string;\n /** Global message format. 'simple' = FormatSimple (Tolgee-compatible default). */\n format?: MessageFormat;\n /** Caller-injected loader: dev→API, prod→bundled file then CDN overlay. */\n loadMessages: (language: string) => Promise<TranslationMessages>;\n /** localStorage key persisting the selected language. */\n storageKey?: string;\n}\n\nexport function TranslationProvider({\n children,\n languages: initialLanguages,\n loadLanguages,\n defaultLanguage = 'en',\n format = 'simple',\n loadMessages,\n storageKey = 'linked.lang',\n}: TranslationProviderProps) {\n const [remoteLanguages, setRemoteLanguages] =\n useState<TranslationLanguageDefinition[]>();\n const languages = remoteLanguages\n ? remoteLanguages\n .filter((item) => item.enabled && item.supported)\n .map((item) => ({\n tag: item.code,\n label: item.nativeName,\n direction: item.direction,\n fallback: item.fallback,\n }))\n : initialLanguages;\n const [language, setLanguageState] = useState<string>(() => {\n if (typeof window !== 'undefined') {\n const stored = window.localStorage?.getItem(storageKey);\n if (stored && languages.some((l) => l.tag === stored)) return stored;\n }\n return defaultLanguage;\n });\n const [previewLanguage, setPreviewLanguage] = useState<string | null>(null);\n const [previewPseudoLocale, setPreviewPseudoLocale] =\n useState<TranslationPreviewPseudoLocale>('off');\n const [previewDirection, setPreviewDirection] =\n useState<TranslationPreviewDirection>('auto');\n const effectiveLanguage = previewLanguage ?? language;\n // A preview direction is an editor coordinate and wins; otherwise the\n // configured language's own direction, then the tag's script default.\n const dir =\n previewDirection === 'ltr' || previewDirection === 'rtl'\n ? previewDirection\n : (languages.find((item) => item.tag === effectiveLanguage)?.direction ??\n directionFor(effectiveLanguage));\n useEffect(() => {\n if (!loadLanguages) return;\n let cancelled = false;\n loadLanguages()\n .then((items) => {\n if (!cancelled) {\n setRemoteLanguages(items);\n setMessagesByLang({});\n const stored =\n typeof window !== 'undefined'\n ? window.localStorage?.getItem(storageKey)\n : null;\n if (\n stored &&\n items.some(\n (item) => item.code === stored && item.enabled && item.supported\n )\n )\n setLanguageState(stored);\n }\n })\n .catch(() => {});\n return () => {\n cancelled = true;\n };\n }, [loadLanguages, storageKey]);\n const [messagesByLang, setMessagesByLang] = useState<\n Record<string, TranslationMessages>\n >({});\n\n // Load the active language's messages once. English renders from inline\n // defaults until (and if) a payload arrives, so the app is never blank.\n useEffect(() => {\n if (messagesByLang[effectiveLanguage]) return;\n let cancelled = false;\n const chain = languageFallbacks(\n effectiveLanguage,\n defaultLanguage,\n remoteLanguages\n ).reverse();\n Promise.all(chain.map((code) => loadMessages(code).catch(() => ({}))))\n .then((maps) => Object.assign({}, ...maps) as TranslationMessages)\n .then((msgs) => {\n if (!cancelled) {\n // A descendant lazy route may still be hydrating when the catalog\n // arrives. Overlay messages without replacing its server-rendered\n // DOM; inline defaults remain usable until the transition commits.\n startTransition(() => {\n setMessagesByLang((prev) => ({\n ...prev,\n [effectiveLanguage]: msgs,\n }));\n });\n }\n })\n .catch(() => {\n /* offline / missing → keep inline defaults */\n });\n return () => {\n cancelled = true;\n };\n }, [\n effectiveLanguage,\n loadMessages,\n messagesByLang,\n defaultLanguage,\n remoteLanguages,\n ]);\n\n // Preview locale is an ephemeral editor coordinate. It intentionally does\n // not call setLanguage(), because doing so would persist a test condition as\n // the app user's real language preference.\n useEffect(() => {\n if (typeof window === 'undefined') return;\n const onPreview = (event: Event) => {\n const detail = (event as CustomEvent<TranslationPreviewDetail | null>)\n .detail;\n const nextLanguage: unknown = detail?.language;\n const nextPseudoLocale: unknown = detail?.pseudoLocale;\n const nextDirection: unknown = detail?.direction;\n setPreviewLanguage(\n typeof nextLanguage === 'string' && nextLanguage ? nextLanguage : null\n );\n setPreviewPseudoLocale(\n nextPseudoLocale === 'accented' || nextPseudoLocale === 'expanded'\n ? nextPseudoLocale\n : 'off'\n );\n setPreviewDirection(\n nextDirection === 'ltr' || nextDirection === 'rtl'\n ? nextDirection\n : 'auto'\n );\n };\n window.addEventListener(TRANSLATION_PREVIEW_EVENT, onPreview);\n return () =>\n window.removeEventListener(TRANSLATION_PREVIEW_EVENT, onPreview);\n }, []);\n\n // Reflect the effective language and text direction on <html>.\n useEffect(() => {\n if (typeof document !== 'undefined') {\n document.documentElement.dir = dir;\n document.documentElement.lang = effectiveLanguage;\n }\n }, [effectiveLanguage, dir]);\n\n const setLanguage = useCallback(\n (tag: string) => {\n setLanguageState(tag);\n if (typeof window !== 'undefined') {\n window.localStorage?.setItem(storageKey, tag);\n }\n },\n [storageKey]\n );\n\n const translatorFor = useCallback(\n (catalogs: Record<string, TranslationMessages>): TranslateFn =>\n (key, defaultValue, params, options) => {\n const translated = translate(\n catalogs[effectiveLanguage] ?? {},\n key,\n defaultValue,\n params,\n options?.format ?? format,\n effectiveLanguage\n );\n if (previewPseudoLocale === 'accented') {\n return effectiveLanguage.toLowerCase() === 'en-xa'\n ? translated\n : pseudoLocalize(translated);\n }\n return previewPseudoLocale === 'expanded'\n ? pseudoExpand(translated)\n : translated;\n },\n [effectiveLanguage, format, previewPseudoLocale]\n );\n const t = useMemo(\n () => translatorFor(messagesByLang),\n [translatorFor, messagesByLang]\n );\n const tInline = useMemo(() => translatorFor(EMPTY_MESSAGES), [translatorFor]);\n\n const value = useMemo<TranslationContextValue>(\n () => ({\n t,\n tInline,\n language: effectiveLanguage,\n setLanguage,\n languages,\n dir,\n }),\n [t, tInline, effectiveLanguage, setLanguage, languages, dir]\n );\n\n return (\n <TranslationContext.Provider value={value}>\n {children}\n </TranslationContext.Provider>\n );\n}\n\nfunction useTranslationContext(hook: string): TranslationContextValue {\n const ctx = useContext(TranslationContext);\n if (!ctx)\n throw new Error(`${hook} must be used within a <TranslationProvider>`);\n return ctx;\n}\n\n/** Tolgee-compatible: `const { t } = useTranslate()`. */\nexport function useTranslate() {\n const ctx = useTranslationContext('useTranslate');\n // The server never has a catalog, and a lazy descendant can hydrate after\n // the catalog has already reached context. Gating per consumer means every\n // hydrating render replays the inline defaults, then re-renders with the\n // catalog once that consumer commits.\n const hydrated = useSyncExternalStore(\n subscribeNever,\n () => true,\n () => false\n );\n return { t: hydrated ? ctx.t : ctx.tInline };\n}\n\nconst subscribeNever = () => () => {};\n\n/** Active language + switcher + direction + the available languages. */\nexport function useLanguage() {\n const ctx = useTranslationContext('useLanguage');\n return {\n language: ctx.language,\n setLanguage: ctx.setLanguage,\n languages: ctx.languages,\n dir: ctx.dir,\n };\n}\n\n/** JSX convenience (Tolgee `<T keyName=… />`). */\nexport function T({\n keyName,\n defaultValue,\n params,\n format,\n}: {\n keyName: string;\n defaultValue?: string;\n params?: Record<string, unknown>;\n format?: MessageFormat;\n}) {\n const { t } = useTranslate();\n return <>{t(keyName, defaultValue, params, { format })}</>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/react.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EACL,iBAAiB,GAElB,MAAM,gBAAgB,CAAC;AACxB,OAAc,EACZ,aAAa,EACb,eAAe,EACf,WAAW,EACX,UAAU,EACV,SAAS,EACT,OAAO,EACP,QAAQ,EACR,oBAAoB,GACrB,MAAM,OAAO,CAAC;AACf,OAAO,EACL,YAAY,EAEZ,YAAY,EACZ,cAAc,EACd,SAAS,GAEV,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,4BAA4B,CAAC;AAkBtE,MAAM,cAAc,GAAwC,EAAE,CAAC;AA0C/D,4FAA4F;AAC5F,8FAA8F;AAC9F,MAAM,EAAE,kBAAkB,EAAE,GAAG,WAAW,CAAC,sBAAsB,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1F,kBAAkB,EAAE,aAAa,CAAiC,IAAI,CAAC;CACxE,CAAC,CAAC,CAAC;AAgBJ,MAAM,UAAU,mBAAmB,CAAC,EAClC,QAAQ,EACR,SAAS,EAAE,gBAAgB,EAC3B,aAAa,EACb,eAAe,GAAG,IAAI,EACtB,MAAM,GAAG,QAAQ,EACjB,YAAY,EACZ,UAAU,GAAG,aAAa,GACD;;IACzB,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GACzC,QAAQ,EAAmC,CAAC;IAC9C,MAAM,SAAS,GAAG,eAAe;QAC/B,CAAC,CAAC,eAAe;aACZ,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC;aAChD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACd,GAAG,EAAE,IAAI,CAAC,IAAI;YACd,KAAK,EAAE,IAAI,CAAC,UAAU;YACtB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;QACP,CAAC,CAAC,gBAAgB,CAAC;IACrB,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAS,GAAG,EAAE;;QACzD,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAA,MAAM,CAAC,YAAY,0CAAE,OAAO,CAAC,UAAU,CAAC,CAAC;YACxD,IAAI,MAAM,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC;gBAAE,OAAO,MAAM,CAAC;QACvE,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GACjD,QAAQ,CAAiC,KAAK,CAAC,CAAC;IAClD,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAC3C,QAAQ,CAA8B,MAAM,CAAC,CAAC;IAChD,MAAM,iBAAiB,GAAG,eAAe,aAAf,eAAe,cAAf,eAAe,GAAI,QAAQ,CAAC;IACtD,sEAAsE;IACtE,sEAAsE;IACtE,MAAM,GAAG,GACP,gBAAgB,KAAK,KAAK,IAAI,gBAAgB,KAAK,KAAK;QACtD,CAAC,CAAC,gBAAgB;QAClB,CAAC,CAAC,CAAC,MAAA,MAAA,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,iBAAiB,CAAC,0CAAE,SAAS,mCACpE,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,aAAa;YAAE,OAAO;QAC3B,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,aAAa,EAAE;aACZ,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;;YACd,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,kBAAkB,CAAC,KAAK,CAAC,CAAC;gBAC1B,iBAAiB,CAAC,EAAE,CAAC,CAAC;gBACtB,MAAM,MAAM,GACV,OAAO,MAAM,KAAK,WAAW;oBAC3B,CAAC,CAAC,MAAA,MAAM,CAAC,YAAY,0CAAE,OAAO,CAAC,UAAU,CAAC;oBAC1C,CAAC,CAAC,IAAI,CAAC;gBACX,IACE,MAAM;oBACN,KAAK,CAAC,IAAI,CACR,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS,CACjE;oBAED,gBAAgB,CAAC,MAAM,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnB,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;IAChC,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAElD,EAAE,CAAC,CAAC;IAEN,wEAAwE;IACxE,wEAAwE;IACxE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,cAAc,CAAC,iBAAiB,CAAC;YAAE,OAAO;QAC9C,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,KAAK,GAAG,iBAAiB,CAC7B,iBAAiB,EACjB,eAAe,EACf,eAAe,CAChB,CAAC,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;aACnE,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,IAAI,CAAwB,CAAC;aACjE,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;YACb,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,kEAAkE;gBAClE,kEAAkE;gBAClE,mEAAmE;gBACnE,eAAe,CAAC,GAAG,EAAE;oBACnB,iBAAiB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;wBAC3B,GAAG,IAAI;wBACP,CAAC,iBAAiB,CAAC,EAAE,IAAI;qBAC1B,CAAC,CAAC,CAAC;gBACN,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACV,8CAA8C;QAChD,CAAC,CAAC,CAAC;QACL,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE;QACD,iBAAiB;QACjB,YAAY;QACZ,cAAc;QACd,eAAe;QACf,eAAe;KAChB,CAAC,CAAC;IAEH,0EAA0E;IAC1E,6EAA6E;IAC7E,2CAA2C;IAC3C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAC1C,MAAM,SAAS,GAAG,CAAC,KAAY,EAAE,EAAE;YACjC,MAAM,MAAM,GAAI,KAAsD;iBACnE,MAAM,CAAC;YACV,MAAM,YAAY,GAAY,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,QAAQ,CAAC;YAC/C,MAAM,gBAAgB,GAAY,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,YAAY,CAAC;YACvD,MAAM,aAAa,GAAY,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,SAAS,CAAC;YACjD,kBAAkB,CAChB,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CACvE,CAAC;YACF,sBAAsB,CACpB,gBAAgB,KAAK,UAAU,IAAI,gBAAgB,KAAK,UAAU;gBAChE,CAAC,CAAC,gBAAgB;gBAClB,CAAC,CAAC,KAAK,CACV,CAAC;YACF,mBAAmB,CACjB,aAAa,KAAK,KAAK,IAAI,aAAa,KAAK,KAAK;gBAChD,CAAC,CAAC,aAAa;gBACf,CAAC,CAAC,MAAM,CACX,CAAC;QACJ,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,yBAAyB,EAAE,SAAS,CAAC,CAAC;QAC9D,OAAO,GAAG,EAAE,CACV,MAAM,CAAC,mBAAmB,CAAC,yBAAyB,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,+DAA+D;IAC/D,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACpC,QAAQ,CAAC,eAAe,CAAC,GAAG,GAAG,GAAG,CAAC;YACnC,QAAQ,CAAC,eAAe,CAAC,IAAI,GAAG,iBAAiB,CAAC;QACpD,CAAC;IACH,CAAC,EAAE,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC,CAAC;IAE7B,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,GAAW,EAAE,EAAE;;QACd,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAA,MAAM,CAAC,YAAY,0CAAE,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAChD,CAAC;IACH,CAAC,EACD,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,QAA6C,EAAe,EAAE,CAC7D,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;;QACrC,MAAM,UAAU,GAAG,SAAS,CAC1B,MAAA,QAAQ,CAAC,iBAAiB,CAAC,mCAAI,EAAE,EACjC,GAAG,EACH,YAAY,EACZ,MAAM,EACN,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM,mCAAI,MAAM,EACzB,iBAAiB,CAClB,CAAC;QACF,IAAI,mBAAmB,KAAK,UAAU,EAAE,CAAC;YACvC,OAAO,iBAAiB,CAAC,WAAW,EAAE,KAAK,OAAO;gBAChD,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,mBAAmB,KAAK,UAAU;YACvC,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC;YAC1B,CAAC,CAAC,UAAU,CAAC;IACjB,CAAC,EACH,CAAC,iBAAiB,EAAE,MAAM,EAAE,mBAAmB,CAAC,CACjD,CAAC;IACF,MAAM,CAAC,GAAG,OAAO,CACf,GAAG,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,EACnC,CAAC,aAAa,EAAE,cAAc,CAAC,CAChC,CAAC;IACF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAE9E,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,CAAC;QACD,OAAO;QACP,QAAQ,EAAE,iBAAiB;QAC3B,WAAW;QACX,SAAS;QACT,GAAG;KACJ,CAAC,EACF,CAAC,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC,CAC7D,CAAC;IAEF,OAAO,CACL,KAAC,kBAAkB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACtC,QAAQ,GACmB,CAC/B,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAY;IACzC,MAAM,GAAG,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAC3C,IAAI,CAAC,GAAG;QACN,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,8CAA8C,CAAC,CAAC;IACzE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,qBAAqB,CAAC,cAAc,CAAC,CAAC;IAClD,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,sCAAsC;IACtC,MAAM,QAAQ,GAAG,oBAAoB,CACnC,cAAc,EACd,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,KAAK,CACZ,CAAC;IACF,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;AAEtC,wEAAwE;AACxE,MAAM,UAAU,WAAW;IACzB,MAAM,GAAG,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACjD,OAAO;QACL,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,GAAG,EAAE,GAAG,CAAC,GAAG;KACb,CAAC;AACJ,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,CAAC,CAAC,EAChB,OAAO,EACP,YAAY,EACZ,MAAM,EACN,MAAM,GAMP;IACC,MAAM,EAAE,CAAC,EAAE,GAAG,YAAY,EAAE,CAAC;IAC7B,OAAO,4BAAG,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,GAAI,CAAC;AAC7D,CAAC","sourcesContent":["import { sharedState } from '@_linked/core/utils/sharedState';\nimport {\n languageFallbacks,\n type TranslationLanguageDefinition,\n} from './languages.js';\nimport React, {\n createContext,\n startTransition,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport {\n directionFor,\n type MessageFormat,\n pseudoExpand,\n pseudoLocalize,\n translate,\n type TranslationMessages,\n} from './core/messages.js';\n\n/**\n * Window event that previews a language, text direction or pseudo-locale in\n * every mounted `TranslationProvider`, without changing the user's language.\n *\n * Dispatch it with a {@link TranslationPreviewDetail}:\n *\n * ```ts\n * window.dispatchEvent(\n * new CustomEvent(TRANSLATION_PREVIEW_EVENT, {\n * detail: { language: 'ar', direction: 'ltr', pseudoLocale: 'expanded' },\n * })\n * );\n * ```\n *\n * Each event replaces the whole preview: a field that is missing or invalid\n * falls back to its default (no preview language, `'auto'`, `'off'`), so an\n * empty detail ends the preview. The preview language is never written to\n * storage, and a preview direction wins over the language's own direction.\n */\nexport const TRANSLATION_PREVIEW_EVENT = 'linked:translation-preview';\n\nexport type TranslationPreviewDirection = 'auto' | 'ltr' | 'rtl';\nexport type TranslationPreviewPseudoLocale = 'off' | 'accented' | 'expanded';\n\n/** The `detail` of a {@link TRANSLATION_PREVIEW_EVENT}. */\nexport interface TranslationPreviewDetail {\n /** BCP-47 tag to render in instead of the selected language. */\n language?: string;\n /** Force a text direction; `'auto'` uses the language's own. */\n direction?: TranslationPreviewDirection;\n /**\n * `'accented'` accents every message; `'expanded'` pads every message by\n * about a third to show controls that only fit their English copy.\n */\n pseudoLocale?: TranslationPreviewPseudoLocale;\n}\n\nconst EMPTY_MESSAGES: Record<string, TranslationMessages> = {};\n\n/**\n * React binding for the translation core (Plan 014 P1.5). Kept in a `/react`\n * subpath so non-React consumers use `./core/messages` without pulling React.\n * Message LOADING is caller-injected (`loadMessages`) so this stays transport-\n * agnostic: dev wires it to the host's API, prod to the bundled `{lang}.json` with a\n * CDN overlay (AD-J/AD-K) — the provider doesn't care which.\n *\n * The hook shape mirrors Tolgee (`useTranslate().t(key, default, params)`,\n * `useLanguage()`), so swapping serve's provider touches only its i18n file.\n */\n\nexport interface TranslationLanguage {\n tag: string; // BCP-47\n label: string;\n direction?: 'ltr' | 'rtl';\n fallback?: string;\n}\n\nexport interface TranslationCallOptions {\n /** Explicit syntax for an inline default before per-key catalog metadata loads. */\n format?: MessageFormat;\n}\n\ntype TranslateFn = (\n key: string,\n defaultValue?: string,\n params?: Record<string, unknown>,\n options?: TranslationCallOptions\n) => string;\n\ninterface TranslationContextValue {\n t: TranslateFn;\n /** `t` over no catalog: exactly what the server rendered. */\n tInline: TranslateFn;\n language: string;\n setLanguage: (tag: string) => void;\n languages: TranslationLanguage[];\n dir: 'ltr' | 'rtl';\n}\n\n// The context object is kept on globalThis: a context created per copy of this module would\n// leave a consumer in one copy blind to a provider from the other (it gets the null default).\nconst { TranslationContext } = sharedState('@_linked/translation', 'react.contexts', () => ({\n TranslationContext: createContext<TranslationContextValue | null>(null),\n}));\n\nexport interface TranslationProviderProps {\n children: React.ReactNode;\n languages: TranslationLanguage[];\n /** Optional live catalog metadata, refreshed independently of an app build. */\n loadLanguages?: () => Promise<TranslationLanguageDefinition[]>;\n defaultLanguage?: string;\n /** Global message format. 'simple' = FormatSimple (Tolgee-compatible default). */\n format?: MessageFormat;\n /** Caller-injected loader: dev→API, prod→bundled file then CDN overlay. */\n loadMessages: (language: string) => Promise<TranslationMessages>;\n /** localStorage key persisting the selected language. */\n storageKey?: string;\n}\n\nexport function TranslationProvider({\n children,\n languages: initialLanguages,\n loadLanguages,\n defaultLanguage = 'en',\n format = 'simple',\n loadMessages,\n storageKey = 'linked.lang',\n}: TranslationProviderProps) {\n const [remoteLanguages, setRemoteLanguages] =\n useState<TranslationLanguageDefinition[]>();\n const languages = remoteLanguages\n ? remoteLanguages\n .filter((item) => item.enabled && item.supported)\n .map((item) => ({\n tag: item.code,\n label: item.nativeName,\n direction: item.direction,\n fallback: item.fallback,\n }))\n : initialLanguages;\n const [language, setLanguageState] = useState<string>(() => {\n if (typeof window !== 'undefined') {\n const stored = window.localStorage?.getItem(storageKey);\n if (stored && languages.some((l) => l.tag === stored)) return stored;\n }\n return defaultLanguage;\n });\n const [previewLanguage, setPreviewLanguage] = useState<string | null>(null);\n const [previewPseudoLocale, setPreviewPseudoLocale] =\n useState<TranslationPreviewPseudoLocale>('off');\n const [previewDirection, setPreviewDirection] =\n useState<TranslationPreviewDirection>('auto');\n const effectiveLanguage = previewLanguage ?? language;\n // A preview direction is an editor coordinate and wins; otherwise the\n // configured language's own direction, then the tag's script default.\n const dir =\n previewDirection === 'ltr' || previewDirection === 'rtl'\n ? previewDirection\n : (languages.find((item) => item.tag === effectiveLanguage)?.direction ??\n directionFor(effectiveLanguage));\n useEffect(() => {\n if (!loadLanguages) return;\n let cancelled = false;\n loadLanguages()\n .then((items) => {\n if (!cancelled) {\n setRemoteLanguages(items);\n setMessagesByLang({});\n const stored =\n typeof window !== 'undefined'\n ? window.localStorage?.getItem(storageKey)\n : null;\n if (\n stored &&\n items.some(\n (item) => item.code === stored && item.enabled && item.supported\n )\n )\n setLanguageState(stored);\n }\n })\n .catch(() => {});\n return () => {\n cancelled = true;\n };\n }, [loadLanguages, storageKey]);\n const [messagesByLang, setMessagesByLang] = useState<\n Record<string, TranslationMessages>\n >({});\n\n // Load the active language's messages once. English renders from inline\n // defaults until (and if) a payload arrives, so the app is never blank.\n useEffect(() => {\n if (messagesByLang[effectiveLanguage]) return;\n let cancelled = false;\n const chain = languageFallbacks(\n effectiveLanguage,\n defaultLanguage,\n remoteLanguages\n ).reverse();\n Promise.all(chain.map((code) => loadMessages(code).catch(() => ({}))))\n .then((maps) => Object.assign({}, ...maps) as TranslationMessages)\n .then((msgs) => {\n if (!cancelled) {\n // A descendant lazy route may still be hydrating when the catalog\n // arrives. Overlay messages without replacing its server-rendered\n // DOM; inline defaults remain usable until the transition commits.\n startTransition(() => {\n setMessagesByLang((prev) => ({\n ...prev,\n [effectiveLanguage]: msgs,\n }));\n });\n }\n })\n .catch(() => {\n /* offline / missing → keep inline defaults */\n });\n return () => {\n cancelled = true;\n };\n }, [\n effectiveLanguage,\n loadMessages,\n messagesByLang,\n defaultLanguage,\n remoteLanguages,\n ]);\n\n // Preview locale is an ephemeral editor coordinate. It intentionally does\n // not call setLanguage(), because doing so would persist a test condition as\n // the app user's real language preference.\n useEffect(() => {\n if (typeof window === 'undefined') return;\n const onPreview = (event: Event) => {\n const detail = (event as CustomEvent<TranslationPreviewDetail | null>)\n .detail;\n const nextLanguage: unknown = detail?.language;\n const nextPseudoLocale: unknown = detail?.pseudoLocale;\n const nextDirection: unknown = detail?.direction;\n setPreviewLanguage(\n typeof nextLanguage === 'string' && nextLanguage ? nextLanguage : null\n );\n setPreviewPseudoLocale(\n nextPseudoLocale === 'accented' || nextPseudoLocale === 'expanded'\n ? nextPseudoLocale\n : 'off'\n );\n setPreviewDirection(\n nextDirection === 'ltr' || nextDirection === 'rtl'\n ? nextDirection\n : 'auto'\n );\n };\n window.addEventListener(TRANSLATION_PREVIEW_EVENT, onPreview);\n return () =>\n window.removeEventListener(TRANSLATION_PREVIEW_EVENT, onPreview);\n }, []);\n\n // Reflect the effective language and text direction on <html>.\n useEffect(() => {\n if (typeof document !== 'undefined') {\n document.documentElement.dir = dir;\n document.documentElement.lang = effectiveLanguage;\n }\n }, [effectiveLanguage, dir]);\n\n const setLanguage = useCallback(\n (tag: string) => {\n setLanguageState(tag);\n if (typeof window !== 'undefined') {\n window.localStorage?.setItem(storageKey, tag);\n }\n },\n [storageKey]\n );\n\n const translatorFor = useCallback(\n (catalogs: Record<string, TranslationMessages>): TranslateFn =>\n (key, defaultValue, params, options) => {\n const translated = translate(\n catalogs[effectiveLanguage] ?? {},\n key,\n defaultValue,\n params,\n options?.format ?? format,\n effectiveLanguage\n );\n if (previewPseudoLocale === 'accented') {\n return effectiveLanguage.toLowerCase() === 'en-xa'\n ? translated\n : pseudoLocalize(translated);\n }\n return previewPseudoLocale === 'expanded'\n ? pseudoExpand(translated)\n : translated;\n },\n [effectiveLanguage, format, previewPseudoLocale]\n );\n const t = useMemo(\n () => translatorFor(messagesByLang),\n [translatorFor, messagesByLang]\n );\n const tInline = useMemo(() => translatorFor(EMPTY_MESSAGES), [translatorFor]);\n\n const value = useMemo<TranslationContextValue>(\n () => ({\n t,\n tInline,\n language: effectiveLanguage,\n setLanguage,\n languages,\n dir,\n }),\n [t, tInline, effectiveLanguage, setLanguage, languages, dir]\n );\n\n return (\n <TranslationContext.Provider value={value}>\n {children}\n </TranslationContext.Provider>\n );\n}\n\nfunction useTranslationContext(hook: string): TranslationContextValue {\n const ctx = useContext(TranslationContext);\n if (!ctx)\n throw new Error(`${hook} must be used within a <TranslationProvider>`);\n return ctx;\n}\n\n/** Tolgee-compatible: `const { t } = useTranslate()`. */\nexport function useTranslate() {\n const ctx = useTranslationContext('useTranslate');\n // The server never has a catalog, and a lazy descendant can hydrate after\n // the catalog has already reached context. Gating per consumer means every\n // hydrating render replays the inline defaults, then re-renders with the\n // catalog once that consumer commits.\n const hydrated = useSyncExternalStore(\n subscribeNever,\n () => true,\n () => false\n );\n return { t: hydrated ? ctx.t : ctx.tInline };\n}\n\nconst subscribeNever = () => () => {};\n\n/** Active language + switcher + direction + the available languages. */\nexport function useLanguage() {\n const ctx = useTranslationContext('useLanguage');\n return {\n language: ctx.language,\n setLanguage: ctx.setLanguage,\n languages: ctx.languages,\n dir: ctx.dir,\n };\n}\n\n/** JSX convenience (Tolgee `<T keyName=… />`). */\nexport function T({\n keyName,\n defaultValue,\n params,\n format,\n}: {\n keyName: string;\n defaultValue?: string;\n params?: Record<string, unknown>;\n format?: MessageFormat;\n}) {\n const { t } = useTranslate();\n return <>{t(keyName, defaultValue, params, { format })}</>;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@_linked/translation",
|
|
3
3
|
"description": "LINKED translation management — graph-native i18n keys + units, Tolgee-compatible client, CDN delivery.",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.2",
|
|
5
5
|
"packageManager": "npm@12.2.0",
|
|
6
6
|
"linkedPackage": true,
|
|
7
7
|
"type": "module",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"dependencies": {
|
|
93
|
-
"@_linked/core": "^2.
|
|
93
|
+
"@_linked/core": "^2.36.1",
|
|
94
94
|
"@_linked/server-utils": "^1.14.0",
|
|
95
95
|
"@formatjs/icu-messageformat-parser": "3.5.21",
|
|
96
96
|
"fflate": "^0.8.2",
|
package/src/authorization.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sharedState } from '@_linked/core/utils/sharedState';
|
|
2
|
+
|
|
1
3
|
export type TranslationAuthoringAction =
|
|
2
4
|
| 'manage'
|
|
3
5
|
| 'review'
|
|
@@ -16,7 +18,11 @@ export type TranslationAuthorizationResolver = (
|
|
|
16
18
|
request: TranslationAuthorizationRequest,
|
|
17
19
|
) => Promise<boolean>;
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
// On globalThis so a second copy of this module sees the resolver the host configured on the
|
|
22
|
+
// first; a copy that cannot see it would deny every author.
|
|
23
|
+
const state = sharedState('@_linked/translation', 'authorization', () => ({
|
|
24
|
+
resolver: undefined as TranslationAuthorizationResolver | undefined,
|
|
25
|
+
}));
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
28
|
* Host hook: the host decides who may read and author an app's translations
|
|
@@ -29,7 +35,7 @@ let resolver: TranslationAuthorizationResolver | undefined;
|
|
|
29
35
|
export function configureTranslationAuthorization(
|
|
30
36
|
next?: TranslationAuthorizationResolver,
|
|
31
37
|
): void {
|
|
32
|
-
resolver = next;
|
|
38
|
+
state.resolver = next;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
/**
|
|
@@ -39,6 +45,7 @@ export function configureTranslationAuthorization(
|
|
|
39
45
|
export async function canAuthorTranslation(
|
|
40
46
|
request: TranslationAuthorizationRequest,
|
|
41
47
|
): Promise<boolean> {
|
|
48
|
+
const resolver = state.resolver;
|
|
42
49
|
if (!resolver) return false;
|
|
43
50
|
try {
|
|
44
51
|
return (await resolver(request)) === true;
|
package/src/exchange.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sharedState } from '@_linked/core/utils/sharedState';
|
|
1
2
|
import type { MessageFormat } from './core/messages.js';
|
|
2
3
|
import { canonicalLanguageTag } from './key-version.js';
|
|
3
4
|
import type { TranslationState } from './records.js';
|
|
@@ -131,7 +132,11 @@ const EXCHANGE_STATES = new Set<TranslationState>([
|
|
|
131
132
|
'reviewed',
|
|
132
133
|
'stale',
|
|
133
134
|
]);
|
|
134
|
-
|
|
135
|
+
// On globalThis so an adapter the host registers on one copy of this module is found by
|
|
136
|
+
// another copy.
|
|
137
|
+
const { adapters } = sharedState('@_linked/translation', 'exchange', () => ({
|
|
138
|
+
adapters: new Map<TranslationFormat, TranslationFormatAdapter>(),
|
|
139
|
+
}));
|
|
135
140
|
|
|
136
141
|
function record(value: unknown, label: string): Record<string, unknown> {
|
|
137
142
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -402,7 +407,11 @@ export function isConfirmedBuildDeclaration(
|
|
|
402
407
|
export function registerTranslationFormatAdapter(
|
|
403
408
|
adapter: TranslationFormatAdapter,
|
|
404
409
|
): void {
|
|
405
|
-
|
|
410
|
+
const registered = adapters.get(adapter.format);
|
|
411
|
+
// The registry is process-wide, so the same adapter registered again (by a second copy of
|
|
412
|
+
// the host module that registers it) is not a conflict; a different adapter for the format is.
|
|
413
|
+
if (registered === adapter) return;
|
|
414
|
+
if (registered) {
|
|
406
415
|
throw new Error(
|
|
407
416
|
`Translation format adapter "${adapter.format}" is already registered.`,
|
|
408
417
|
);
|
package/src/mt.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* SDK imports — concrete providers live with the host application and are
|
|
9
9
|
* registered at boot.
|
|
10
10
|
*/
|
|
11
|
+
import { sharedState } from '@_linked/core/utils/sharedState';
|
|
11
12
|
import {
|
|
12
13
|
analyzeMessage,
|
|
13
14
|
compareMessageArguments,
|
|
@@ -72,7 +73,11 @@ export interface TranslationMtProvider {
|
|
|
72
73
|
translateBatch(request: MtBatchRequest): Promise<MtBatchResult[]>;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
// On globalThis so a provider the host registers on one copy of this module is found by
|
|
77
|
+
// another copy.
|
|
78
|
+
const { providers } = sharedState('@_linked/translation', 'mt', () => ({
|
|
79
|
+
providers: new Map<string, TranslationMtProvider>(),
|
|
80
|
+
}));
|
|
76
81
|
|
|
77
82
|
export function registerMtProvider(provider: TranslationMtProvider): void {
|
|
78
83
|
providers.set(provider.key, provider);
|
package/src/react.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sharedState } from '@_linked/core/utils/sharedState';
|
|
1
2
|
import {
|
|
2
3
|
languageFallbacks,
|
|
3
4
|
type TranslationLanguageDefinition,
|
|
@@ -100,7 +101,11 @@ interface TranslationContextValue {
|
|
|
100
101
|
dir: 'ltr' | 'rtl';
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
// The context object is kept on globalThis: a context created per copy of this module would
|
|
105
|
+
// leave a consumer in one copy blind to a provider from the other (it gets the null default).
|
|
106
|
+
const { TranslationContext } = sharedState('@_linked/translation', 'react.contexts', () => ({
|
|
107
|
+
TranslationContext: createContext<TranslationContextValue | null>(null),
|
|
108
|
+
}));
|
|
104
109
|
|
|
105
110
|
export interface TranslationProviderProps {
|
|
106
111
|
children: React.ReactNode;
|