@_linked/translation 0.2.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.
Files changed (153) hide show
  1. package/README.md +102 -0
  2. package/lib/cjs/data/translation.json +8 -0
  3. package/lib/esm/archive.d.ts +133 -0
  4. package/lib/esm/archive.js +567 -0
  5. package/lib/esm/archive.js.map +1 -0
  6. package/lib/esm/authorization.d.ts +12 -0
  7. package/lib/esm/authorization.js +10 -0
  8. package/lib/esm/authorization.js.map +1 -0
  9. package/lib/esm/backend.d.ts +1 -0
  10. package/lib/esm/backend.js +3 -0
  11. package/lib/esm/backend.js.map +1 -0
  12. package/lib/esm/cdn-loader.d.ts +40 -0
  13. package/lib/esm/cdn-loader.js +200 -0
  14. package/lib/esm/cdn-loader.js.map +1 -0
  15. package/lib/esm/checks.d.ts +56 -0
  16. package/lib/esm/checks.js +288 -0
  17. package/lib/esm/checks.js.map +1 -0
  18. package/lib/esm/compile.d.ts +64 -0
  19. package/lib/esm/compile.js +99 -0
  20. package/lib/esm/compile.js.map +1 -0
  21. package/lib/esm/core/messages.d.ts +25 -0
  22. package/lib/esm/core/messages.js +76 -0
  23. package/lib/esm/core/messages.js.map +1 -0
  24. package/lib/esm/data/translation.json +8 -0
  25. package/lib/esm/discovery.d.ts +97 -0
  26. package/lib/esm/discovery.js +365 -0
  27. package/lib/esm/discovery.js.map +1 -0
  28. package/lib/esm/exchange.d.ts +100 -0
  29. package/lib/esm/exchange.js +223 -0
  30. package/lib/esm/exchange.js.map +1 -0
  31. package/lib/esm/export.d.ts +28 -0
  32. package/lib/esm/export.js +92 -0
  33. package/lib/esm/export.js.map +1 -0
  34. package/lib/esm/formats/csv.d.ts +8 -0
  35. package/lib/esm/formats/csv.js +397 -0
  36. package/lib/esm/formats/csv.js.map +1 -0
  37. package/lib/esm/formats/json.d.ts +30 -0
  38. package/lib/esm/formats/json.js +459 -0
  39. package/lib/esm/formats/json.js.map +1 -0
  40. package/lib/esm/formats/xliff.d.ts +12 -0
  41. package/lib/esm/formats/xliff.js +577 -0
  42. package/lib/esm/formats/xliff.js.map +1 -0
  43. package/lib/esm/import.d.ts +197 -0
  44. package/lib/esm/import.js +391 -0
  45. package/lib/esm/import.js.map +1 -0
  46. package/lib/esm/index.d.ts +42 -0
  47. package/lib/esm/index.js +34 -0
  48. package/lib/esm/index.js.map +1 -0
  49. package/lib/esm/key-sync-node.d.ts +29 -0
  50. package/lib/esm/key-sync-node.js +174 -0
  51. package/lib/esm/key-sync-node.js.map +1 -0
  52. package/lib/esm/key-sync.d.ts +46 -0
  53. package/lib/esm/key-sync.js +114 -0
  54. package/lib/esm/key-sync.js.map +1 -0
  55. package/lib/esm/key-version.d.ts +50 -0
  56. package/lib/esm/key-version.js +134 -0
  57. package/lib/esm/key-version.js.map +1 -0
  58. package/lib/esm/languages.d.ts +18 -0
  59. package/lib/esm/languages.js +74 -0
  60. package/lib/esm/languages.js.map +1 -0
  61. package/lib/esm/linked-discovery.d.ts +44 -0
  62. package/lib/esm/linked-discovery.js +267 -0
  63. package/lib/esm/linked-discovery.js.map +1 -0
  64. package/lib/esm/message-format.d.ts +41 -0
  65. package/lib/esm/message-format.js +133 -0
  66. package/lib/esm/message-format.js.map +1 -0
  67. package/lib/esm/mt.d.ts +111 -0
  68. package/lib/esm/mt.js +142 -0
  69. package/lib/esm/mt.js.map +1 -0
  70. package/lib/esm/ontologies/translation.d.ts +203 -0
  71. package/lib/esm/ontologies/translation.js +219 -0
  72. package/lib/esm/ontologies/translation.js.map +1 -0
  73. package/lib/esm/package.d.ts +4 -0
  74. package/lib/esm/package.js +11 -0
  75. package/lib/esm/package.js.map +1 -0
  76. package/lib/esm/react.d.ts +55 -0
  77. package/lib/esm/react.js +127 -0
  78. package/lib/esm/react.js.map +1 -0
  79. package/lib/esm/records.d.ts +151 -0
  80. package/lib/esm/records.js +2 -0
  81. package/lib/esm/records.js.map +1 -0
  82. package/lib/esm/release.d.ts +198 -0
  83. package/lib/esm/release.js +435 -0
  84. package/lib/esm/release.js.map +1 -0
  85. package/lib/esm/shape-catalog.d.ts +79 -0
  86. package/lib/esm/shape-catalog.js +118 -0
  87. package/lib/esm/shape-catalog.js.map +1 -0
  88. package/lib/esm/shapes/GlossaryTerm.d.ts +28 -0
  89. package/lib/esm/shapes/GlossaryTerm.js +94 -0
  90. package/lib/esm/shapes/GlossaryTerm.js.map +1 -0
  91. package/lib/esm/shapes/TranslationInventoryOrigin.d.ts +33 -0
  92. package/lib/esm/shapes/TranslationInventoryOrigin.js +209 -0
  93. package/lib/esm/shapes/TranslationInventoryOrigin.js.map +1 -0
  94. package/lib/esm/shapes/TranslationKey.d.ts +92 -0
  95. package/lib/esm/shapes/TranslationKey.js +194 -0
  96. package/lib/esm/shapes/TranslationKey.js.map +1 -0
  97. package/lib/esm/shapes/TranslationKeyVersion.d.ts +19 -0
  98. package/lib/esm/shapes/TranslationKeyVersion.js +125 -0
  99. package/lib/esm/shapes/TranslationKeyVersion.js.map +1 -0
  100. package/lib/esm/shapes/TranslationLanguage.d.ts +15 -0
  101. package/lib/esm/shapes/TranslationLanguage.js +102 -0
  102. package/lib/esm/shapes/TranslationLanguage.js.map +1 -0
  103. package/lib/esm/shapes/TranslationProvider.d.ts +409 -0
  104. package/lib/esm/shapes/TranslationProvider.js +1376 -0
  105. package/lib/esm/shapes/TranslationProvider.js.map +1 -0
  106. package/lib/esm/shapes/TranslationRelease.d.ts +36 -0
  107. package/lib/esm/shapes/TranslationRelease.js +149 -0
  108. package/lib/esm/shapes/TranslationRelease.js.map +1 -0
  109. package/lib/esm/shapes/TranslationRevision.d.ts +39 -0
  110. package/lib/esm/shapes/TranslationRevision.js +159 -0
  111. package/lib/esm/shapes/TranslationRevision.js.map +1 -0
  112. package/lib/esm/shapes/TranslationUnit.d.ts +26 -0
  113. package/lib/esm/shapes/TranslationUnit.js +111 -0
  114. package/lib/esm/shapes/TranslationUnit.js.map +1 -0
  115. package/package.json +134 -0
  116. package/src/archive.ts +894 -0
  117. package/src/authorization.ts +33 -0
  118. package/src/backend.ts +2 -0
  119. package/src/cdn-loader.ts +264 -0
  120. package/src/checks.ts +413 -0
  121. package/src/compile.ts +168 -0
  122. package/src/core/messages.ts +106 -0
  123. package/src/data/translation.json +8 -0
  124. package/src/discovery.ts +594 -0
  125. package/src/exchange.ts +428 -0
  126. package/src/export.ts +135 -0
  127. package/src/formats/csv.ts +459 -0
  128. package/src/formats/json.ts +587 -0
  129. package/src/formats/xliff.ts +708 -0
  130. package/src/import.ts +648 -0
  131. package/src/index.ts +207 -0
  132. package/src/key-sync-node.ts +269 -0
  133. package/src/key-sync.ts +168 -0
  134. package/src/key-version.ts +206 -0
  135. package/src/languages.ts +102 -0
  136. package/src/linked-discovery.ts +385 -0
  137. package/src/message-format.ts +214 -0
  138. package/src/mt.ts +248 -0
  139. package/src/ontologies/translation.ts +234 -0
  140. package/src/package.ts +19 -0
  141. package/src/react.tsx +250 -0
  142. package/src/records.ts +180 -0
  143. package/src/release.ts +726 -0
  144. package/src/shape-catalog.ts +198 -0
  145. package/src/shapes/GlossaryTerm.ts +61 -0
  146. package/src/shapes/TranslationInventoryOrigin.ts +131 -0
  147. package/src/shapes/TranslationKey.ts +174 -0
  148. package/src/shapes/TranslationKeyVersion.ts +77 -0
  149. package/src/shapes/TranslationLanguage.ts +51 -0
  150. package/src/shapes/TranslationProvider.ts +1755 -0
  151. package/src/shapes/TranslationRelease.ts +114 -0
  152. package/src/shapes/TranslationRevision.ts +105 -0
  153. package/src/shapes/TranslationUnit.ts +72 -0
@@ -0,0 +1,198 @@
1
+ import type {
2
+ TranslationEntryRecord,
3
+ TranslationState,
4
+ } from './records.js';
5
+ import type { TranslationDiscoverySource } from './discovery.js';
6
+
7
+ /**
8
+ * Code-canonical translations shipped beside one reusable LINKED shape.
9
+ * Packages may expose this object from code or serialize the same contract as
10
+ * JSON for Host Agent discovery without executing package code.
11
+ */
12
+ export interface ShapeTranslationCatalog {
13
+ schemaVersion: 1;
14
+ packageName: string;
15
+ /** Canonical SHACL node-shape IRI, not a display label or filesystem path. */
16
+ shapeIri: string;
17
+ /** Optional stable domain-class IRI used for semantic browsing. */
18
+ targetClass?: string;
19
+ entries: ShapeTranslationCatalogEntry[];
20
+ }
21
+
22
+ export interface ShapeTranslationCatalogEntry {
23
+ key: string;
24
+ sourceText: string;
25
+ description?: string;
26
+ namespace?: string;
27
+ /** Stable predicate IRI when this message describes a shape property. */
28
+ propertyIri?: string;
29
+ format?: 'simple' | 'icu';
30
+ /** Curated BCP-47 language values supplied by the package. */
31
+ translations?: Record<
32
+ string,
33
+ string | { text: string; state?: TranslationState }
34
+ >;
35
+ }
36
+
37
+ export interface ShapeTranslationCatalogTarget {
38
+ listEntries(data: { appId: string }): Promise<TranslationEntryRecord[]>;
39
+ upsertKey(data: {
40
+ appId: string;
41
+ key: string;
42
+ sourceText: string;
43
+ namespace?: string;
44
+ description?: string;
45
+ kind: 'ui';
46
+ format?: 'simple' | 'icu';
47
+ ofShape: string;
48
+ ofProperty?: string;
49
+ fromPackage: string;
50
+ }): Promise<unknown>;
51
+ upsertUnit(data: {
52
+ appId: string;
53
+ key: string;
54
+ language: string;
55
+ text: string;
56
+ state: TranslationState;
57
+ }): Promise<unknown>;
58
+ }
59
+
60
+ export interface ShapeTranslationInstallReport {
61
+ createdKeys: string[];
62
+ createdUnits: Array<{ key: string; language: string }>;
63
+ skippedExistingKeys: string[];
64
+ skippedExistingUnits: Array<{ key: string; language: string }>;
65
+ }
66
+
67
+ function requireString(value: unknown, label: string): string {
68
+ if (typeof value !== 'string' || !value.trim()) {
69
+ throw new Error(`${label} is required.`);
70
+ }
71
+ return value.trim();
72
+ }
73
+
74
+ /** Validate and freeze the package-owned declaration at module load time. */
75
+ export function defineShapeTranslationCatalog(
76
+ catalog: ShapeTranslationCatalog,
77
+ ): Readonly<ShapeTranslationCatalog> {
78
+ if (catalog?.schemaVersion !== 1) {
79
+ throw new Error('Shape translation catalog schemaVersion must be 1.');
80
+ }
81
+ requireString(catalog.packageName, 'packageName');
82
+ requireString(catalog.shapeIri, 'shapeIri');
83
+ if (!Array.isArray(catalog.entries)) {
84
+ throw new Error('Shape translation catalog entries must be an array.');
85
+ }
86
+ const keys = new Set<string>();
87
+ for (const entry of catalog.entries) {
88
+ const key = requireString(entry?.key, 'entry.key');
89
+ requireString(entry?.sourceText, `sourceText for ${key}`);
90
+ if (keys.has(key)) throw new Error(`Duplicate shape translation key "${key}".`);
91
+ keys.add(key);
92
+ }
93
+ return Object.freeze(catalog);
94
+ }
95
+
96
+ /** Adapt the existing schema-v1 catalog without changing its install contract. */
97
+ export function shapeCatalogTranslationDiscoverySource(
98
+ input: ShapeTranslationCatalog,
99
+ ): TranslationDiscoverySource {
100
+ const catalog = defineShapeTranslationCatalog(input);
101
+ return {
102
+ name: `shape-catalog:${catalog.packageName}:${catalog.shapeIri}`,
103
+ async *discover() {
104
+ for (const entry of catalog.entries) {
105
+ yield {
106
+ schemaVersion: 1,
107
+ key: entry.key,
108
+ sourceText: entry.sourceText,
109
+ kind: 'ui',
110
+ format: entry.format ?? 'simple',
111
+ ...(entry.namespace ? { namespace: entry.namespace } : {}),
112
+ ...(entry.description ? { description: entry.description } : {}),
113
+ provenance: {
114
+ source: 'package',
115
+ sourceId: `${catalog.packageName}:${catalog.shapeIri}`,
116
+ authoritative: true,
117
+ ownerIri: catalog.shapeIri,
118
+ shapeIri: catalog.shapeIri,
119
+ fromPackage: catalog.packageName,
120
+ ...(catalog.targetClass
121
+ ? { targetClass: catalog.targetClass }
122
+ : {}),
123
+ ...(entry.propertyIri
124
+ ? { propertyIri: entry.propertyIri }
125
+ : {}),
126
+ },
127
+ };
128
+ }
129
+ },
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Seed only missing app values for the currently activated languages.
135
+ * Existing project-authored keys and units always win over package defaults.
136
+ */
137
+ export async function installShapeTranslationCatalog(
138
+ target: ShapeTranslationCatalogTarget,
139
+ input: {
140
+ appId: string;
141
+ catalog: ShapeTranslationCatalog;
142
+ activeLanguages: Iterable<string>;
143
+ },
144
+ ): Promise<ShapeTranslationInstallReport> {
145
+ const appId = requireString(input.appId, 'appId');
146
+ const catalog = defineShapeTranslationCatalog(input.catalog);
147
+ const activeLanguages = new Set(
148
+ [...input.activeLanguages].map((language) => language.trim()).filter(Boolean),
149
+ );
150
+ const existing = new Map(
151
+ (await target.listEntries({ appId })).map((entry) => [entry.key, entry]),
152
+ );
153
+ const report: ShapeTranslationInstallReport = {
154
+ createdKeys: [],
155
+ createdUnits: [],
156
+ skippedExistingKeys: [],
157
+ skippedExistingUnits: [],
158
+ };
159
+
160
+ for (const entry of catalog.entries) {
161
+ const current = existing.get(entry.key);
162
+ if (current) report.skippedExistingKeys.push(entry.key);
163
+ else {
164
+ await target.upsertKey({
165
+ appId,
166
+ key: entry.key,
167
+ sourceText: entry.sourceText,
168
+ namespace: entry.namespace,
169
+ description: entry.description,
170
+ kind: 'ui',
171
+ format: entry.format,
172
+ ofShape: catalog.shapeIri,
173
+ ofProperty: entry.propertyIri,
174
+ fromPackage: catalog.packageName,
175
+ });
176
+ report.createdKeys.push(entry.key);
177
+ }
178
+
179
+ for (const language of [...activeLanguages].sort()) {
180
+ const value = entry.translations?.[language];
181
+ if (value === undefined) continue;
182
+ if (current?.units[language]) {
183
+ report.skippedExistingUnits.push({ key: entry.key, language });
184
+ continue;
185
+ }
186
+ const unit = typeof value === 'string' ? { text: value } : value;
187
+ await target.upsertUnit({
188
+ appId,
189
+ key: entry.key,
190
+ language,
191
+ text: unit.text,
192
+ state: unit.state ?? 'reviewed',
193
+ });
194
+ report.createdUnits.push({ key: entry.key, language });
195
+ }
196
+ }
197
+ return report;
198
+ }
@@ -0,0 +1,61 @@
1
+ import { Shape } from '@_linked/core/shapes/Shape';
2
+ import { literalProperty } from '@_linked/core/shapes/SHACL';
3
+ import { tr } from '../ontologies/translation.js';
4
+ import { linkedShape } from '../package.js';
5
+
6
+ /**
7
+ * A glossary entry that steers machine translation (Plan 014 AD-L) — e.g. keep
8
+ * a brand/term consistent or fixed across languages. Fed to the MT provider's
9
+ * prompt. Per-app content like the rest of the translation model.
10
+ */
11
+ @linkedShape
12
+ export class GlossaryTerm extends Shape {
13
+ static targetClass = tr.GlossaryTerm;
14
+
15
+ /** The source term (English). */
16
+ @literalProperty({ path: tr.term, maxCount: 1 })
17
+ get term(): string {
18
+ return '';
19
+ }
20
+
21
+ /** Preferred translation for the target language (optional; empty = keep as-is). */
22
+ @literalProperty({ path: tr.glossaryTranslation, maxCount: 1 })
23
+ get translation(): string {
24
+ return '';
25
+ }
26
+
27
+ /** Target language this entry applies to (empty = all). */
28
+ @literalProperty({ path: tr.glossaryLanguage, maxCount: 1 })
29
+ get language(): string {
30
+ return '';
31
+ }
32
+
33
+ @literalProperty({ path: tr.description, maxCount: 1 })
34
+ get description(): string {
35
+ return '';
36
+ }
37
+
38
+ /**
39
+ * Termbase entry type: 'prefer' advises {@link translation}; 'require'
40
+ * enforces that wording whenever the source uses the term; 'keep' =
41
+ * do-not-translate, preserved exactly (proper
42
+ * nouns, stay-one-language words); 'forbid' = never use this word/spelling —
43
+ * {@link useInstead} names the replacement (alternative-spelling policing).
44
+ */
45
+ @literalProperty({ path: tr.termType, maxCount: 1 })
46
+ get termType(): string {
47
+ return 'prefer';
48
+ }
49
+
50
+ /** Replacement wording for a 'forbid' entry. */
51
+ @literalProperty({ path: tr.useInstead, maxCount: 1 })
52
+ get useInstead(): string {
53
+ return '';
54
+ }
55
+
56
+ /** Match with exact casing (proper nouns / all-caps brand styling). */
57
+ @literalProperty({ path: tr.caseSensitive, maxCount: 1 })
58
+ get caseSensitive(): boolean {
59
+ return false;
60
+ }
61
+ }
@@ -0,0 +1,131 @@
1
+ import { Shape } from '@_linked/core/shapes/Shape';
2
+ import { literalProperty, objectProperty } from '@_linked/core/shapes/SHACL';
3
+ import { xsd } from '@_linked/core/ontologies/xsd';
4
+ import { tr } from '../ontologies/translation.js';
5
+ import { linkedShape } from '../package.js';
6
+
7
+ /**
8
+ * One observed origin for a branch translation-inventory item.
9
+ *
10
+ * Host code executes queries for this shape against the explicit branch
11
+ * metadata dataset. It is intentionally not globally pinned by the portable
12
+ * package: standalone LINKED apps may use a different metadata topology.
13
+ */
14
+ @linkedShape
15
+ export class TranslationInventoryOrigin extends Shape {
16
+ static targetClass = tr.TranslationInventoryOrigin;
17
+
18
+ @objectProperty({ path: tr.ofApplication, maxCount: 1 })
19
+ get ofApplication(): string {
20
+ return '';
21
+ }
22
+
23
+ @objectProperty({ path: tr.inventoryBranch, maxCount: 1 })
24
+ get branch(): string {
25
+ return '';
26
+ }
27
+
28
+ @literalProperty({ path: tr.inventoryIdentity, maxCount: 1 })
29
+ get identity(): string {
30
+ return '';
31
+ }
32
+
33
+ @literalProperty({ path: tr.key, maxCount: 1 })
34
+ get key(): string {
35
+ return '';
36
+ }
37
+
38
+ @literalProperty({ path: tr.namespace, maxCount: 1 })
39
+ get namespace(): string {
40
+ return '';
41
+ }
42
+
43
+ @literalProperty({ path: tr.sourceText, maxCount: 1 })
44
+ get sourceText(): string {
45
+ return '';
46
+ }
47
+
48
+ @literalProperty({ path: tr.kind, maxCount: 1 })
49
+ get kind(): string {
50
+ return 'ui';
51
+ }
52
+
53
+ @literalProperty({ path: tr.format, maxCount: 1 })
54
+ get format(): string {
55
+ return 'simple';
56
+ }
57
+
58
+ @literalProperty({ path: tr.description, maxCount: 1 })
59
+ get description(): string {
60
+ return '';
61
+ }
62
+
63
+ @literalProperty({ path: tr.inventoryStatus, maxCount: 1 })
64
+ get status(): string {
65
+ return 'pending';
66
+ }
67
+
68
+ @literalProperty({ path: tr.inventorySource, maxCount: 1 })
69
+ get sourceKind(): string {
70
+ return '';
71
+ }
72
+
73
+ @literalProperty({ path: tr.inventorySourceId, maxCount: 1 })
74
+ get sourceId(): string {
75
+ return '';
76
+ }
77
+
78
+ @literalProperty({ path: tr.inventoryAuthoritative, maxCount: 1 })
79
+ get authoritative(): boolean {
80
+ return false;
81
+ }
82
+
83
+ @literalProperty({ path: tr.inventoryFile, maxCount: 1 })
84
+ get file(): string {
85
+ return '';
86
+ }
87
+
88
+ @literalProperty({ path: tr.inventoryLine, datatype: xsd.integer, maxCount: 1 })
89
+ get line(): number {
90
+ return 0;
91
+ }
92
+
93
+ @objectProperty({ path: tr.inventoryOwner, maxCount: 1 })
94
+ get ownerIri(): string {
95
+ return '';
96
+ }
97
+
98
+ @objectProperty({ path: tr.inventoryShape, maxCount: 1 })
99
+ get shapeIri(): string {
100
+ return '';
101
+ }
102
+
103
+ @objectProperty({ path: tr.inventoryProperty, maxCount: 1 })
104
+ get propertyIri(): string {
105
+ return '';
106
+ }
107
+
108
+ @objectProperty({ path: tr.inventoryNode, maxCount: 1 })
109
+ get nodeIri(): string {
110
+ return '';
111
+ }
112
+
113
+ @literalProperty({ path: tr.fromPackage, maxCount: 1 })
114
+ get fromPackage(): string {
115
+ return '';
116
+ }
117
+
118
+ @literalProperty({ path: tr.inventoryTargetClass, maxCount: 1 })
119
+ get targetClass(): string {
120
+ return '';
121
+ }
122
+
123
+ @literalProperty({
124
+ path: tr.inventoryObservedAt,
125
+ datatype: xsd.dateTime,
126
+ maxCount: 1,
127
+ })
128
+ get observedAt(): Date {
129
+ return new Date(0);
130
+ }
131
+ }
@@ -0,0 +1,174 @@
1
+ import { Shape } from '@_linked/core/shapes/Shape';
2
+ import { literalProperty, objectProperty } from '@_linked/core/shapes/SHACL';
3
+ import { Server } from '@_linked/server-utils/utils/Server';
4
+ import { tr } from '../ontologies/translation.js';
5
+ import { linkedShape } from '../package.js';
6
+ import type {
7
+ TranslationEntryRecord,
8
+ TranslationKeyVersionRecord,
9
+ TranslationState,
10
+ } from '../records.js';
11
+ import type { TranslationMessages } from '../core/messages.js';
12
+ import type { TranslationKeyVersion } from './TranslationKeyVersion.js';
13
+
14
+ /**
15
+ * A translatable key (Plan 014 AD-H). Covers both UI-string keys (`kind:'ui'`)
16
+ * and graph-content translations (`kind:'content'` + `ofNode`/`ofField`), unified
17
+ * in one scheme. `sourceText` is the canonical English (R7); `format` pins the
18
+ * message syntax so imported FormatSimple strings aren't reinterpreted as ICU
19
+ * (R8). Named `TranslationKey` (its per-language values are {@link TranslationUnit}s,
20
+ * not `Translation` — avoids collision with serve's legacy shape, R13).
21
+ */
22
+ @linkedShape
23
+ export class TranslationKey extends Shape {
24
+ static targetClass = tr.TranslationKey;
25
+
26
+ /** Dotted namespace grouping, e.g. "account". */
27
+ @literalProperty({ path: tr.namespace, maxCount: 1 })
28
+ get namespace(): string {
29
+ return '';
30
+ }
31
+
32
+ /** The key path, e.g. "account.title". */
33
+ @literalProperty({ path: tr.key, maxCount: 1 })
34
+ get key(): string {
35
+ return '';
36
+ }
37
+
38
+ /** Canonical English source string. */
39
+ @literalProperty({ path: tr.sourceText, maxCount: 1 })
40
+ get sourceText(): string {
41
+ return '';
42
+ }
43
+
44
+ @literalProperty({ path: tr.description, maxCount: 1 })
45
+ get description(): string {
46
+ return '';
47
+ }
48
+
49
+ /** 'ui' (chrome string) | 'content' (graph field). */
50
+ @literalProperty({ path: tr.kind, maxCount: 1 })
51
+ get kind(): string {
52
+ return 'ui';
53
+ }
54
+
55
+ /** IRI of the owning Application (translation content is app-scoped, AD-I). */
56
+ @objectProperty({ path: tr.ofApplication, maxCount: 1 })
57
+ get ofApplication(): string {
58
+ return '';
59
+ }
60
+
61
+ /** For kind:'content' — the IRI of the translated subject node. */
62
+ @objectProperty({ path: tr.ofNode, maxCount: 1 })
63
+ get ofNode(): string {
64
+ return '';
65
+ }
66
+
67
+ /** For kind:'content' — the field/predicate being translated. */
68
+ @literalProperty({ path: tr.ofField, maxCount: 1 })
69
+ get ofField(): string {
70
+ return '';
71
+ }
72
+
73
+ /** SHACL node-shape IRI this packaged key accompanies. */
74
+ @objectProperty({ path: tr.ofShape, maxCount: 1 })
75
+ get ofShape(): string {
76
+ return '';
77
+ }
78
+
79
+ /** Stable predicate IRI when this key describes one shape property. */
80
+ @objectProperty({ path: tr.ofProperty, maxCount: 1 })
81
+ get ofProperty(): string {
82
+ return '';
83
+ }
84
+
85
+ /** Reusable package that supplied the seed value. */
86
+ @literalProperty({ path: tr.fromPackage, maxCount: 1 })
87
+ get fromPackage(): string {
88
+ return '';
89
+ }
90
+
91
+ /**
92
+ * True when this app copy of a shape-owned key has been edited locally and
93
+ * should NOT be overwritten by shape-route propagation (AD-N: "never
94
+ * overwrites an app's own override"). Set when the per-app editor changes the
95
+ * source of a key that carries `ofShape`.
96
+ */
97
+ @literalProperty({ path: tr.overridden, maxCount: 1 })
98
+ get overridden(): boolean {
99
+ return false;
100
+ }
101
+
102
+ /**
103
+ * The shape's canonical source captured the moment this app first overrode the
104
+ * key — what "clear override" reverts to when no non-overridden sibling copy is
105
+ * available to read the current canonical from (AD-N).
106
+ */
107
+ @literalProperty({ path: tr.shapeSource, maxCount: 1 })
108
+ get shapeSource(): string {
109
+ return '';
110
+ }
111
+
112
+ /** Message syntax: 'simple' (FormatSimple) | 'icu'. */
113
+ @literalProperty({ path: tr.format, maxCount: 1 })
114
+ get format(): string {
115
+ return 'simple';
116
+ }
117
+
118
+ /** Current immutable source/argument contract. Populated by Plan 018 migration. */
119
+ @objectProperty({ path: tr.currentVersion, maxCount: 1 })
120
+ get currentVersion(): TranslationKeyVersion {
121
+ return undefined as any;
122
+ }
123
+
124
+ static list(data: { appId: string }) {
125
+ return Server.call(this, 'listKeys', data);
126
+ }
127
+
128
+ /** Plain app-scoped records for authoring UIs; no live Shape crosses RPC. */
129
+ static listEntries(data: { appId: string }) {
130
+ return Server.call(this, 'listEntries', data) as Promise<
131
+ TranslationEntryRecord[]
132
+ >;
133
+ }
134
+
135
+ static listVersions(data: { appId: string; key?: string }) {
136
+ return Server.call(this, 'listKeyVersions', data) as Promise<
137
+ TranslationKeyVersionRecord[]
138
+ >;
139
+ }
140
+
141
+ // Shape.upsert is the framework's typed graph-update builder. Keep the
142
+ // app-scoped translation RPC separate from that inherited API.
143
+ static upsertKey(data: {
144
+ appId: string;
145
+ key: string;
146
+ sourceText: string;
147
+ namespace?: string;
148
+ description?: string;
149
+ kind?: 'ui' | 'content';
150
+ ofNode?: string;
151
+ ofField?: string;
152
+ ofShape?: string;
153
+ ofProperty?: string;
154
+ fromPackage?: string;
155
+ format?: 'simple' | 'icu';
156
+ }): Promise<{ id: string }> {
157
+ return Server.call(this, 'upsertKey', data);
158
+ }
159
+
160
+ static upsertUnit(data: {
161
+ appId: string;
162
+ key: string;
163
+ language: string;
164
+ text: string;
165
+ state?: TranslationState;
166
+ updatedBy?: string;
167
+ }) {
168
+ return Server.call(this, 'upsertUnit', data);
169
+ }
170
+
171
+ static getMessages(data: { appId: string; language: string }) {
172
+ return Server.call(this, 'getMessages', data) as Promise<TranslationMessages>;
173
+ }
174
+ }
@@ -0,0 +1,77 @@
1
+ import { Shape } from '@_linked/core/shapes/Shape';
2
+ import { literalProperty, objectProperty } from '@_linked/core/shapes/SHACL';
3
+ import { xsd } from '@_linked/core/ontologies/xsd';
4
+ import { tr } from '../ontologies/translation.js';
5
+ import { linkedShape } from '../package.js';
6
+ import { TranslationKey } from './TranslationKey.js';
7
+
8
+ /** Immutable source text + runtime argument contract for one logical key. */
9
+ @linkedShape
10
+ export class TranslationKeyVersion extends Shape {
11
+ static targetClass = tr.TranslationKeyVersion;
12
+
13
+ @objectProperty({ path: tr.ofApplication, maxCount: 1 })
14
+ get ofApplication(): string {
15
+ return '';
16
+ }
17
+
18
+ @objectProperty({ path: tr.ofKey, shape: TranslationKey, maxCount: 1 })
19
+ get ofKey(): TranslationKey {
20
+ return undefined as any;
21
+ }
22
+
23
+ /** Monotonic ULID for new versions; deterministic `backfill-*` for migration. */
24
+ @literalProperty({ path: tr.versionId, maxCount: 1 })
25
+ get versionId(): string {
26
+ return '';
27
+ }
28
+
29
+ @literalProperty({ path: tr.sourceLanguage, maxCount: 1 })
30
+ get sourceLanguage(): string {
31
+ return '';
32
+ }
33
+
34
+ @literalProperty({ path: tr.sourceText, maxCount: 1 })
35
+ get sourceText(): string {
36
+ return '';
37
+ }
38
+
39
+ @literalProperty({ path: tr.format, maxCount: 1 })
40
+ get format(): string {
41
+ return 'simple';
42
+ }
43
+
44
+ @literalProperty({ path: tr.argumentSignature, maxCount: 1 })
45
+ get argumentSignature(): string {
46
+ return '[]';
47
+ }
48
+
49
+ @literalProperty({ path: tr.contractHash, maxCount: 1 })
50
+ get contractHash(): string {
51
+ return '';
52
+ }
53
+
54
+ @literalProperty({ path: tr.sourceHash, maxCount: 1 })
55
+ get sourceHash(): string {
56
+ return '';
57
+ }
58
+
59
+ @objectProperty({
60
+ path: tr.supersedes,
61
+ shape: TranslationKeyVersion,
62
+ maxCount: 1,
63
+ })
64
+ get supersedes(): TranslationKeyVersion {
65
+ return undefined as any;
66
+ }
67
+
68
+ @literalProperty({ path: tr.createdAt, datatype: xsd.dateTime, maxCount: 1 })
69
+ get createdAt(): Date {
70
+ return new Date(0);
71
+ }
72
+
73
+ @objectProperty({ path: tr.createdBy, maxCount: 1 })
74
+ get createdBy(): string {
75
+ return '';
76
+ }
77
+ }
@@ -0,0 +1,51 @@
1
+ import { Shape } from '@_linked/core/shapes/Shape';
2
+ import { literalProperty, objectProperty } from '@_linked/core/shapes/SHACL';
3
+ import { linkedShape } from '../package.js';
4
+ import { tr } from '../ontologies/translation.js';
5
+
6
+ /** App language configuration. Hosts choose the explicit control-plane store. */
7
+ @linkedShape
8
+ export class TranslationLanguage extends Shape {
9
+ static targetClass = tr.TranslationLanguage;
10
+
11
+ @objectProperty({ path: tr.ofApplication, maxCount: 1 })
12
+ get ofApplication(): string {
13
+ return '';
14
+ }
15
+ @objectProperty({ path: tr.languageIdentifier, maxCount: 1 })
16
+ get identifier(): string {
17
+ return '';
18
+ }
19
+ @literalProperty({ path: tr.languageCode, maxCount: 1 })
20
+ get code(): string {
21
+ return '';
22
+ }
23
+ @literalProperty({ path: tr.nativeName, maxCount: 1 })
24
+ get nativeName(): string {
25
+ return '';
26
+ }
27
+ @literalProperty({ path: tr.englishName, maxCount: 1 })
28
+ get englishName(): string {
29
+ return '';
30
+ }
31
+ @literalProperty({ path: tr.textDirection, maxCount: 1 })
32
+ get direction(): string {
33
+ return 'ltr';
34
+ }
35
+ @objectProperty({ path: tr.parentLanguage, maxCount: 1 })
36
+ get parent(): string {
37
+ return '';
38
+ }
39
+ @objectProperty({ path: tr.fallbackLanguage, maxCount: 1 })
40
+ get fallback(): string {
41
+ return '';
42
+ }
43
+ @literalProperty({ path: tr.languageEnabled, maxCount: 1 })
44
+ get enabled(): boolean {
45
+ return false;
46
+ }
47
+ @literalProperty({ path: tr.languageSupported, maxCount: 1 })
48
+ get supported(): boolean {
49
+ return false;
50
+ }
51
+ }