@_linked/translation 0.7.0 → 0.8.1

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 (68) hide show
  1. package/README.md +15 -16
  2. package/lib/esm/archive.d.ts +2 -2
  3. package/lib/esm/archive.js +2 -2
  4. package/lib/esm/archive.js.map +1 -1
  5. package/lib/esm/authorization.d.ts +1 -1
  6. package/lib/esm/authorization.js +1 -1
  7. package/lib/esm/authorization.js.map +1 -1
  8. package/lib/esm/cdn-loader.d.ts +1 -1
  9. package/lib/esm/cdn-loader.js.map +1 -1
  10. package/lib/esm/core/messages.d.ts +14 -1
  11. package/lib/esm/core/messages.js +33 -2
  12. package/lib/esm/core/messages.js.map +1 -1
  13. package/lib/esm/exchange.d.ts +1 -1
  14. package/lib/esm/exchange.js +1 -1
  15. package/lib/esm/exchange.js.map +1 -1
  16. package/lib/esm/export.js +4 -1
  17. package/lib/esm/export.js.map +1 -1
  18. package/lib/esm/formats/xliff.js +1 -1
  19. package/lib/esm/formats/xliff.js.map +1 -1
  20. package/lib/esm/key-sync.d.ts +12 -0
  21. package/lib/esm/key-sync.js +32 -7
  22. package/lib/esm/key-sync.js.map +1 -1
  23. package/lib/esm/mt.js +1 -1
  24. package/lib/esm/mt.js.map +1 -1
  25. package/lib/esm/ontologies/translation.d.ts +193 -191
  26. package/lib/esm/ontologies/translation.js +4 -2
  27. package/lib/esm/ontologies/translation.js.map +1 -1
  28. package/lib/esm/package.d.ts +3 -3
  29. package/lib/esm/react.d.ts +38 -2
  30. package/lib/esm/react.js +98 -15
  31. package/lib/esm/react.js.map +1 -1
  32. package/lib/esm/records.d.ts +2 -1
  33. package/lib/esm/records.js.map +1 -1
  34. package/lib/esm/release.d.ts +3 -1
  35. package/lib/esm/release.js +21 -4
  36. package/lib/esm/release.js.map +1 -1
  37. package/lib/esm/shape-catalog.d.ts +1 -1
  38. package/lib/esm/shape-catalog.js.map +1 -1
  39. package/lib/esm/shapes/GlossaryTerm.d.ts +1 -1
  40. package/lib/esm/shapes/TranslationInventoryOrigin.d.ts +1 -1
  41. package/lib/esm/shapes/TranslationKey.d.ts +11 -6
  42. package/lib/esm/shapes/TranslationKey.js +16 -5
  43. package/lib/esm/shapes/TranslationKey.js.map +1 -1
  44. package/lib/esm/shapes/TranslationKeyVersion.d.ts +1 -1
  45. package/lib/esm/shapes/TranslationLanguage.d.ts +1 -1
  46. package/lib/esm/shapes/TranslationProvider.d.ts +25 -8
  47. package/lib/esm/shapes/TranslationProvider.js +69 -38
  48. package/lib/esm/shapes/TranslationProvider.js.map +1 -1
  49. package/lib/esm/shapes/TranslationRelease.d.ts +1 -1
  50. package/lib/esm/shapes/TranslationRevision.d.ts +1 -1
  51. package/lib/esm/shapes/TranslationUnit.d.ts +1 -1
  52. package/package.json +7 -7
  53. package/src/archive.ts +2 -2
  54. package/src/authorization.ts +1 -1
  55. package/src/cdn-loader.ts +1 -1
  56. package/src/core/messages.ts +35 -2
  57. package/src/exchange.ts +1 -1
  58. package/src/export.ts +4 -1
  59. package/src/formats/xliff.ts +1 -1
  60. package/src/key-sync.ts +46 -6
  61. package/src/mt.ts +1 -1
  62. package/src/ontologies/translation.ts +4 -2
  63. package/src/react.tsx +158 -35
  64. package/src/records.ts +2 -1
  65. package/src/release.ts +31 -3
  66. package/src/shape-catalog.ts +1 -1
  67. package/src/shapes/TranslationKey.ts +14 -5
  68. package/src/shapes/TranslationProvider.ts +92 -32
@@ -82,6 +82,12 @@ export class TranslationKey extends Shape {
82
82
  return '';
83
83
  }
84
84
 
85
+ /** Arbitrary declared semantic resource, such as an action or condition. */
86
+ @objectProperty({ path: tr.ofResource, maxCount: 1 })
87
+ get ofResource(): string {
88
+ return '';
89
+ }
90
+
85
91
  /** Reusable package that supplied the seed value. */
86
92
  @literalProperty({ path: tr.fromPackage, maxCount: 1 })
87
93
  get fromPackage(): string {
@@ -89,10 +95,11 @@ export class TranslationKey extends Shape {
89
95
  }
90
96
 
91
97
  /**
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
98
+ * True when this app copy of a declaration-owned key has been edited locally
99
+ * (shape, property or named package resource, including help) and should NOT
100
+ * be overwritten by declaration refresh or shape-route propagation (AD-N: "never
94
101
  * overwrites an app's own override"). Set when the per-app editor changes the
95
- * source of a key that carries `ofShape`.
102
+ * source of a key with declared ownership.
96
103
  */
97
104
  @literalProperty({ path: tr.overridden, maxCount: 1 })
98
105
  get overridden(): boolean {
@@ -100,9 +107,10 @@ export class TranslationKey extends Shape {
100
107
  }
101
108
 
102
109
  /**
103
- * The shape's canonical source captured the moment this app first overrode the
110
+ * The declaration's canonical source captured when this app first overrode the
104
111
  * key — what "clear override" reverts to when no non-overridden sibling copy is
105
- * available to read the current canonical from (AD-N).
112
+ * available to read the current canonical from (AD-N). The historic predicate
113
+ * name stays compatible; resource-owned help does not invent a second carrier.
106
114
  */
107
115
  @literalProperty({ path: tr.shapeSource, maxCount: 1 })
108
116
  get shapeSource(): string {
@@ -151,6 +159,7 @@ export class TranslationKey extends Shape {
151
159
  ofField?: string;
152
160
  ofShape?: string;
153
161
  ofProperty?: string;
162
+ ofResource?: string;
154
163
  fromPackage?: string;
155
164
  format?: 'simple' | 'icu';
156
165
  }): Promise<{ id: string }> {
@@ -44,6 +44,7 @@ export interface TranslationKeyInput {
44
44
  ofField?: string;
45
45
  ofShape?: string;
46
46
  ofProperty?: string;
47
+ ofResource?: string;
47
48
  fromPackage?: string;
48
49
  /** Force the override flag; when omitted it is derived (see upsertTranslationKey). */
49
50
  overridden?: boolean;
@@ -51,6 +52,25 @@ export interface TranslationKeyInput {
51
52
  sourceLanguage?: string;
52
53
  }
53
54
 
55
+ /** Trusted caller context, not a client payload flag. Package discovery and
56
+ * explicit app authoring have different ownership semantics. */
57
+ export interface TranslationKeyWriteContext {
58
+ createdBy?: string;
59
+ /**
60
+ * `'declaration-sync'` is a refresh from code/package declarations: it never
61
+ * marks a key overridden, and it leaves an existing app override untouched
62
+ * (answering `preservedOverride: true`). Anything else is an app edit.
63
+ */
64
+ source?: 'app-edit' | 'declaration-sync';
65
+ }
66
+ export interface TranslationKeyWriteResult {
67
+ id: string;
68
+ versionId: string;
69
+ versionCreated: boolean;
70
+ /** Present when a declaration refresh kept the app's override and wrote nothing. */
71
+ preservedOverride?: true;
72
+ }
73
+
54
74
  export interface TranslationUnitInput {
55
75
  appId: string;
56
76
  key: string;
@@ -313,6 +333,7 @@ export function groupTranslationEntries(
313
333
  format: row.format === 'icu' ? 'icu' : 'simple',
314
334
  ofShape: iri(row.ofShape),
315
335
  ofProperty: iri(row.ofProperty),
336
+ ofResource: iri(row.ofResource),
316
337
  fromPackage: row.fromPackage || undefined,
317
338
  overridden:
318
339
  (row as any).overridden === true || (row as any).overridden === 'true'
@@ -397,6 +418,7 @@ export async function listTranslationKeys(data: { appId: string }) {
397
418
  k.ofField,
398
419
  k.ofShape,
399
420
  k.ofProperty,
421
+ k.ofResource,
400
422
  k.fromPackage,
401
423
  k.overridden,
402
424
  k.format,
@@ -423,6 +445,7 @@ export async function listTranslationEntries(data: {
423
445
  k.format,
424
446
  k.ofShape,
425
447
  k.ofProperty,
448
+ k.ofResource,
426
449
  k.fromPackage,
427
450
  k.overridden,
428
451
  k.currentVersion,
@@ -1061,8 +1084,8 @@ export async function decideTranslationProposal(
1061
1084
  */
1062
1085
  export async function upsertTranslationKey(
1063
1086
  data: TranslationKeyInput,
1064
- authorship: { createdBy?: string } = {},
1065
- ): Promise<{ id: string; versionId: string; versionCreated: boolean }> {
1087
+ authorship: TranslationKeyWriteContext = {},
1088
+ ): Promise<TranslationKeyWriteResult> {
1066
1089
  const appId = requireText(data.appId, 'appId');
1067
1090
  const key = requireText(data.key, 'key');
1068
1091
  requireText(data.sourceText, 'sourceText');
@@ -1078,29 +1101,59 @@ export async function upsertTranslationKey(
1078
1101
  ofField: data.ofField,
1079
1102
  ofShape: data.ofShape ? { id: data.ofShape } : undefined,
1080
1103
  ofProperty: data.ofProperty ? { id: data.ofProperty } : undefined,
1104
+ ofResource: data.ofResource ? { id: data.ofResource } : undefined,
1081
1105
  fromPackage: data.fromPackage,
1082
- overridden: data.overridden === true ? true : undefined,
1106
+ overridden: data.overridden,
1083
1107
  format: data.format ?? 'simple',
1084
1108
  } as any;
1109
+ // No catch: a failed read is not permission to create or overwrite the key.
1085
1110
  const existing = await TranslationKey.select((k) => [
1086
1111
  k.sourceText,
1087
1112
  k.format,
1088
1113
  k.ofShape,
1114
+ k.ofProperty,
1115
+ k.ofResource,
1116
+ k.fromPackage,
1089
1117
  k.overridden,
1090
1118
  k.currentVersion,
1091
1119
  ])
1092
1120
  .where((k) => k.equals({ id } as any))
1093
- .one()
1094
- .catch(() => null);
1121
+ .one();
1095
1122
  if (existing) {
1096
1123
  const sourceChanged = (existing as any).sourceText !== data.sourceText;
1124
+ const formatChanged =
1125
+ ((existing as any).format ?? 'simple') !== (data.format ?? 'simple');
1097
1126
  const wasOverridden = (existing as any).overridden === true;
1098
- // AD-N override rule: editing the source of a SHAPE-owned key through the
1099
- // per-app path (this write) marks the app copy `overridden`, so shape-route
1100
- // propagation won't clobber the app's local wording. An explicit
1101
- // `data.overridden` (e.g. the installer clearing it) always wins.
1102
- const isShapeKey = !!(existing as any).ofShape || !!data.ofShape;
1103
- if (data.overridden === undefined && isShapeKey && sourceChanged) {
1127
+ if (authorship.source === 'declaration-sync' && wasOverridden) {
1128
+ const versionId = iri((existing as any).currentVersion);
1129
+ if (!versionId)
1130
+ throw new Error(
1131
+ `Translation override ${id} has no current version; refresh refused without changing its source.`
1132
+ );
1133
+ // No key write, version creation or unit invalidation. The declaration
1134
+ // inventory still records the package's current source independently.
1135
+ return { id, versionId, versionCreated: false, preservedOverride: true };
1136
+ }
1137
+ // AD-N ownership also covers property/resource/package declarations (help,
1138
+ // actions, groups), not only shapes. App edits preserve the first canonical
1139
+ // wording in the existing shapeSource carrier. Only an explicit app edit
1140
+ // may clear the override; automatic discovery cannot clear it by payload.
1141
+ const isDeclaredKey = !!(
1142
+ (existing as any).ofShape ||
1143
+ (existing as any).ofProperty ||
1144
+ (existing as any).ofResource ||
1145
+ (existing as any).fromPackage ||
1146
+ data.ofShape ||
1147
+ data.ofProperty ||
1148
+ data.ofResource ||
1149
+ data.fromPackage
1150
+ );
1151
+ if (
1152
+ authorship.source !== 'declaration-sync' &&
1153
+ data.overridden === undefined &&
1154
+ isDeclaredKey &&
1155
+ (sourceChanged || formatChanged)
1156
+ ) {
1104
1157
  values.overridden = true;
1105
1158
  // Snapshot the canonical at FIRST divergence so "clear override" has a
1106
1159
  // value to revert to even for a single-app project. Later edits keep the
@@ -1134,7 +1187,14 @@ export async function upsertTranslationKey(
1134
1187
  })
1135
1188
  : currentVersion;
1136
1189
  values.currentVersion = { id: nextVersion.id };
1137
- await TranslationKey.update(values).for({ id } as any);
1190
+ // In the Linked mutation DSL an explicitly present `undefined` removes
1191
+ // that predicate. A partial source edit has not requested removal of the
1192
+ // declaration owner, help description, or existing override flag. Omit
1193
+ // untouched fields; an explicit false (reset) remains a real write.
1194
+ const patch = Object.fromEntries(
1195
+ Object.entries(values).filter(([, value]) => value !== undefined)
1196
+ );
1197
+ await TranslationKey.update(patch as any).for({ id } as any);
1138
1198
  if (decision.action === 'create-version') {
1139
1199
  await TranslationUnit.update({ state: 'stale' } as any).where((u) =>
1140
1200
  u.ofKey.equals({ id } as any),
@@ -1491,7 +1551,7 @@ export class TranslationProvider extends ShapeProvider {
1491
1551
 
1492
1552
  /**
1493
1553
  * Authoring gate. Reads `linkedAuth` off the request the LINKED server already
1494
- * populates, so this stays portable (no Create Now import, AD-G).
1554
+ * populates, so this stays portable (no host-application import, AD-G).
1495
1555
  *
1496
1556
  * Every method except `getMessages` resolves the caller once, synchronously,
1497
1557
  * before its first `await`, and uses that value for both the permission check
@@ -1542,21 +1602,21 @@ export class TranslationProvider extends ShapeProvider {
1542
1602
  }
1543
1603
  }
1544
1604
 
1545
- @callable('user')
1605
+ @callable('signed-in')
1546
1606
  async listKeys(data: { appId: string }) {
1547
1607
  const actor = this.requireActor();
1548
1608
  await this.requireAccess(actor, { appId: data?.appId }, 'read');
1549
1609
  return listTranslationKeys(data);
1550
1610
  }
1551
1611
 
1552
- @callable('user')
1612
+ @callable('signed-in')
1553
1613
  async listEntries(data: { appId: string }): Promise<TranslationEntryRecord[]> {
1554
1614
  const actor = this.requireActor();
1555
1615
  await this.requireAccess(actor, { appId: data?.appId }, 'read');
1556
1616
  return listTranslationEntries(data);
1557
1617
  }
1558
1618
 
1559
- @callable('user')
1619
+ @callable('signed-in')
1560
1620
  async listKeyVersions(data: {
1561
1621
  appId: string;
1562
1622
  key?: string;
@@ -1566,7 +1626,7 @@ export class TranslationProvider extends ShapeProvider {
1566
1626
  return listTranslationKeyVersions(data);
1567
1627
  }
1568
1628
 
1569
- @callable('user')
1629
+ @callable('signed-in')
1570
1630
  async listMemoryMatches(data: {
1571
1631
  appId: string;
1572
1632
  key: string;
@@ -1577,7 +1637,7 @@ export class TranslationProvider extends ShapeProvider {
1577
1637
  return listTranslationMemoryMatches(data);
1578
1638
  }
1579
1639
 
1580
- @callable('user')
1640
+ @callable('signed-in')
1581
1641
  async proposeRevision(data: {
1582
1642
  appId: string;
1583
1643
  key: string;
@@ -1601,7 +1661,7 @@ export class TranslationProvider extends ShapeProvider {
1601
1661
  });
1602
1662
  }
1603
1663
 
1604
- @callable('user')
1664
+ @callable('signed-in')
1605
1665
  async listProposals(data: {
1606
1666
  appId: string;
1607
1667
  language?: string;
@@ -1622,7 +1682,7 @@ export class TranslationProvider extends ShapeProvider {
1622
1682
  return proposals.filter((_, index) => allowed[index]);
1623
1683
  }
1624
1684
 
1625
- @callable('user')
1685
+ @callable('signed-in')
1626
1686
  async decideProposal(data: {
1627
1687
  appId: string;
1628
1688
  revisionId: string;
@@ -1646,7 +1706,7 @@ export class TranslationProvider extends ShapeProvider {
1646
1706
  return decideTranslationProposal(data, actor);
1647
1707
  }
1648
1708
 
1649
- @callable('user')
1709
+ @callable('signed-in')
1650
1710
  async listMemory(data: {
1651
1711
  appId: string;
1652
1712
  }): Promise<TranslationMemoryRecord[]> {
@@ -1655,7 +1715,7 @@ export class TranslationProvider extends ShapeProvider {
1655
1715
  return listTranslationMemory(data);
1656
1716
  }
1657
1717
 
1658
- @callable('user')
1718
+ @callable('signed-in')
1659
1719
  async pretranslateMemory(data: {
1660
1720
  appId: string;
1661
1721
  language: string;
@@ -1666,7 +1726,7 @@ export class TranslationProvider extends ShapeProvider {
1666
1726
  return pretranslateFromMemory(data, { author: actor });
1667
1727
  }
1668
1728
 
1669
- @callable('user')
1729
+ @callable('signed-in')
1670
1730
  async applyMemoryMatch(data: {
1671
1731
  appId: string;
1672
1732
  key: string;
@@ -1678,7 +1738,7 @@ export class TranslationProvider extends ShapeProvider {
1678
1738
  return applyTranslationMemoryMatch(data, { author: actor });
1679
1739
  }
1680
1740
 
1681
- @callable('user')
1741
+ @callable('signed-in')
1682
1742
  async listReleases(data: {
1683
1743
  appId: string;
1684
1744
  }): Promise<TranslationReleaseRecord[]> {
@@ -1687,7 +1747,7 @@ export class TranslationProvider extends ShapeProvider {
1687
1747
  return listTranslationReleases(data);
1688
1748
  }
1689
1749
 
1690
- @callable('user')
1750
+ @callable('signed-in')
1691
1751
  async createRelease(
1692
1752
  data: Omit<TranslationReleaseRecord, 'id' | 'createdAt'> & {
1693
1753
  createdAt?: string;
@@ -1698,7 +1758,7 @@ export class TranslationProvider extends ShapeProvider {
1698
1758
  return createTranslationRelease(data);
1699
1759
  }
1700
1760
 
1701
- @callable('user')
1761
+ @callable('signed-in')
1702
1762
  async advanceReleaseHotfix(data: {
1703
1763
  id: string;
1704
1764
  expectedSequence: number;
@@ -1712,14 +1772,14 @@ export class TranslationProvider extends ShapeProvider {
1712
1772
  return advanceTranslationReleaseHotfix({ ...data, appId });
1713
1773
  }
1714
1774
 
1715
- @callable('user')
1775
+ @callable('signed-in')
1716
1776
  async upsertKey(data: TranslationKeyInput): Promise<{ id: string }> {
1717
1777
  const actor = this.requireActor();
1718
1778
  await this.requireAccess(actor, data, 'manage');
1719
1779
  return upsertTranslationKey(data, { createdBy: actor });
1720
1780
  }
1721
1781
 
1722
- @callable('user')
1782
+ @callable('signed-in')
1723
1783
  async upsertUnit(data: TranslationUnitInput): Promise<{ id: string }> {
1724
1784
  const actor = this.requireActor();
1725
1785
  await this.requireAccess(actor, data, 'review');
@@ -1735,14 +1795,14 @@ export class TranslationProvider extends ShapeProvider {
1735
1795
  * anchors the IRIs. Uniqueness = (term, language) — upsert is
1736
1796
  * delete-then-create on that identity.
1737
1797
  */
1738
- @callable('user')
1798
+ @callable('signed-in')
1739
1799
  async listGlossary(data: { appId: string }): Promise<GlossaryTermRecord[]> {
1740
1800
  const actor = this.requireActor();
1741
1801
  await this.requireAccess(actor, { appId: data?.appId }, 'read');
1742
1802
  return listGlossaryTerms(data);
1743
1803
  }
1744
1804
 
1745
- @callable('user')
1805
+ @callable('signed-in')
1746
1806
  async upsertGlossaryTerm(data: {
1747
1807
  appId: string;
1748
1808
  term: string;
@@ -1785,7 +1845,7 @@ export class TranslationProvider extends ShapeProvider {
1785
1845
  return { id };
1786
1846
  }
1787
1847
 
1788
- @callable('user')
1848
+ @callable('signed-in')
1789
1849
  async deleteGlossaryTerm(data: { appId: string; id: string }): Promise<{ deleted: boolean }> {
1790
1850
  const actor = this.requireActor();
1791
1851
  await this.requireAccess(actor, data, 'manage');
@@ -1800,7 +1860,7 @@ export class TranslationProvider extends ShapeProvider {
1800
1860
  }
1801
1861
 
1802
1862
  /** History for one (key, language) cell — newest first (Plan 017 AD-P). */
1803
- @callable('user')
1863
+ @callable('signed-in')
1804
1864
  async listRevisions(data: {
1805
1865
  appId: string;
1806
1866
  key: string;