@_linked/translation 0.9.4 → 0.9.5

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.
Files changed (41) hide show
  1. package/package.json +2 -3
  2. package/src/archive.ts +0 -914
  3. package/src/authorization.ts +0 -56
  4. package/src/backend.ts +0 -2
  5. package/src/cdn-loader.ts +0 -264
  6. package/src/checks.ts +0 -413
  7. package/src/compile.ts +0 -168
  8. package/src/core/messages.ts +0 -139
  9. package/src/data/translation.ttl +0 -489
  10. package/src/discovery.ts +0 -594
  11. package/src/exchange.ts +0 -437
  12. package/src/export.ts +0 -138
  13. package/src/formats/csv.ts +0 -467
  14. package/src/formats/json.ts +0 -596
  15. package/src/formats/xliff.ts +0 -733
  16. package/src/import.ts +0 -648
  17. package/src/index.ts +0 -201
  18. package/src/key-sync-node.ts +0 -269
  19. package/src/key-sync.ts +0 -208
  20. package/src/key-version.ts +0 -207
  21. package/src/languages.ts +0 -102
  22. package/src/linked-discovery.ts +0 -385
  23. package/src/message-format.ts +0 -214
  24. package/src/mt.ts +0 -253
  25. package/src/ontologies/translation.register.ts +0 -17
  26. package/src/ontologies/translation.ts +0 -239
  27. package/src/package.ts +0 -14
  28. package/src/react.tsx +0 -378
  29. package/src/records.ts +0 -181
  30. package/src/release.ts +0 -754
  31. package/src/shape-catalog.ts +0 -198
  32. package/src/shapes/GlossaryTerm.ts +0 -61
  33. package/src/shapes/TranslationInventoryOrigin.ts +0 -131
  34. package/src/shapes/TranslationKey.ts +0 -183
  35. package/src/shapes/TranslationKeyVersion.ts +0 -77
  36. package/src/shapes/TranslationLanguage.ts +0 -51
  37. package/src/shapes/TranslationProvider.ts +0 -1927
  38. package/src/shapes/TranslationRelease.ts +0 -114
  39. package/src/shapes/TranslationRevision.ts +0 -105
  40. package/src/shapes/TranslationUnit.ts +0 -72
  41. package/src/shapes/index.ts +0 -13
package/src/discovery.ts DELETED
@@ -1,594 +0,0 @@
1
- import type { MessageFormat } from './core/messages.js';
2
- import type {
3
- TranslationBuildDeclaration,
4
- TranslationDeclarationKind,
5
- TranslationDeclarationStatus,
6
- } from './exchange.js';
7
-
8
- export type TranslationDeclarationSource =
9
- | 'code'
10
- | 'manifest'
11
- | 'package'
12
- | 'linked'
13
- | 'import'
14
- | 'runtime';
15
-
16
- export interface TranslationDeclarationProvenance {
17
- source: TranslationDeclarationSource;
18
- sourceId: string;
19
- authoritative: boolean;
20
- file?: string;
21
- line?: number;
22
- ownerIri?: string;
23
- shapeIri?: string;
24
- propertyIri?: string;
25
- nodeIri?: string;
26
- fromPackage?: string;
27
- targetClass?: string;
28
- }
29
-
30
- export interface TranslationDeclaration {
31
- schemaVersion: 1;
32
- key: string;
33
- sourceText: string;
34
- namespace?: string;
35
- kind: TranslationDeclarationKind;
36
- format: MessageFormat;
37
- description?: string;
38
- provenance: TranslationDeclarationProvenance;
39
- }
40
-
41
- export interface TranslationDiscoveryContext {
42
- appId: string;
43
- branchId?: string;
44
- sourceRoot?: string;
45
- }
46
-
47
- export interface TranslationDiscoverySource {
48
- readonly name: string;
49
- discover(
50
- context: TranslationDiscoveryContext,
51
- ): AsyncIterable<TranslationDeclaration>;
52
- }
53
-
54
- export type TranslationInventoryStatus =
55
- | 'confirmed'
56
- | 'pending'
57
- | 'conflict'
58
- | 'orphaned';
59
-
60
- export interface TranslationInventoryItem {
61
- identity: string;
62
- appId: string;
63
- key: string;
64
- sourceText: string;
65
- namespace?: string;
66
- kind: TranslationDeclarationKind;
67
- format: MessageFormat;
68
- description?: string;
69
- status: TranslationInventoryStatus;
70
- authoritative: boolean;
71
- exchangeEligible: boolean;
72
- buildEligible: boolean;
73
- origins: TranslationDeclarationProvenance[];
74
- }
75
-
76
- export interface TranslationInventoryConflict {
77
- identity: string;
78
- declarations: TranslationDeclaration[];
79
- }
80
-
81
- export interface TranslationInventorySnapshot {
82
- schemaVersion: 1;
83
- appId: string;
84
- branchId?: string;
85
- items: TranslationInventoryItem[];
86
- conflicts: TranslationInventoryConflict[];
87
- buildDeclarations: TranslationBuildDeclaration[];
88
- }
89
-
90
- export interface TranslationDiscoveryManifestEntry {
91
- key: string;
92
- sourceText: string;
93
- namespace?: string;
94
- kind?: TranslationDeclarationKind;
95
- format?: MessageFormat;
96
- description?: string;
97
- ownerIri?: string;
98
- shapeIri?: string;
99
- propertyIri?: string;
100
- nodeIri?: string;
101
- }
102
-
103
- export interface TranslationDiscoveryManifest {
104
- schemaVersion: 1;
105
- sourceId: string;
106
- declarations: TranslationDiscoveryManifestEntry[];
107
- }
108
-
109
- export interface DiscoverTranslationInventoryOptions {
110
- previous?: TranslationInventorySnapshot;
111
- }
112
-
113
- const KIND_ORDER: Record<TranslationDeclarationKind, number> = {
114
- ui: 0,
115
- semantic: 1,
116
- content: 2,
117
- };
118
-
119
- function requiredString(value: unknown, label: string): string {
120
- if (typeof value !== 'string' || !value.trim()) {
121
- throw new Error(`${label} is required.`);
122
- }
123
- return value.trim();
124
- }
125
-
126
- function optionalString(value: unknown, label: string): string | undefined {
127
- if (value === undefined) return undefined;
128
- if (typeof value !== 'string' || !value.trim()) {
129
- throw new Error(`${label} must be a non-empty string when supplied.`);
130
- }
131
- return value.trim();
132
- }
133
-
134
- function declarationKind(
135
- value: unknown,
136
- label: string,
137
- fallback: TranslationDeclarationKind = 'ui',
138
- ): TranslationDeclarationKind {
139
- const candidate = value ?? fallback;
140
- if (
141
- candidate !== 'ui' &&
142
- candidate !== 'semantic' &&
143
- candidate !== 'content'
144
- ) {
145
- throw new Error(`${label} must be "ui", "semantic", or "content".`);
146
- }
147
- return candidate;
148
- }
149
-
150
- function messageFormat(
151
- value: unknown,
152
- label: string,
153
- fallback: MessageFormat = 'simple',
154
- ): MessageFormat {
155
- const candidate = value ?? fallback;
156
- if (candidate !== 'simple' && candidate !== 'icu') {
157
- throw new Error(`${label} must be "simple" or "icu".`);
158
- }
159
- return candidate;
160
- }
161
-
162
- function object(value: unknown, label: string): Record<string, unknown> {
163
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
164
- throw new Error(`${label} must be an object.`);
165
- }
166
- return value as Record<string, unknown>;
167
- }
168
-
169
- function normalizeManifestEntry(
170
- value: unknown,
171
- index: number,
172
- ): TranslationDiscoveryManifestEntry {
173
- const entry = object(value, `declarations[${index}]`);
174
- const kind = declarationKind(entry.kind, `declarations[${index}].kind`);
175
- const nodeIri = optionalString(
176
- entry.nodeIri,
177
- `declarations[${index}].nodeIri`,
178
- );
179
- const propertyIri = optionalString(
180
- entry.propertyIri,
181
- `declarations[${index}].propertyIri`,
182
- );
183
- if (kind === 'content' && (!nodeIri || !propertyIri)) {
184
- throw new Error(
185
- `declarations[${index}] content entries require nodeIri and propertyIri.`,
186
- );
187
- }
188
- if (typeof entry.sourceText !== 'string') {
189
- throw new Error(`declarations[${index}].sourceText must be a string.`);
190
- }
191
- return {
192
- key: requiredString(entry.key, `declarations[${index}].key`),
193
- sourceText: entry.sourceText,
194
- kind,
195
- format: messageFormat(entry.format, `declarations[${index}].format`),
196
- ...(optionalString(
197
- entry.namespace,
198
- `declarations[${index}].namespace`,
199
- )
200
- ? {
201
- namespace: optionalString(
202
- entry.namespace,
203
- `declarations[${index}].namespace`,
204
- ),
205
- }
206
- : {}),
207
- ...(optionalString(
208
- entry.description,
209
- `declarations[${index}].description`,
210
- )
211
- ? {
212
- description: optionalString(
213
- entry.description,
214
- `declarations[${index}].description`,
215
- ),
216
- }
217
- : {}),
218
- ...(optionalString(entry.ownerIri, `declarations[${index}].ownerIri`)
219
- ? {
220
- ownerIri: optionalString(
221
- entry.ownerIri,
222
- `declarations[${index}].ownerIri`,
223
- ),
224
- }
225
- : {}),
226
- ...(optionalString(entry.shapeIri, `declarations[${index}].shapeIri`)
227
- ? {
228
- shapeIri: optionalString(
229
- entry.shapeIri,
230
- `declarations[${index}].shapeIri`,
231
- ),
232
- }
233
- : {}),
234
- ...(propertyIri ? { propertyIri } : {}),
235
- ...(nodeIri ? { nodeIri } : {}),
236
- };
237
- }
238
-
239
- /** Parse the portable v1 manifest used to declare dynamic translation keys. */
240
- export function parseTranslationDiscoveryManifest(
241
- value: unknown,
242
- ): TranslationDiscoveryManifest {
243
- const manifest = object(value, 'manifest');
244
- if (manifest.schemaVersion !== 1) {
245
- throw new Error('Translation discovery manifest schemaVersion must be 1.');
246
- }
247
- if (!Array.isArray(manifest.declarations)) {
248
- throw new Error('Translation discovery manifest declarations must be an array.');
249
- }
250
- return {
251
- schemaVersion: 1,
252
- sourceId: requiredString(manifest.sourceId, 'manifest.sourceId'),
253
- declarations: manifest.declarations.map(normalizeManifestEntry),
254
- };
255
- }
256
-
257
- /** Turn a portable manifest into an authoritative discovery source. */
258
- export function manifestTranslationDiscoverySource(
259
- value: unknown,
260
- ): TranslationDiscoverySource {
261
- const manifest = parseTranslationDiscoveryManifest(value);
262
- return {
263
- name: `manifest:${manifest.sourceId}`,
264
- async *discover() {
265
- for (const entry of manifest.declarations) {
266
- yield {
267
- schemaVersion: 1,
268
- key: entry.key,
269
- sourceText: entry.sourceText,
270
- kind: entry.kind ?? 'ui',
271
- format: entry.format ?? 'simple',
272
- ...(entry.namespace ? { namespace: entry.namespace } : {}),
273
- ...(entry.description ? { description: entry.description } : {}),
274
- provenance: {
275
- source: 'manifest',
276
- sourceId: manifest.sourceId,
277
- authoritative: true,
278
- ...(entry.ownerIri ? { ownerIri: entry.ownerIri } : {}),
279
- ...(entry.shapeIri ? { shapeIri: entry.shapeIri } : {}),
280
- ...(entry.propertyIri ? { propertyIri: entry.propertyIri } : {}),
281
- ...(entry.nodeIri ? { nodeIri: entry.nodeIri } : {}),
282
- },
283
- };
284
- }
285
- },
286
- };
287
- }
288
-
289
- function normalizeDeclaration(
290
- declaration: TranslationDeclaration,
291
- ): TranslationDeclaration {
292
- if (declaration?.schemaVersion !== 1) {
293
- throw new Error('Translation declaration schemaVersion must be 1.');
294
- }
295
- const kind = declarationKind(declaration.kind, 'declaration.kind');
296
- const provenance = object(
297
- declaration.provenance,
298
- 'declaration.provenance',
299
- ) as unknown as TranslationDeclarationProvenance;
300
- const nodeIri = optionalString(provenance.nodeIri, 'provenance.nodeIri');
301
- const propertyIri = optionalString(
302
- provenance.propertyIri,
303
- 'provenance.propertyIri',
304
- );
305
- if (kind === 'content' && (!nodeIri || !propertyIri)) {
306
- throw new Error(
307
- 'Content translation declarations require provenance.nodeIri and provenance.propertyIri.',
308
- );
309
- }
310
- if (typeof declaration.sourceText !== 'string') {
311
- throw new Error('declaration.sourceText must be a string.');
312
- }
313
- const source = provenance.source;
314
- if (
315
- source !== 'code' &&
316
- source !== 'manifest' &&
317
- source !== 'package' &&
318
- source !== 'linked' &&
319
- source !== 'import' &&
320
- source !== 'runtime'
321
- ) {
322
- throw new Error('declaration.provenance.source is invalid.');
323
- }
324
- if (typeof provenance.authoritative !== 'boolean') {
325
- throw new Error('declaration.provenance.authoritative must be boolean.');
326
- }
327
- return {
328
- schemaVersion: 1,
329
- key: requiredString(declaration.key, 'declaration.key'),
330
- sourceText: declaration.sourceText,
331
- kind,
332
- format: messageFormat(declaration.format, 'declaration.format'),
333
- ...(optionalString(declaration.namespace, 'declaration.namespace')
334
- ? { namespace: declaration.namespace!.trim() }
335
- : {}),
336
- ...(optionalString(declaration.description, 'declaration.description')
337
- ? { description: declaration.description!.trim() }
338
- : {}),
339
- provenance: {
340
- source,
341
- sourceId: requiredString(provenance.sourceId, 'provenance.sourceId'),
342
- authoritative: provenance.authoritative,
343
- ...(optionalString(provenance.file, 'provenance.file')
344
- ? { file: provenance.file!.trim() }
345
- : {}),
346
- ...(provenance.line === undefined
347
- ? {}
348
- : Number.isSafeInteger(provenance.line) && provenance.line > 0
349
- ? { line: provenance.line }
350
- : (() => {
351
- throw new Error('provenance.line must be a positive integer.');
352
- })()),
353
- ...(optionalString(provenance.ownerIri, 'provenance.ownerIri')
354
- ? { ownerIri: provenance.ownerIri!.trim() }
355
- : {}),
356
- ...(optionalString(provenance.shapeIri, 'provenance.shapeIri')
357
- ? { shapeIri: provenance.shapeIri!.trim() }
358
- : {}),
359
- ...(propertyIri ? { propertyIri } : {}),
360
- ...(nodeIri ? { nodeIri } : {}),
361
- ...(optionalString(provenance.fromPackage, 'provenance.fromPackage')
362
- ? { fromPackage: provenance.fromPackage!.trim() }
363
- : {}),
364
- ...(optionalString(provenance.targetClass, 'provenance.targetClass')
365
- ? { targetClass: provenance.targetClass!.trim() }
366
- : {}),
367
- },
368
- };
369
- }
370
-
371
- /** Stable identity: UI/semantic by namespace+key; content by node+field IRI. */
372
- export function translationDeclarationIdentity(
373
- appId: string,
374
- declaration: TranslationDeclaration,
375
- ): string {
376
- const normalizedAppId = requiredString(appId, 'appId');
377
- if (declaration.kind === 'content') {
378
- return `content:${JSON.stringify([
379
- normalizedAppId,
380
- declaration.provenance.nodeIri,
381
- declaration.provenance.propertyIri,
382
- ])}`;
383
- }
384
- return `message:${JSON.stringify([
385
- normalizedAppId,
386
- declaration.namespace ?? '',
387
- declaration.key,
388
- ])}`;
389
- }
390
-
391
- function canonicalProvenance(
392
- provenance: TranslationDeclarationProvenance,
393
- ): string {
394
- return JSON.stringify([
395
- provenance.source,
396
- provenance.sourceId,
397
- provenance.authoritative,
398
- provenance.file ?? '',
399
- provenance.line ?? 0,
400
- provenance.ownerIri ?? '',
401
- provenance.shapeIri ?? '',
402
- provenance.propertyIri ?? '',
403
- provenance.nodeIri ?? '',
404
- provenance.fromPackage ?? '',
405
- provenance.targetClass ?? '',
406
- ]);
407
- }
408
-
409
- function sortDeclarations(
410
- declarations: TranslationDeclaration[],
411
- ): TranslationDeclaration[] {
412
- return [...declarations].sort((left, right) => {
413
- const source = canonicalProvenance(left.provenance).localeCompare(
414
- canonicalProvenance(right.provenance),
415
- );
416
- if (source !== 0) return source;
417
- return JSON.stringify([
418
- left.sourceText,
419
- left.format,
420
- left.kind,
421
- left.key,
422
- ]).localeCompare(
423
- JSON.stringify([
424
- right.sourceText,
425
- right.format,
426
- right.kind,
427
- right.key,
428
- ]),
429
- );
430
- });
431
- }
432
-
433
- function uniqueOrigins(
434
- declarations: TranslationDeclaration[],
435
- ): TranslationDeclarationProvenance[] {
436
- return [
437
- ...new Map(
438
- sortDeclarations(declarations).map(({ provenance }) => [
439
- canonicalProvenance(provenance),
440
- provenance,
441
- ]),
442
- ).values(),
443
- ];
444
- }
445
-
446
- function itemFromDeclarations(
447
- appId: string,
448
- identity: string,
449
- declarations: TranslationDeclaration[],
450
- ): {
451
- item: TranslationInventoryItem;
452
- conflict?: TranslationInventoryConflict;
453
- } {
454
- const ordered = sortDeclarations(declarations);
455
- const authoritative = ordered.filter(
456
- ({ provenance }) => provenance.authoritative,
457
- );
458
- const contracts = new Set(
459
- authoritative.map(({ sourceText, format }) =>
460
- JSON.stringify([sourceText, format]),
461
- ),
462
- );
463
- const preferred = authoritative[0] ?? ordered[0];
464
- const conflict = contracts.size > 1;
465
- const status: TranslationInventoryStatus = conflict
466
- ? 'conflict'
467
- : authoritative.length
468
- ? 'confirmed'
469
- : 'pending';
470
- const kind = ordered.reduce(
471
- (current, declaration) =>
472
- KIND_ORDER[declaration.kind] > KIND_ORDER[current]
473
- ? declaration.kind
474
- : current,
475
- preferred.kind,
476
- );
477
- const item: TranslationInventoryItem = {
478
- identity,
479
- appId,
480
- key: preferred.key,
481
- sourceText: preferred.sourceText,
482
- kind,
483
- format: preferred.format,
484
- status,
485
- authoritative: authoritative.length > 0,
486
- exchangeEligible:
487
- status === 'confirmed' && kind !== 'content',
488
- buildEligible:
489
- status === 'confirmed' && kind !== 'content',
490
- origins: uniqueOrigins(ordered),
491
- ...(preferred.namespace ? { namespace: preferred.namespace } : {}),
492
- ...(preferred.description ? { description: preferred.description } : {}),
493
- };
494
- return {
495
- item,
496
- ...(conflict ? { conflict: { identity, declarations: ordered } } : {}),
497
- };
498
- }
499
-
500
- function buildDeclaration(
501
- item: TranslationInventoryItem,
502
- ): TranslationBuildDeclaration {
503
- const first = item.origins[0];
504
- return {
505
- key: item.key,
506
- sourceText: item.sourceText,
507
- format: item.format,
508
- kind: item.kind,
509
- status: 'confirmed' satisfies TranslationDeclarationStatus,
510
- authoritative: true,
511
- ...(item.namespace ? { namespace: item.namespace } : {}),
512
- ...(first?.file ? { file: first.file } : {}),
513
- ...(first?.line ? { line: first.line } : {}),
514
- };
515
- }
516
-
517
- /** Build-facing confirmed subset of a persisted or freshly discovered inventory. */
518
- export function translationBuildDeclarationsFromInventory(
519
- items: Iterable<TranslationInventoryItem>,
520
- ): TranslationBuildDeclaration[] {
521
- return [...items]
522
- .filter(({ buildEligible, status }) => buildEligible && status === 'confirmed')
523
- .map(buildDeclaration)
524
- .sort((left, right) =>
525
- JSON.stringify([left.namespace ?? '', left.key]).localeCompare(
526
- JSON.stringify([right.namespace ?? '', right.key]),
527
- ),
528
- );
529
- }
530
-
531
- /**
532
- * Discover and deterministically merge every source. This function is pure
533
- * with respect to storage: hosts decide how snapshots and lifecycle changes
534
- * are persisted.
535
- */
536
- export async function discoverTranslationInventory(
537
- sources: Iterable<TranslationDiscoverySource>,
538
- context: TranslationDiscoveryContext,
539
- options: DiscoverTranslationInventoryOptions = {},
540
- ): Promise<TranslationInventorySnapshot> {
541
- const appId = requiredString(context.appId, 'context.appId');
542
- const byIdentity = new Map<string, TranslationDeclaration[]>();
543
- const orderedSources = [...sources].sort((left, right) =>
544
- left.name.localeCompare(right.name),
545
- );
546
- for (const source of orderedSources) {
547
- for await (const raw of source.discover({ ...context, appId })) {
548
- const declaration = normalizeDeclaration(raw);
549
- const identity = translationDeclarationIdentity(appId, declaration);
550
- const values = byIdentity.get(identity) ?? [];
551
- values.push(declaration);
552
- byIdentity.set(identity, values);
553
- }
554
- }
555
-
556
- const items: TranslationInventoryItem[] = [];
557
- const conflicts: TranslationInventoryConflict[] = [];
558
- for (const identity of [...byIdentity.keys()].sort()) {
559
- const merged = itemFromDeclarations(
560
- appId,
561
- identity,
562
- byIdentity.get(identity)!,
563
- );
564
- items.push(merged.item);
565
- if (merged.conflict) conflicts.push(merged.conflict);
566
- }
567
-
568
- const currentIdentities = new Set(byIdentity.keys());
569
- for (const previous of options.previous?.items ?? []) {
570
- if (
571
- previous.appId !== appId ||
572
- currentIdentities.has(previous.identity) ||
573
- previous.status === 'pending'
574
- ) {
575
- continue;
576
- }
577
- items.push({
578
- ...previous,
579
- status: 'orphaned',
580
- buildEligible: false,
581
- exchangeEligible: false,
582
- });
583
- }
584
- items.sort((left, right) => left.identity.localeCompare(right.identity));
585
-
586
- return {
587
- schemaVersion: 1,
588
- appId,
589
- ...(context.branchId ? { branchId: context.branchId } : {}),
590
- items,
591
- conflicts,
592
- buildDeclarations: translationBuildDeclarationsFromInventory(items),
593
- };
594
- }