@_linked/translation 0.9.4 → 0.10.0

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 (45) hide show
  1. package/README.md +30 -0
  2. package/lib/esm/shape-key-copies.d.ts +62 -0
  3. package/lib/esm/shape-key-copies.js +156 -0
  4. package/lib/esm/shape-key-copies.js.map +1 -0
  5. package/package.json +2 -3
  6. package/src/archive.ts +0 -914
  7. package/src/authorization.ts +0 -56
  8. package/src/backend.ts +0 -2
  9. package/src/cdn-loader.ts +0 -264
  10. package/src/checks.ts +0 -413
  11. package/src/compile.ts +0 -168
  12. package/src/core/messages.ts +0 -139
  13. package/src/data/translation.ttl +0 -489
  14. package/src/discovery.ts +0 -594
  15. package/src/exchange.ts +0 -437
  16. package/src/export.ts +0 -138
  17. package/src/formats/csv.ts +0 -467
  18. package/src/formats/json.ts +0 -596
  19. package/src/formats/xliff.ts +0 -733
  20. package/src/import.ts +0 -648
  21. package/src/index.ts +0 -201
  22. package/src/key-sync-node.ts +0 -269
  23. package/src/key-sync.ts +0 -208
  24. package/src/key-version.ts +0 -207
  25. package/src/languages.ts +0 -102
  26. package/src/linked-discovery.ts +0 -385
  27. package/src/message-format.ts +0 -214
  28. package/src/mt.ts +0 -253
  29. package/src/ontologies/translation.register.ts +0 -17
  30. package/src/ontologies/translation.ts +0 -239
  31. package/src/package.ts +0 -14
  32. package/src/react.tsx +0 -378
  33. package/src/records.ts +0 -181
  34. package/src/release.ts +0 -754
  35. package/src/shape-catalog.ts +0 -198
  36. package/src/shapes/GlossaryTerm.ts +0 -61
  37. package/src/shapes/TranslationInventoryOrigin.ts +0 -131
  38. package/src/shapes/TranslationKey.ts +0 -183
  39. package/src/shapes/TranslationKeyVersion.ts +0 -77
  40. package/src/shapes/TranslationLanguage.ts +0 -51
  41. package/src/shapes/TranslationProvider.ts +0 -1927
  42. package/src/shapes/TranslationRelease.ts +0 -114
  43. package/src/shapes/TranslationRevision.ts +0 -105
  44. package/src/shapes/TranslationUnit.ts +0 -72
  45. package/src/shapes/index.ts +0 -13
package/README.md CHANGED
@@ -37,6 +37,7 @@ package works without either peer.
37
37
  - `@_linked/translation/key-sync` — development-time source extraction and sync
38
38
  - `@_linked/translation/key-sync/node` — recursive source-tree scan + dry-run/apply orchestration
39
39
  - `@_linked/translation/shape-catalog` — package-owned translations attached to stable shape/property IRIs
40
+ - `@_linked/translation/shape-key-copies` — server-side: one shape key across every app that holds it (list, propagate, clear overrides)
40
41
  - `@_linked/translation/discovery` — portable declarations and deterministic inventory merging
41
42
  - `@_linked/translation/linked-discovery` — serialized LINKED package catalogs
42
43
  - `@_linked/translation/archive` — full-fidelity backup and pure restore planning
@@ -87,6 +88,35 @@ Existing app-authored translations are never overwritten. Packages may export
87
88
  the TypeScript object or ship the same contract as JSON for a host's tooling
88
89
  to discover without executing application code.
89
90
 
91
+ ### One shape key across apps
92
+
93
+ Every app that uses a shape keeps its own copy of each key the shape carries.
94
+ An app may reword its copy; that marks the copy `overridden` and keeps the
95
+ shape's wording at that moment as `shapeSource`. The server-side
96
+ `@_linked/translation/shape-key-copies` entry point acts on all copies of one
97
+ shape key at once:
98
+
99
+ ```ts
100
+ import {
101
+ listShapeKeyCopies,
102
+ propagateShapeKeySource,
103
+ clearShapeKeyOverride,
104
+ } from '@_linked/translation/shape-key-copies';
105
+
106
+ // Every app's copy, with its override state and translated languages.
107
+ await listShapeKeyCopies({ shapeIri, key: 'person.givenName' });
108
+ // New wording for every copy that follows the shape; overrides keep theirs.
109
+ await propagateShapeKeySource({ shapeIri, key: 'person.givenName', sourceText: 'Given name' });
110
+ // Revert overrides (all apps, or one with `appId`) so they follow the shape again.
111
+ await clearShapeKeyOverride({ shapeIri, key: 'person.givenName', appId });
112
+ ```
113
+
114
+ Each text change is a normal key write (`upsertTranslationKey`): it records a
115
+ new key version and marks that key's units stale. A clear reverts to the text of
116
+ a copy that still follows the shape, or else to the copy's own `shapeSource`.
117
+ The operations run on the default dataset and check no permissions: the host
118
+ decides who may call them and which dataset they run in.
119
+
90
120
  Authoring tools — editors, menus, entitlements, capability activation — are
91
121
  deliberately not part of this package. They belong to the host application, so
92
122
  any app can use this runtime without depending on a particular host.
@@ -0,0 +1,62 @@
1
+ import { type TranslationKeyWriteContext } from './shapes/TranslationProvider.js';
2
+ /** One app's copy of a shape key. */
3
+ export interface ShapeKeyCopyRecord {
4
+ /** The key node. */
5
+ id: string;
6
+ key: string;
7
+ shapeIri: string;
8
+ appId: string;
9
+ sourceText?: string;
10
+ /** The app reworded this copy; propagation leaves it alone. */
11
+ overridden: boolean;
12
+ /** The shape's text when the app first reworded the copy: what a clear reverts to without a sibling. */
13
+ shapeSource?: string;
14
+ ofProperty?: string;
15
+ fromPackage?: string;
16
+ namespace?: string;
17
+ description?: string;
18
+ kind?: 'ui' | 'content';
19
+ format?: 'simple' | 'icu';
20
+ /** The languages this copy has a unit for, sorted. */
21
+ languages: string[];
22
+ }
23
+ export interface ShapeKeyQuery {
24
+ /** One shape's keys; omitted, every shape key. */
25
+ shapeIri?: string;
26
+ /** One key name of that shape. */
27
+ key?: string;
28
+ }
29
+ /**
30
+ * The copies of the shape keys `query` names, one per app. A key that names no app is not a
31
+ * copy and is left out.
32
+ */
33
+ export declare function listShapeKeyCopies(query?: ShapeKeyQuery): Promise<ShapeKeyCopyRecord[]>;
34
+ export interface ShapeKeyPropagation {
35
+ /** Apps whose copy now has the new text (or had it already). */
36
+ updatedApps: string[];
37
+ /** Apps whose copy is overridden and was left alone. */
38
+ skippedOverrides: string[];
39
+ }
40
+ /**
41
+ * Give one shape key a new source text in every app whose copy follows the shape. Overridden
42
+ * copies keep their text. A copy whose text changes gets a new key version and stale units.
43
+ */
44
+ export declare function propagateShapeKeySource(data: {
45
+ shapeIri: string;
46
+ key: string;
47
+ sourceText: string;
48
+ }, authorship?: Pick<TranslationKeyWriteContext, 'createdBy'>): Promise<ShapeKeyPropagation>;
49
+ /**
50
+ * Clear the override on one shape key's copies: every overridden copy, or only `appId`'s. Each
51
+ * reverts to the text of a copy that follows the shape, or, when none does, to its own snapshot
52
+ * (`shapeSource`); with neither, it keeps its own text. The flag is reset (`overridden: false`)
53
+ * and the snapshot removed, so the copy follows the shape again. Returns the apps cleared; a copy
54
+ * with no text at all is not written and not counted.
55
+ */
56
+ export declare function clearShapeKeyOverride(data: {
57
+ shapeIri: string;
58
+ key: string;
59
+ appId?: string;
60
+ }, authorship?: Pick<TranslationKeyWriteContext, 'createdBy'>): Promise<{
61
+ clearedApps: string[];
62
+ }>;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Shape keys across apps: every app that uses a shape holds its own copy of each translation key
3
+ * the shape carries (`TranslationKey` with `ofShape`, one per `ofApplication`). These operations
4
+ * act on all of a shape key's copies at once:
5
+ *
6
+ * - {@link listShapeKeyCopies} reads them, with each copy's override state and the languages it
7
+ * is translated into;
8
+ * - {@link propagateShapeKeySource} gives every copy that follows the shape a new source text;
9
+ * a copy the app reworded (`overridden`) keeps its own text;
10
+ * - {@link clearShapeKeyOverride} reverts overridden copies (all, or one app's) to the shape's
11
+ * current text, so they follow the shape again.
12
+ *
13
+ * Every text change goes through {@link upsertTranslationKey}, so it gets a new key version and
14
+ * marks the key's units stale exactly as any other source change does. Like the other key writes,
15
+ * they run on the default dataset (`LinkedStorage`); a host that keeps each app's data in its own
16
+ * dataset runs them in that dataset's context. They decide nothing about who may call them: the
17
+ * host checks that before.
18
+ */
19
+ import { TranslationKey } from './shapes/TranslationKey.js';
20
+ import { TranslationUnit } from './shapes/TranslationUnit.js';
21
+ import { upsertTranslationKey, } from './shapes/TranslationProvider.js';
22
+ const iri = (value) => typeof value === 'string' ? value || undefined : (value === null || value === void 0 ? void 0 : value.id) || undefined;
23
+ const text = (value) => value === undefined || value === null || value === '' ? undefined : String(value);
24
+ /** The flag as stored: a boolean, or the string "true" an older writer left. */
25
+ const isOverridden = (value) => value === true || value === 'true';
26
+ /** The node a key write addresses: `upsertTranslationKey` keys a copy by its app and key name. */
27
+ const keyIri = (appId, key) => `${appId.replace(/\/$/, '')}/translation/key/${encodeURIComponent(key)}`;
28
+ const keyWhere = (query) => (k) => {
29
+ const shape = query.shapeIri ? k.ofShape.equals({ id: query.shapeIri }) : k.ofShape.isDefined();
30
+ return query.key === undefined ? shape : shape.and(k.key.equals(query.key));
31
+ };
32
+ /**
33
+ * The copies of the shape keys `query` names, one per app. A key that names no app is not a
34
+ * copy and is left out.
35
+ */
36
+ export async function listShapeKeyCopies(query = {}) {
37
+ var _a, _b;
38
+ const [keys, units] = await Promise.all([
39
+ TranslationKey.select((k) => [
40
+ k.key,
41
+ k.ofShape,
42
+ k.ofApplication,
43
+ k.sourceText,
44
+ k.overridden,
45
+ k.shapeSource,
46
+ k.ofProperty,
47
+ k.fromPackage,
48
+ k.namespace,
49
+ k.description,
50
+ k.kind,
51
+ k.format,
52
+ ]).where(keyWhere(query)),
53
+ TranslationUnit.select((u) => [u.ofKey, u.language]).where(((u) => keyWhere(query)(u.ofKey))),
54
+ ]);
55
+ const languagesOf = new Map();
56
+ for (const unit of (units !== null && units !== void 0 ? units : [])) {
57
+ const keyId = iri(unit.ofKey);
58
+ const language = text(unit.language);
59
+ if (!keyId || !language)
60
+ continue;
61
+ const set = (_a = languagesOf.get(keyId)) !== null && _a !== void 0 ? _a : new Set();
62
+ set.add(language);
63
+ languagesOf.set(keyId, set);
64
+ }
65
+ const copies = [];
66
+ for (const row of (keys !== null && keys !== void 0 ? keys : [])) {
67
+ const id = iri(row.id);
68
+ const key = text(row.key);
69
+ const shapeIri = iri(row.ofShape);
70
+ const appId = iri(row.ofApplication);
71
+ if (!id || !key || !shapeIri || !appId)
72
+ continue;
73
+ copies.push({
74
+ id,
75
+ key,
76
+ shapeIri,
77
+ appId,
78
+ sourceText: text(row.sourceText),
79
+ overridden: isOverridden(row.overridden),
80
+ shapeSource: text(row.shapeSource),
81
+ ofProperty: iri(row.ofProperty),
82
+ fromPackage: text(row.fromPackage),
83
+ namespace: text(row.namespace),
84
+ description: text(row.description),
85
+ kind: row.kind === 'content' ? 'content' : row.kind === 'ui' ? 'ui' : undefined,
86
+ format: row.format === 'icu' ? 'icu' : row.format === 'simple' ? 'simple' : undefined,
87
+ languages: [...((_b = languagesOf.get(id)) !== null && _b !== void 0 ? _b : [])].sort(),
88
+ });
89
+ }
90
+ return copies;
91
+ }
92
+ /** The copy's own declaration, so a text write keeps what the copy declares. */
93
+ function asKeyInput(copy, sourceText) {
94
+ if (copy.id !== keyIri(copy.appId, copy.key)) {
95
+ // A key write addresses the app's key node; writing this copy there would create a second key.
96
+ throw new Error(`Shape key copy ${copy.id} is not at its app's key node; it was not written.`);
97
+ }
98
+ return {
99
+ appId: copy.appId,
100
+ key: copy.key,
101
+ sourceText,
102
+ namespace: copy.namespace,
103
+ description: copy.description,
104
+ kind: copy.kind,
105
+ ofShape: copy.shapeIri,
106
+ ofProperty: copy.ofProperty,
107
+ fromPackage: copy.fromPackage,
108
+ format: copy.format,
109
+ };
110
+ }
111
+ const appsOf = (copies) => [...new Set(copies.map((c) => c.appId))].sort();
112
+ /**
113
+ * Give one shape key a new source text in every app whose copy follows the shape. Overridden
114
+ * copies keep their text. A copy whose text changes gets a new key version and stale units.
115
+ */
116
+ export async function propagateShapeKeySource(data, authorship = {}) {
117
+ var _a;
118
+ const sourceText = (_a = data.sourceText) === null || _a === void 0 ? void 0 : _a.trim();
119
+ if (!sourceText)
120
+ throw new Error('sourceText is required.');
121
+ const copies = await listShapeKeyCopies({ shapeIri: data.shapeIri, key: data.key });
122
+ const following = copies.filter((c) => !c.overridden);
123
+ for (const copy of following) {
124
+ // A declaration refresh: it never marks the copy overridden.
125
+ await upsertTranslationKey(asKeyInput(copy, sourceText), { ...authorship, source: 'declaration-sync' });
126
+ }
127
+ return { updatedApps: appsOf(following), skippedOverrides: appsOf(copies.filter((c) => c.overridden)) };
128
+ }
129
+ /**
130
+ * Clear the override on one shape key's copies: every overridden copy, or only `appId`'s. Each
131
+ * reverts to the text of a copy that follows the shape, or, when none does, to its own snapshot
132
+ * (`shapeSource`); with neither, it keeps its own text. The flag is reset (`overridden: false`)
133
+ * and the snapshot removed, so the copy follows the shape again. Returns the apps cleared; a copy
134
+ * with no text at all is not written and not counted.
135
+ */
136
+ export async function clearShapeKeyOverride(data, authorship = {}) {
137
+ var _a, _b;
138
+ const copies = await listShapeKeyCopies({ shapeIri: data.shapeIri, key: data.key });
139
+ const overridden = copies.filter((c) => c.overridden && (data.appId === undefined || c.appId === data.appId));
140
+ const canonical = (_a = copies.find((c) => !c.overridden && c.sourceText !== undefined)) === null || _a === void 0 ? void 0 : _a.sourceText;
141
+ const cleared = [];
142
+ for (const copy of overridden) {
143
+ const next = (_b = canonical !== null && canonical !== void 0 ? canonical : copy.shapeSource) !== null && _b !== void 0 ? _b : copy.sourceText;
144
+ if (next === undefined)
145
+ continue;
146
+ // An explicit app edit: the only write that may reset the override flag.
147
+ await upsertTranslationKey({ ...asKeyInput(copy, next), overridden: false }, { ...authorship, source: 'app-edit' });
148
+ if (copy.shapeSource !== undefined) {
149
+ // In the mutation DSL an explicit `undefined` removes the value.
150
+ await TranslationKey.update({ shapeSource: undefined }).for({ id: copy.id });
151
+ }
152
+ cleared.push(copy);
153
+ }
154
+ return { clearedApps: appsOf(cleared) };
155
+ }
156
+ //# sourceMappingURL=shape-key-copies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape-key-copies.js","sourceRoot":"","sources":["../../src/shape-key-copies.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shape keys across apps: every app that uses a shape holds its own copy of each translation key\n * the shape carries (`TranslationKey` with `ofShape`, one per `ofApplication`). These operations\n * act on all of a shape key's copies at once:\n *\n * - {@link listShapeKeyCopies} reads them, with each copy's override state and the languages it\n * is translated into;\n * - {@link propagateShapeKeySource} gives every copy that follows the shape a new source text;\n * a copy the app reworded (`overridden`) keeps its own text;\n * - {@link clearShapeKeyOverride} reverts overridden copies (all, or one app's) to the shape's\n * current text, so they follow the shape again.\n *\n * Every text change goes through {@link upsertTranslationKey}, so it gets a new key version and\n * marks the key's units stale exactly as any other source change does. Like the other key writes,\n * they run on the default dataset (`LinkedStorage`); a host that keeps each app's data in its own\n * dataset runs them in that dataset's context. They decide nothing about who may call them: the\n * host checks that before.\n */\nimport { TranslationKey } from './shapes/TranslationKey.js';\nimport { TranslationUnit } from './shapes/TranslationUnit.js';\nimport {\n upsertTranslationKey,\n type TranslationKeyInput,\n type TranslationKeyWriteContext,\n} from './shapes/TranslationProvider.js';\n\n/** One app's copy of a shape key. */\nexport interface ShapeKeyCopyRecord {\n /** The key node. */\n id: string;\n key: string;\n shapeIri: string;\n appId: string;\n sourceText?: string;\n /** The app reworded this copy; propagation leaves it alone. */\n overridden: boolean;\n /** The shape's text when the app first reworded the copy: what a clear reverts to without a sibling. */\n shapeSource?: string;\n ofProperty?: string;\n fromPackage?: string;\n namespace?: string;\n description?: string;\n kind?: 'ui' | 'content';\n format?: 'simple' | 'icu';\n /** The languages this copy has a unit for, sorted. */\n languages: string[];\n}\n\nexport interface ShapeKeyQuery {\n /** One shape's keys; omitted, every shape key. */\n shapeIri?: string;\n /** One key name of that shape. */\n key?: string;\n}\n\nconst iri = (value: unknown): string | undefined =>\n typeof value === 'string' ? value || undefined : (value as { id?: string } | undefined)?.id || undefined;\nconst text = (value: unknown): string | undefined =>\n value === undefined || value === null || value === '' ? undefined : String(value);\n/** The flag as stored: a boolean, or the string \"true\" an older writer left. */\nconst isOverridden = (value: unknown): boolean => value === true || value === 'true';\n\n/** The node a key write addresses: `upsertTranslationKey` keys a copy by its app and key name. */\nconst keyIri = (appId: string, key: string): string =>\n `${appId.replace(/\\/$/, '')}/translation/key/${encodeURIComponent(key)}`;\n\nconst keyWhere = (query: ShapeKeyQuery) => (k: any) => {\n const shape = query.shapeIri ? k.ofShape.equals({ id: query.shapeIri }) : k.ofShape.isDefined();\n return query.key === undefined ? shape : shape.and(k.key.equals(query.key));\n};\n\n/**\n * The copies of the shape keys `query` names, one per app. A key that names no app is not a\n * copy and is left out.\n */\nexport async function listShapeKeyCopies(query: ShapeKeyQuery = {}): Promise<ShapeKeyCopyRecord[]> {\n const [keys, units] = await Promise.all([\n TranslationKey.select((k) => [\n k.key,\n k.ofShape,\n k.ofApplication,\n k.sourceText,\n k.overridden,\n k.shapeSource,\n k.ofProperty,\n k.fromPackage,\n k.namespace,\n k.description,\n k.kind,\n k.format,\n ]).where(keyWhere(query) as any),\n TranslationUnit.select((u) => [u.ofKey, u.language]).where(((u: any) => keyWhere(query)(u.ofKey)) as any),\n ]);\n const languagesOf = new Map<string, Set<string>>();\n for (const unit of (units ?? []) as any[]) {\n const keyId = iri(unit.ofKey);\n const language = text(unit.language);\n if (!keyId || !language) continue;\n const set = languagesOf.get(keyId) ?? new Set<string>();\n set.add(language);\n languagesOf.set(keyId, set);\n }\n const copies: ShapeKeyCopyRecord[] = [];\n for (const row of (keys ?? []) as any[]) {\n const id = iri(row.id);\n const key = text(row.key);\n const shapeIri = iri(row.ofShape);\n const appId = iri(row.ofApplication);\n if (!id || !key || !shapeIri || !appId) continue;\n copies.push({\n id,\n key,\n shapeIri,\n appId,\n sourceText: text(row.sourceText),\n overridden: isOverridden(row.overridden),\n shapeSource: text(row.shapeSource),\n ofProperty: iri(row.ofProperty),\n fromPackage: text(row.fromPackage),\n namespace: text(row.namespace),\n description: text(row.description),\n kind: row.kind === 'content' ? 'content' : row.kind === 'ui' ? 'ui' : undefined,\n format: row.format === 'icu' ? 'icu' : row.format === 'simple' ? 'simple' : undefined,\n languages: [...(languagesOf.get(id) ?? [])].sort(),\n });\n }\n return copies;\n}\n\n/** The copy's own declaration, so a text write keeps what the copy declares. */\nfunction asKeyInput(copy: ShapeKeyCopyRecord, sourceText: string): TranslationKeyInput {\n if (copy.id !== keyIri(copy.appId, copy.key)) {\n // A key write addresses the app's key node; writing this copy there would create a second key.\n throw new Error(`Shape key copy ${copy.id} is not at its app's key node; it was not written.`);\n }\n return {\n appId: copy.appId,\n key: copy.key,\n sourceText,\n namespace: copy.namespace,\n description: copy.description,\n kind: copy.kind,\n ofShape: copy.shapeIri,\n ofProperty: copy.ofProperty,\n fromPackage: copy.fromPackage,\n format: copy.format,\n };\n}\n\nconst appsOf = (copies: readonly ShapeKeyCopyRecord[]): string[] => [...new Set(copies.map((c) => c.appId))].sort();\n\nexport interface ShapeKeyPropagation {\n /** Apps whose copy now has the new text (or had it already). */\n updatedApps: string[];\n /** Apps whose copy is overridden and was left alone. */\n skippedOverrides: string[];\n}\n\n/**\n * Give one shape key a new source text in every app whose copy follows the shape. Overridden\n * copies keep their text. A copy whose text changes gets a new key version and stale units.\n */\nexport async function propagateShapeKeySource(\n data: { shapeIri: string; key: string; sourceText: string },\n authorship: Pick<TranslationKeyWriteContext, 'createdBy'> = {}\n): Promise<ShapeKeyPropagation> {\n const sourceText = data.sourceText?.trim();\n if (!sourceText) throw new Error('sourceText is required.');\n const copies = await listShapeKeyCopies({ shapeIri: data.shapeIri, key: data.key });\n const following = copies.filter((c) => !c.overridden);\n for (const copy of following) {\n // A declaration refresh: it never marks the copy overridden.\n await upsertTranslationKey(asKeyInput(copy, sourceText), { ...authorship, source: 'declaration-sync' });\n }\n return { updatedApps: appsOf(following), skippedOverrides: appsOf(copies.filter((c) => c.overridden)) };\n}\n\n/**\n * Clear the override on one shape key's copies: every overridden copy, or only `appId`'s. Each\n * reverts to the text of a copy that follows the shape, or, when none does, to its own snapshot\n * (`shapeSource`); with neither, it keeps its own text. The flag is reset (`overridden: false`)\n * and the snapshot removed, so the copy follows the shape again. Returns the apps cleared; a copy\n * with no text at all is not written and not counted.\n */\nexport async function clearShapeKeyOverride(\n data: { shapeIri: string; key: string; appId?: string },\n authorship: Pick<TranslationKeyWriteContext, 'createdBy'> = {}\n): Promise<{ clearedApps: string[] }> {\n const copies = await listShapeKeyCopies({ shapeIri: data.shapeIri, key: data.key });\n const overridden = copies.filter((c) => c.overridden && (data.appId === undefined || c.appId === data.appId));\n const canonical = copies.find((c) => !c.overridden && c.sourceText !== undefined)?.sourceText;\n const cleared: ShapeKeyCopyRecord[] = [];\n for (const copy of overridden) {\n const next = canonical ?? copy.shapeSource ?? copy.sourceText;\n if (next === undefined) continue;\n // An explicit app edit: the only write that may reset the override flag.\n await upsertTranslationKey({ ...asKeyInput(copy, next), overridden: false }, { ...authorship, source: 'app-edit' });\n if (copy.shapeSource !== undefined) {\n // In the mutation DSL an explicit `undefined` removes the value.\n await TranslationKey.update({ shapeSource: undefined } as any).for({ id: copy.id } as any);\n }\n cleared.push(copy);\n }\n return { clearedApps: appsOf(cleared) };\n}\n"]}
package/package.json CHANGED
@@ -1,14 +1,14 @@
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.9.4",
4
+ "version": "0.10.0",
5
5
  "packageManager": "npm@12.2.0",
6
6
  "linked": {
7
7
  "kind": "library"
8
8
  },
9
9
  "type": "module",
10
10
  "main": "lib/esm/index.js",
11
- "types": "index.d.ts",
11
+ "types": "lib/esm/index.d.ts",
12
12
  "license": "MIT",
13
13
  "repository": {
14
14
  "type": "git",
@@ -20,7 +20,6 @@
20
20
  },
21
21
  "files": [
22
22
  "lib",
23
- "src",
24
23
  "LICENSE"
25
24
  ],
26
25
  "publishConfig": {