diff --git a/.changeset/i18n-generated-leaf-source-provenance.md b/.changeset/i18n-generated-leaf-source-provenance.md new file mode 100644 index 0000000000..0315927283 --- /dev/null +++ b/.changeset/i18n-generated-leaf-source-provenance.md @@ -0,0 +1,55 @@ +--- +"@objectstack/platform-objects": minor +"@objectstack/cli": minor +--- + +feat(platform-objects,cli): record which source revision a generated translation leaf was filled from (#11671) + +Closes the half of the sticky-translation-drift class that no value comparison +could reach, under maintainer ruling #12069 Option A — by extending the existing +#8765 Option B source-hash mechanism to the generated bundles rather than +building a second one. + +**The hole.** `os i18n extract --fill=default` fills gaps only: any non-empty +value in a translated locale wins forever. So the ordinary sequence — extract, +revise the source string, extract again — rewrites `en` and strands the previous +source text in every other locale. The bundle is still in sync by key, so +`check:i18n` reports OK; the leaf is still present, so `check:i18n-coverage` +counts it translated. Measured on #11659 at `bbe0b17`: three locales serving a +602-char superseded draft of a 411-char help string under 31 green checks. Once +the source has moved, that stale fill is indistinguishable **by value** from a +real translation — 2648 of 3010 leaves differ from `en`, so "untranslated AND +differing from the source" describes an empty set, not a noisy one. + +**What is new.** `os i18n extract --source-hashes` writes +`.source-hashes.generated.ts` beside each generated bundle: per leaf, +the digest of the source revision that leaf is **still a byte copy of**. +`withSourceFallback` takes that table as a fourth argument and now judges the +`objects` / `metadataForms` sections as well as the hand-authored ones, so a +leaf whose source has moved underneath it serves the current source string +instead of a superseded draft — the same degradation an untranslated key already +produces, which is the invariance the #8765 ruling turned on. + +The generated half needs one conjunct the hand-authored half does not: the leaf +must still hold the recorded bytes. Its hash table is itself generated, so a +translator cannot be asked to refresh a digest by hand the way +`.source-hashes.ts` asks; without that conjunct, re-translating a stale +leaf would leave the old record standing and report the fresh translation as +stale forever. With it, editing the value clears the flag by itself. + +**Behaviour on the day it lands: unchanged for every leaf.** Records are +written only where a leaf is currently a byte copy of the **current** source, so +every record equals the current digest and nothing is stale. Measured across the +nine bundle sets: 9030 translated leaves, 1543 byte-equal to `en` (records +written), 7487 differing (left with no record — legacy-trusted, per the ruling's +property 1, since nothing in the tree says which revision they were made from). +No committed bundle changed a byte. + +**Scope.** `--source-hashes` is off by default and `@objectstack/platform-objects` +is the one bundle set that opts in, by documenting the flag in its extract +config. The other eight sets keep exactly today's behaviour and can be enabled +file-by-file later; a set with no companion is entirely legacy-trusted. + +The false "this hole cannot occur there" note that kept the generated sections +out of the mechanism is corrected in `source-hash.ts`, with the measurement that +falsifies it. diff --git a/package.json b/package.json index 8a155842d6..195d8efbdb 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "objectui:refresh": "bash scripts/bump-objectui.sh && bash scripts/build-console.sh", "objectui:clean": "rm -rf packages/console/dist .cache/objectui-*", "lint": "node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config", - "i18n:extract": "tsx packages/cli/bin/run-dev.js i18n extract packages/platform-objects/scripts/i18n-extract.config.ts --locales=zh-CN,ja-JP,es-ES --fill=default --out=packages/platform-objects/src/apps/translations", + "i18n:extract": "tsx packages/cli/bin/run-dev.js i18n extract packages/platform-objects/scripts/i18n-extract.config.ts --locales=zh-CN,ja-JP,es-ES --fill=default --source-hashes --out=packages/platform-objects/src/apps/translations", "check:i18n": "node scripts/check-i18n-bundles.mjs --self-test && node scripts/check-i18n-bundles.mjs", "check:i18n-coverage": "node scripts/check-i18n-coverage.mjs --self-test && node scripts/check-i18n-coverage.mjs", "check:i18n-stale-fill": "node scripts/check-i18n-stale-fill.mjs --self-test && node scripts/check-i18n-stale-fill.mjs", diff --git a/packages/cli/src/commands/i18n/extract.ts b/packages/cli/src/commands/i18n/extract.ts index 19ed045a10..c838ea9269 100644 --- a/packages/cli/src/commands/i18n/extract.ts +++ b/packages/cli/src/commands/i18n/extract.ts @@ -16,7 +16,13 @@ import { emitJson, isExitSignal, } from '../../utils/format.js'; -import { extractTranslations, renderTranslationModule, type FillStrategy } from '../../utils/i18n-extract.js'; +import { + extractTranslations, + renderTranslationModule, + renderSourceHashModule, + parseSourceHashModule, + type FillStrategy, +} from '../../utils/i18n-extract.js'; const FILL_STRATEGIES: FillStrategy[] = ['empty', 'default', 'todo']; @@ -89,6 +95,12 @@ export default class I18nExtract extends Command { default: true, allowNo: true, }), + 'source-hashes': Flags.boolean({ + description: + 'Also write .source-hashes.generated.ts — the provenance companion that lets a stale fill be told from a translation (#11671). Off by default: it is a format addition, so a bundle set opts in by documenting the flag in its extract config.', + default: false, + allowNo: true, + }), 'dry-run': Flags.boolean({ description: 'Print to stdout instead of writing to --out', default: false, @@ -131,9 +143,25 @@ export default class I18nExtract extends Command { ? declared.defaultLocale : 'en'); + // Resolved before the extract because the previously committed provenance + // records are an INPUT to it: they are the mechanism's only memory, and a + // run that could not read them would silently re-derive every record from + // the current tree and forget the drift it is supposed to be holding on to. + const outDir = flags.out ? path.resolve(process.cwd(), flags.out) : undefined; + const previousSourceHashes: Record> = {}; + if (flags['source-hashes'] && outDir) { + for (const locale of locales ?? []) { + const file = path.join(outDir, `${locale}.source-hashes.generated.ts`); + if (!fs.existsSync(file)) continue; + const table = parseSourceHashModule(fs.readFileSync(file, 'utf8')); + if (table) previousSourceHashes[locale] = table; + } + } + const result = extractTranslations(normalized, { defaultLocale, locales, + previousSourceHashes, fill: flags.fill as FillStrategy, filter, // Merge (the default) never overwrites an existing non-default-locale @@ -222,7 +250,8 @@ export default class I18nExtract extends Command { return; } - const outDir = path.resolve(process.cwd(), flags.out); + // `flags.out` is non-empty here — the two branches above return otherwise. + const resolvedOutDir = outDir as string; // Every file a normal run would emit, paired with its rendered content. // Both branches below iterate this, so `--check` can never diverge from @@ -231,18 +260,29 @@ export default class I18nExtract extends Command { for (const locale of localesEmitted) { if (result.counts[locale] > 0) { emitted.push({ - file: path.join(outDir, `${locale}.objects.generated.ts`), + file: path.join(resolvedOutDir, `${locale}.objects.generated.ts`), content: renderTranslationModule(result.bundles[locale], { locale, objectsOnly }), keys: result.counts[locale], }); } if (emitsMetadataForms(locale)) { emitted.push({ - file: path.join(outDir, `${locale}.metadata-forms.generated.ts`), + file: path.join(resolvedOutDir, `${locale}.metadata-forms.generated.ts`), content: renderTranslationModule(result.bundles[locale], { locale, kind: 'metadataForms' }), keys: metadataFormsCounts[locale], }); } + // The provenance companion rides in the SAME list, so `--check` compares + // it by the same byte-for-byte rule as the bundles it belongs to and can + // never diverge from what a real extract writes. + const table = result.sourceHashes[locale]; + if (flags['source-hashes'] && table) { + emitted.push({ + file: path.join(resolvedOutDir, `${locale}.source-hashes.generated.ts`), + content: renderSourceHashModule(table, { locale }), + keys: Object.keys(table).length, + }); + } } if (flags.check) { @@ -269,7 +309,7 @@ export default class I18nExtract extends Command { process.exit(1); } - fs.mkdirSync(outDir, { recursive: true }); + fs.mkdirSync(resolvedOutDir, { recursive: true }); let written = 0; for (const { file, content, keys } of emitted) { fs.writeFileSync(file, content, 'utf8'); diff --git a/packages/cli/src/utils/i18n-extract.ts b/packages/cli/src/utils/i18n-extract.ts index 6266cd739d..9ab57a4c45 100644 --- a/packages/cli/src/utils/i18n-extract.ts +++ b/packages/cli/src/utils/i18n-extract.ts @@ -84,6 +84,7 @@ import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; import { deriveFieldGroupLayout } from '@objectstack/spec/data'; import { expandViewContainer } from '@objectstack/spec/ui'; import { authorWarnedProperties, walkPageComponents } from '@objectstack/lint'; +import { collectFilledFromHashes } from '@objectstack/platform-objects/apps'; // ─── Public types ────────────────────────────────────────────────────── @@ -163,6 +164,18 @@ export interface ExtractOptions extends ExpectedEntryOptions { * This makes extract idempotent — re-running only fills the gaps. */ mergeExisting?: boolean; + /** + * The `.source-hashes.generated.ts` tables already committed beside + * the bundles, keyed by locale. + * + * This is the mechanism's ONLY memory (#11671 / #12069 Option A): a leaf that + * is a byte copy of a source revision keeps its record across runs, which is + * what makes the drift detectable after the source moves. Passing nothing + * makes the run behave like a first extract — every record is re-derived from + * the tree, so leaves that already drifted stay legacy-trusted rather than + * being reported. + */ + previousSourceHashes?: Record>; } export interface ExtractResult { @@ -172,6 +185,18 @@ export interface ExtractResult { counts: Record; /** Total expected entries before per-locale merge filtering. */ totalExpected: number; + /** + * Per translated locale, the digest of the source revision each GENERATED + * leaf is still a byte copy of — the content of + * `.source-hashes.generated.ts`. + * + * Computed by `collectFilledFromHashes` in + * `@objectstack/platform-objects/apps`, the module maintainer ruling #8765 + * Option B put the mechanism in; the extractor supplies the tree and the + * previous records and owns none of the rule. The default locale gets no + * entry: it is the source, not a copy of one. + */ + sourceHashes: Record>; } // ─── Walk helpers ────────────────────────────────────────────────────── @@ -1313,7 +1338,18 @@ export function extractTranslations(config: any, opts: ExtractOptions = {}): Ext counts[locale] = count; } - return { bundles, counts, totalExpected: entries.length }; + const sourceHashes: Record> = {}; + const sourceBundle = bundles[defaultLocale]; + for (const locale of locales) { + if (locale === defaultLocale) continue; + sourceHashes[locale] = collectFilledFromHashes( + bundles[locale], + sourceBundle, + opts.previousSourceHashes?.[locale], + ); + } + + return { bundles, counts, totalExpected: entries.length, sourceHashes }; } // ─── Serialization ───────────────────────────────────────────────────── @@ -1390,6 +1426,80 @@ export function renderTranslationModule( return lines.join('\n'); } +/** + * Render one locale's generated source-hash table as a TypeScript module body — + * the `.source-hashes.generated.ts` companion. + * + * Deliberately types the export STRUCTURALLY (`Readonly>`) instead of importing `SourceHashes`. The companion is written into + * whichever package owns the bundles, and only one of those packages can spell + * the type with a relative import; an import path guessed per package is a + * portability bug waiting for the second package to use this. The structural + * type is what `SourceHashes` is defined as, so nothing is lost. + * + * Keys are emitted sorted, and every key is quoted (they are dotted paths, so + * `formatKey` would quote them anyway). Both are load-bearing for `--check`: + * the comparison is byte-for-byte, so a table that reordered with the walk + * would fail on a tree that is in fact in sync. + */ +export function renderSourceHashModule( + hashes: Record, + options: { locale: string; exportName?: string }, +): string { + const exportName = options.exportName ?? `${camelize(options.locale)}GeneratedSourceHashes`; + const keys = Object.keys(hashes).sort(); + const lines: string[] = []; + lines.push('// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.'); + lines.push(''); + lines.push('/**'); + lines.push(` * Auto-generated by 'os i18n extract' for locale '${options.locale}'. Do not hand-edit.`); + lines.push(' *'); + lines.push(" * Each entry is the digest of the SOURCE REVISION that this locale's leaf at"); + lines.push(' * that path is still a byte copy of — provenance for the generated half of the'); + lines.push(' * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B).'); + lines.push(' *'); + lines.push(' * An entry exists only while the leaf IS such a copy. Re-translate the leaf in'); + lines.push(' * `.objects.generated.ts` and the next extract drops its entry by'); + lines.push(' * itself — the table makes no claim about text a translator wrote. A path with'); + lines.push(' * no entry is LEGACY-TRUSTED and never reported stale.'); + lines.push(' *'); + lines.push(' * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest'); + lines.push(' * records that the current text was copied from the current source, which is'); + lines.push(' * the false claim the mechanism exists to detect. Fix the TRANSLATION.'); + lines.push(' */'); + lines.push(''); + lines.push(`export const ${exportName}: Readonly> = {`); + for (const key of keys) lines.push(` ${JSON.stringify(key)}: ${JSON.stringify(hashes[key])},`); + lines.push('};'); + lines.push(''); + return lines.join('\n'); +} + +/** + * Read a committed `.source-hashes.generated.ts` back into a table. + * + * The module body is written by {@link renderSourceHashModule}, which quotes + * every key and every value, so the object literal is already valid JSON — the + * parse needs no TypeScript and no evaluation. A file that does not parse is a + * hard `undefined` (treated as "no previous records", i.e. everything + * legacy-trusted) rather than a guess: inventing records from a file we cannot + * read is how a mechanism starts asserting provenance it does not have. + */ +export function parseSourceHashModule(source: string): Record | undefined { + const marker = source.indexOf('export const'); + const open = marker < 0 ? -1 : source.indexOf('= {', marker); + if (open < 0) return undefined; + const literal = source.slice(open + 2).replace(/;\s*$/, ''); + try { + const parsed = JSON.parse(literal.replace(/,(\s*})/g, '$1')); + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined; + for (const value of Object.values(parsed)) if (typeof value !== 'string') return undefined; + return parsed as Record; + } catch { + return undefined; + } +} + function camelize(locale: string): string { // 'zh-CN' → 'zhCN', 'ja-JP' → 'jaJP', 'es-ES' → 'esES' return locale.replace(/-(.)/g, (_m, c) => c.toUpperCase()); diff --git a/packages/cli/test/i18n-extract-source-hashes.test.ts b/packages/cli/test/i18n-extract-source-hashes.test.ts new file mode 100644 index 0000000000..e29caffa19 --- /dev/null +++ b/packages/cli/test/i18n-extract-source-hashes.test.ts @@ -0,0 +1,86 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// `os i18n extract --source-hashes` — the provenance companion (#11671, +// maintainer ruling #12069 Option A). +// +// The RULE itself lives in `@objectstack/platform-objects/apps` +// (`collectFilledFromHashes`) and is pinned by that package's +// `source-hash.test.ts`. What this file pins is the extractor's side of the +// contract: that the table reaches the result, that the emitted module is +// byte-stable, and that reading it back returns what was written — the three +// properties `--check` rests on, since it compares the companion by bytes. + +import { describe, it, expect } from 'vitest'; +import { + extractTranslations, + renderSourceHashModule, + parseSourceHashModule, +} from '../src/utils/i18n-extract.js'; + +const stack = (help: string) => ({ + objects: [ + { + name: 'sys_activity', + label: 'Activity', + fields: { type: { type: 'text', label: 'Type', help } }, + }, + ], +}); + +const SRC_V1 = 'The kind of activity. Readonly fields are skipped by validateRecord.'; +const SRC_V2 = 'The kind of activity.'; +const HELP = 'objects.sys_activity.fields.type.help'; + +describe('the extractor produces a per-locale provenance table', () => { + it('records the leaves it filled from the source, and nothing for `en`', () => { + const r = extractTranslations(stack(SRC_V1), { + locales: ['zh-CN'], + fill: 'default', + }); + expect(r.sourceHashes.en).toBeUndefined(); + expect(r.sourceHashes['zh-CN'][HELP]).toMatch(/^[0-9a-f]{16}$/); + }); + + it('carries a record forward when the source moves — the stale fill stays visible', () => { + const first = extractTranslations(stack(SRC_V1), { locales: ['zh-CN'], fill: 'default' }); + const recorded = first.sourceHashes['zh-CN']; + + // The source is revised. The translated bundle still holds the OLD text, + // because merge only ever fills gaps — that is the card's whole mechanism. + const second = extractTranslations( + { ...stack(SRC_V2), translations: [{ 'zh-CN': first.bundles['zh-CN'] }] }, + { locales: ['zh-CN'], fill: 'default', previousSourceHashes: { 'zh-CN': recorded } }, + ); + expect(second.bundles['zh-CN'].objects?.sys_activity?.fields?.type?.help).toBe(SRC_V1); + expect(second.sourceHashes['zh-CN'][HELP]).toBe(recorded[HELP]); + }); + + it('is idempotent — feeding its own output back changes nothing', () => { + const first = extractTranslations(stack(SRC_V1), { locales: ['zh-CN'], fill: 'default' }); + const again = extractTranslations( + { ...stack(SRC_V1), translations: [{ 'zh-CN': first.bundles['zh-CN'] }] }, + { locales: ['zh-CN'], fill: 'default', previousSourceHashes: first.sourceHashes }, + ); + expect(again.sourceHashes['zh-CN']).toEqual(first.sourceHashes['zh-CN']); + }); +}); + +describe('the emitted module', () => { + const table = { 'objects.b.label': '00ff00ff00ff00ff', 'objects.a.label': 'a1b2c3d4e5f60718' }; + + it('sorts its keys, so a walk-order change cannot fail `--check` on a tree that is in sync', () => { + const out = renderSourceHashModule(table, { locale: 'zh-CN' }); + expect(out.indexOf('objects.a.label')).toBeLessThan(out.indexOf('objects.b.label')); + expect(out).toContain('export const zhCNGeneratedSourceHashes: Readonly> ='); + }); + + it('round-trips through the reader the command uses to recover previous records', () => { + expect(parseSourceHashModule(renderSourceHashModule(table, { locale: 'ja-JP' }))).toEqual(table); + }); + + it('returns undefined for anything it cannot read, rather than inventing records', () => { + expect(parseSourceHashModule('')).toBeUndefined(); + expect(parseSourceHashModule('export const x: Readonly> = { oops };')).toBeUndefined(); + expect(parseSourceHashModule('export const x: Readonly> = { "a": 3 };')).toBeUndefined(); + }); +}); diff --git a/packages/platform-objects/scripts/i18n-extract.config.ts b/packages/platform-objects/scripts/i18n-extract.config.ts index f483ae377c..73a8ff66a0 100644 --- a/packages/platform-objects/scripts/i18n-extract.config.ts +++ b/packages/platform-objects/scripts/i18n-extract.config.ts @@ -10,8 +10,17 @@ * os i18n extract packages/platform-objects/scripts/i18n-extract.config.ts \ * --locales=zh-CN,ja-JP,es-ES \ * --fill=default \ + * --source-hashes \ * --out=packages/platform-objects/src/apps/translations * + * `--source-hashes` also emits `.source-hashes.generated.ts` — the + * provenance companion from maintainer ruling #12069 Option A (#11671). Without + * it, a leaf filled from the source and then left behind when the source was + * revised is indistinguishable BY VALUE from a real translation, so it publishes + * a superseded draft under a green `check:i18n` forever. This package opts in; + * the other eight bundle sets do not yet, and until they do their generated + * leaves stay legacy-trusted (never reported, never wrong about). + * * From the repo root that is `pnpm i18n:extract`, and `pnpm check:i18n` is the * same run with `--check`: it writes nothing and fails if the committed * bundles differ from a fresh extract. CI runs the latter (lint.yml), so a diff --git a/packages/platform-objects/src/apps/translations/es-ES.source-hashes.generated.ts b/packages/platform-objects/src/apps/translations/es-ES.source-hashes.generated.ts new file mode 100644 index 0000000000..8e6c78fbae --- /dev/null +++ b/packages/platform-objects/src/apps/translations/es-ES.source-hashes.generated.ts @@ -0,0 +1,419 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'es-ES'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const esESGeneratedSourceHashes: Readonly> = { + "metadataForms.action.fields.ai.helpText": "e8c9a5009eb96ef6", + "metadataForms.action.fields.ai.label": "17ad12a926c9f22d", + "metadataForms.action.fields.body.capabilities.helpText": "2db3f83342bd3fa1", + "metadataForms.action.fields.body.capabilities.label": "0b59005e10add41f", + "metadataForms.action.fields.body.language.helpText": "b0ad78a396675964", + "metadataForms.action.fields.body.language.label": "012664c233fc15b0", + "metadataForms.action.fields.body.memoryMb.helpText": "f53c826ce3c94ad9", + "metadataForms.action.fields.body.memoryMb.label": "a1e1612eff7f82ea", + "metadataForms.action.fields.body.source.helpText": "3a066e75948ec72c", + "metadataForms.action.fields.body.source.label": "8fe786e8e29c8cec", + "metadataForms.action.fields.body.timeoutMs.helpText": "47042e57f537d281", + "metadataForms.action.fields.body.timeoutMs.label": "2b887c62238d3532", + "metadataForms.api.description": "62421e8039e013f5", + "metadataForms.api.label": "589af3fe4aab4af5", + "metadataForms.book.description": "45e72c8c74617530", + "metadataForms.book.label": "b48c719759fca087", + "metadataForms.capability.description": "1130a8a487c35f3d", + "metadataForms.capability.label": "cb58b72c5cc77632", + "metadataForms.dataset.description": "ce0d0a4d536bbc1e", + "metadataForms.dataset.fields.description.helpText": "f82c5070e1f78508", + "metadataForms.dataset.fields.description.label": "779a4342c8eb6707", + "metadataForms.dataset.fields.dimensions.helpText": "a4df7393a6bd88c9", + "metadataForms.dataset.fields.dimensions.label": "d75e6dd9d87e93b4", + "metadataForms.dataset.fields.filter.helpText": "8d996f9121810664", + "metadataForms.dataset.fields.filter.label": "802895ec427fc6af", + "metadataForms.dataset.fields.include.helpText": "e8f9933ae4f99e77", + "metadataForms.dataset.fields.include.label": "45b27393fb9f3b2a", + "metadataForms.dataset.fields.label.helpText": "f0cbad8e334db1d3", + "metadataForms.dataset.fields.label.label": "c680364257b11569", + "metadataForms.dataset.fields.measures.label": "395dac46abcf1943", + "metadataForms.dataset.fields.name.helpText": "8c3cae6dba12b42a", + "metadataForms.dataset.fields.name.label": "5274417ff1ccaf01", + "metadataForms.dataset.fields.object.helpText": "09a85579054a7197", + "metadataForms.dataset.fields.object.label": "d0c49032d95ebb36", + "metadataForms.dataset.label": "19647dcb84ce82d3", + "metadataForms.dataset.sections.basics.description": "844b82a322f8edd6", + "metadataForms.dataset.sections.basics.label": "55562f900da68c5d", + "metadataForms.dataset.sections.dimensions.description": "da19c8faa0cb1023", + "metadataForms.dataset.sections.dimensions.label": "d75e6dd9d87e93b4", + "metadataForms.dataset.sections.measures.description": "8186d1c0fe6a86f4", + "metadataForms.dataset.sections.measures.label": "395dac46abcf1943", + "metadataForms.dataset.sections.source.description": "ee9be5509026b678", + "metadataForms.dataset.sections.source.label": "8fe786e8e29c8cec", + "metadataForms.doc.description": "f233af60238b0879", + "metadataForms.doc.label": "3f35cf5088b999ad", + "metadataForms.field.fields.placeholder.helpText": "07cecac0844860be", + "metadataForms.field.fields.placeholder.label": "44d62b55b63fe718", + "metadataForms.field.fields.summaryOperations.field.helpText": "b6897e7341b31c09", + "metadataForms.field.fields.summaryOperations.field.label": "e21c314685cd95ca", + "metadataForms.field.fields.summaryOperations.filter.helpText": "7a46d2abde5a25c4", + "metadataForms.field.fields.summaryOperations.filter.label": "802895ec427fc6af", + "metadataForms.field.fields.summaryOperations.function.helpText": "b1022db50ca584b3", + "metadataForms.field.fields.summaryOperations.function.label": "9ae3f998b780a051", + "metadataForms.field.fields.summaryOperations.object.helpText": "d937a7f5e5d922cf", + "metadataForms.field.fields.summaryOperations.object.label": "d0c49032d95ebb36", + "metadataForms.field.fields.summaryOperations.relationshipField.helpText": "5d8a9925c9a1b45a", + "metadataForms.field.fields.summaryOperations.relationshipField.label": "d8e119c564a5453a", + "metadataForms.hook.fields.body.memoryMb.helpText": "f53c826ce3c94ad9", + "metadataForms.hook.fields.body.memoryMb.label": "a1e1612eff7f82ea", + "metadataForms.hook.fields.retryPolicy.backoffMs.helpText": "42137a83459ed6b7", + "metadataForms.hook.fields.retryPolicy.backoffMs.label": "cad56dd875f84c22", + "metadataForms.hook.fields.retryPolicy.helpText": "de59f5fbb8ad7fda", + "metadataForms.hook.fields.retryPolicy.label": "7ec5dad15f687f05", + "metadataForms.hook.fields.retryPolicy.maxRetries.helpText": "f7e133cfc5e64240", + "metadataForms.hook.fields.retryPolicy.maxRetries.label": "422d764e2265642c", + "metadataForms.hook.fields.timeout.helpText": "31e114f26389aec7", + "metadataForms.hook.fields.timeout.label": "4e36a2d29b385090", + "metadataForms.mapping.description": "654a322ed6e264bb", + "metadataForms.mapping.label": "9baba989f46cd1e8", + "metadataForms.object.fields.enable.activities.label": "1091422f7f3133d3", + "metadataForms.object.fields.enable.apiEnabled.label": "34396a2f6cfda5d5", + "metadataForms.object.fields.enable.clone.label": "48cbbe43b82f32c2", + "metadataForms.object.fields.enable.feeds.label": "669c64671f632139", + "metadataForms.object.fields.enable.files.label": "411069282de23f9a", + "metadataForms.object.fields.enable.helpText": "554c6b16fa1e3a5a", + "metadataForms.object.fields.enable.label": "fdecf828d4e86eed", + "metadataForms.object.fields.enable.searchable.label": "d8f50099e7217830", + "metadataForms.object.fields.enable.trackHistory.label": "7bc28212817b2c43", + "metadataForms.object.fields.fields.autonumberFormat.helpText": "14efe56f7cf8812e", + "metadataForms.object.fields.fields.autonumberFormat.label": "ced754e3dde1e78f", + "metadataForms.object.fields.fields.deleteBehavior.helpText": "6d94538a80df113e", + "metadataForms.object.fields.fields.deleteBehavior.label": "01c3c66572ce674b", + "metadataForms.object.fields.fields.expression.helpText": "6c559ad697dbaa85", + "metadataForms.object.fields.fields.expression.label": "cd1b52ad1588075e", + "metadataForms.object.fields.fields.lookupFilters.helpText": "ce8004df6ef768f1", + "metadataForms.object.fields.fields.lookupFilters.label": "c2860cdcfe74ad9a", + "metadataForms.object.fields.fields.placeholder.helpText": "497ea6299e075744", + "metadataForms.object.fields.fields.placeholder.label": "44d62b55b63fe718", + "metadataForms.object.fields.fields.readonlyWhen.helpText": "1daba886fd4e7f25", + "metadataForms.object.fields.fields.readonlyWhen.label": "af1c3f486fb2fbf7", + "metadataForms.object.fields.fields.requiredWhen.helpText": "997a0d21e5e32203", + "metadataForms.object.fields.fields.requiredWhen.label": "89b4d31ea46e2e76", + "metadataForms.object.fields.fields.summaryOperations.field.helpText": "ff2f535b8f9cfffe", + "metadataForms.object.fields.fields.summaryOperations.field.label": "e21c314685cd95ca", + "metadataForms.object.fields.fields.summaryOperations.function.helpText": "b1022db50ca584b3", + "metadataForms.object.fields.fields.summaryOperations.function.label": "9ae3f998b780a051", + "metadataForms.object.fields.fields.summaryOperations.helpText": "4657cc4f93ecc549", + "metadataForms.object.fields.fields.summaryOperations.label": "bcf65520573738ee", + "metadataForms.object.fields.fields.summaryOperations.object.helpText": "7e6fc21663360844", + "metadataForms.object.fields.fields.summaryOperations.object.label": "d0c49032d95ebb36", + "metadataForms.object.fields.fields.visibleWhen.helpText": "46de2d3ff57b6667", + "metadataForms.object.fields.fields.visibleWhen.label": "c852d4249db93285", + "metadataForms.object.fields.lifecycle.archive.after.helpText": "e0e76ef140528e1c", + "metadataForms.object.fields.lifecycle.archive.after.label": "05e119362ddb98d6", + "metadataForms.object.fields.lifecycle.archive.helpText": "e1d3f49d818d0807", + "metadataForms.object.fields.lifecycle.archive.keep.helpText": "5c64726b12f2717a", + "metadataForms.object.fields.lifecycle.archive.keep.label": "a4a66d4839231b06", + "metadataForms.object.fields.lifecycle.archive.label": "dc721777496e2512", + "metadataForms.object.fields.lifecycle.archive.to.helpText": "b086a13b2c7cbd23", + "metadataForms.object.fields.lifecycle.archive.to.label": "ca6e09b7adfeb23f", + "metadataForms.object.fields.lifecycle.class.helpText": "a2a0c1b381c361cf", + "metadataForms.object.fields.lifecycle.class.label": "e9dba3be46bf7fea", + "metadataForms.object.fields.lifecycle.helpText": "608118f856cec104", + "metadataForms.object.fields.lifecycle.label": "aeb8410b6c716b40", + "metadataForms.object.fields.lifecycle.reclaim.helpText": "3214ee85bf5ec2d1", + "metadataForms.object.fields.lifecycle.reclaim.label": "a3be311d3ce0282c", + "metadataForms.object.fields.lifecycle.retention.helpText": "3d8f859e28eb37ec", + "metadataForms.object.fields.lifecycle.retention.label": "a672155e6c24e41a", + "metadataForms.object.fields.lifecycle.retention.maxAge.helpText": "7cd18232c490868c", + "metadataForms.object.fields.lifecycle.retention.maxAge.label": "3654cac5ba14e6ce", + "metadataForms.object.fields.lifecycle.storage.helpText": "dc5d38fadc7e273c", + "metadataForms.object.fields.lifecycle.storage.label": "6fb0804db64324af", + "metadataForms.object.fields.lifecycle.storage.shards.helpText": "d948a024425640ba", + "metadataForms.object.fields.lifecycle.storage.shards.label": "739988affb36b2e0", + "metadataForms.object.fields.lifecycle.storage.strategy.helpText": "93bf61da82533a75", + "metadataForms.object.fields.lifecycle.storage.strategy.label": "785c1dd353da380b", + "metadataForms.object.fields.lifecycle.storage.unit.helpText": "f43af4c056eab727", + "metadataForms.object.fields.lifecycle.storage.unit.label": "c53f07123f162a30", + "metadataForms.object.fields.lifecycle.ttl.expireAfter.helpText": "ed226b79ee7fd60f", + "metadataForms.object.fields.lifecycle.ttl.expireAfter.label": "27a86cf076af8e84", + "metadataForms.object.fields.lifecycle.ttl.field.helpText": "66edfde80e1ee2ff", + "metadataForms.object.fields.lifecycle.ttl.field.label": "e21c314685cd95ca", + "metadataForms.object.fields.lifecycle.ttl.helpText": "9b125bc1584a941f", + "metadataForms.object.fields.lifecycle.ttl.label": "e37616f70b0d157a", + "metadataForms.page.fields.interfaceConfig.addRecord.helpText": "3eb7b86c3a630db9", + "metadataForms.page.fields.interfaceConfig.addRecord.label": "01068706d8b5418a", + "metadataForms.page.fields.interfaceConfig.allowPrinting.helpText": "b8fa84e13cd1432c", + "metadataForms.page.fields.interfaceConfig.allowPrinting.label": "3d0f3f6f51215492", + "metadataForms.page.fields.interfaceConfig.appearance.helpText": "a6118f268ea9f841", + "metadataForms.page.fields.interfaceConfig.appearance.label": "e71255d10680838c", + "metadataForms.page.fields.interfaceConfig.buttons.helpText": "bdc9b266702b27d8", + "metadataForms.page.fields.interfaceConfig.buttons.label": "213eb6a492478539", + "metadataForms.page.fields.interfaceConfig.columns.helpText": "f2330254ad68acb8", + "metadataForms.page.fields.interfaceConfig.columns.label": "c849ea0a39dedbb6", + "metadataForms.page.fields.interfaceConfig.filterBy.helpText": "a73677b167a7379e", + "metadataForms.page.fields.interfaceConfig.filterBy.label": "141e52c71ef928bc", + "metadataForms.page.fields.interfaceConfig.helpText": "d99664c5cd130355", + "metadataForms.page.fields.interfaceConfig.label": "ebcdc9f327772aac", + "metadataForms.page.fields.interfaceConfig.levels.helpText": "6d33feacb82984ff", + "metadataForms.page.fields.interfaceConfig.levels.label": "f90c5b5c1457e991", + "metadataForms.page.fields.interfaceConfig.recordAction.helpText": "2356903762168e64", + "metadataForms.page.fields.interfaceConfig.recordAction.label": "b6e738e297b43df0", + "metadataForms.page.fields.interfaceConfig.showRecordCount.helpText": "23de51872ec0e7a5", + "metadataForms.page.fields.interfaceConfig.showRecordCount.label": "3f45f6242141d01a", + "metadataForms.page.fields.interfaceConfig.sort.helpText": "15ebb2c0a3d2390e", + "metadataForms.page.fields.interfaceConfig.sort.label": "4c590a0ed0c02eff", + "metadataForms.page.fields.interfaceConfig.source.helpText": "f3351049c2af63e7", + "metadataForms.page.fields.interfaceConfig.source.label": "8fe786e8e29c8cec", + "metadataForms.page.fields.interfaceConfig.userActions.helpText": "f720e5e6f4f35dae", + "metadataForms.page.fields.interfaceConfig.userActions.label": "3aea42abc7d6ffd0", + "metadataForms.page.fields.interfaceConfig.userFilters.helpText": "8791bc40505cac7d", + "metadataForms.page.fields.interfaceConfig.userFilters.label": "7e2bbb262bd29aa1", + "metadataForms.page.sections.interface.description": "7e60b977279a05dc", + "metadataForms.page.sections.interface.label": "e2280abc5267ddec", + "metadataForms.report.fields.dataset.helpText": "3cd157f274c985df", + "metadataForms.report.fields.dataset.label": "19647dcb84ce82d3", + "metadataForms.report.fields.drilldown.helpText": "778174eac8057764", + "metadataForms.report.fields.drilldown.label": "25ae77d240ced5f5", + "metadataForms.report.fields.rows.helpText": "4307edcc3f3097b5", + "metadataForms.report.fields.rows.label": "319af74cc41ea823", + "metadataForms.report.fields.runtimeFilter.helpText": "eb9ba8aeb55b7d40", + "metadataForms.report.fields.runtimeFilter.label": "90fe0e44293633fa", + "metadataForms.report.fields.values.helpText": "0cd8bc86069a89d1", + "metadataForms.report.fields.values.label": "3edea9880d8fee80", + "metadataForms.report.sections.dataset_binding.description": "54650ff5f09fd1bd", + "metadataForms.report.sections.dataset_binding.label": "34d97a3093d6fc56", + "metadataForms.seed.description": "c22921feb0f06273", + "metadataForms.seed.label": "60e0e22a54230cab", + "objects.sys_account._actions.link_social.params.provider.options.apple": "cfdc41e15ed6699b", + "objects.sys_account._actions.link_social.params.provider.options.discord": "12f931cc062e76ae", + "objects.sys_account._actions.link_social.params.provider.options.facebook": "7eea009178f5b807", + "objects.sys_account._actions.link_social.params.provider.options.github": "2971d247eb4e6abc", + "objects.sys_account._actions.link_social.params.provider.options.gitlab": "bc3dbbf4b650e600", + "objects.sys_account._actions.link_social.params.provider.options.google": "6fadcd05bb8da367", + "objects.sys_account._actions.link_social.params.provider.options.microsoft": "17309efbdb1ec122", + "objects.sys_account.fields.access_token.help": "814695a136172228", + "objects.sys_account.fields.id_token.help": "b545f62224b21f69", + "objects.sys_account.fields.previous_password_hashes.help": "3a98e8164b0daf11", + "objects.sys_account.fields.previous_password_hashes.label": "2aa498ce717c7d77", + "objects.sys_account.fields.refresh_token.help": "f337bcd15c9b4cb3", + "objects.sys_api_key.fields.active_organization_id.help": "74718b93fdaa52cc", + "objects.sys_api_key.fields.active_organization_id.label": "51bce7fbc99fbf87", + "objects.sys_email.fields.cc_addresses.label": "02946d952cf15623", + "objects.sys_email.fields.error.label": "786fed84bd8d5a32", + "objects.sys_email.fields.message_id.label": "14cd089a4062f4b1", + "objects.sys_email_template.fields.customized.help": "fc75c32a37f906ea", + "objects.sys_email_template.fields.customized.label": "6990986111e53429", + "objects.sys_email_template.fields.id.label": "00b0385c9c152888", + "objects.sys_email_template.fields.managed_by.help": "0d7397e9e0e84cfe", + "objects.sys_email_template.fields.managed_by.label": "be9070801491e6af", + "objects.sys_email_template.fields.managed_by.options.admin": "f8c9f123caf6217d", + "objects.sys_email_template.fields.managed_by.options.package": "1c8c6168730324a3", + "objects.sys_email_template.fields.managed_by.options.platform": "820d30a5082ebc6c", + "objects.sys_email_template.fields.variables_json.label": "c2cfff50e8151e3d", + "objects.sys_invitation.fields.business_unit_id.help": "b4e37b1bfcc40f34", + "objects.sys_invitation.fields.business_unit_id.label": "bf0e0fdbda66ec9b", + "objects.sys_invitation.fields.positions.help": "abfa7911007e38f1", + "objects.sys_invitation.fields.positions.label": "3428d066379ee33b", + "objects.sys_invitation.fields.role.options.delegated_admin": "13f0e7f118c839e0", + "objects.sys_job_run.fields.error.label": "786fed84bd8d5a32", + "objects.sys_member._views.mine.emptyState.message": "36cf7e3a3eebed0b", + "objects.sys_member._views.mine.emptyState.title": "df533ee25aba6de9", + "objects.sys_member._views.mine.label": "46b6227bcdb2d02e", + "objects.sys_member.fields.role.options.delegated_admin": "13f0e7f118c839e0", + "objects.sys_metadata.fields.checksum.label": "4fe7cc0967282ec9", + "objects.sys_metadata.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_audit.fields.actor.label": "b155813f8a7f06e3", + "objects.sys_metadata_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_audit.fields.lock_state.options.nodelete": "248b810d57fe8bb9", + "objects.sys_metadata_audit.fields.lock_state.options.nooverlay": "49a44e4a03ff124c", + "objects.sys_metadata_history.fields.checksum.label": "4fe7cc0967282ec9", + "objects.sys_metadata_history.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_history.fields.recorded_by.help": "e293bd6cf3c47060", + "objects.sys_migration.description": "2124b4abfb243e56", + "objects.sys_migration.fields.advisory.help": "ff96ea989b140e52", + "objects.sys_migration.fields.advisory.label": "b32c5f9a93d20e02", + "objects.sys_migration.fields.applied_at.help": "7414bd643f6b30cf", + "objects.sys_migration.fields.applied_at.label": "e95c287d92cd1215", + "objects.sys_migration.fields.blocking.help": "e807388f81f23dda", + "objects.sys_migration.fields.blocking.label": "fde63021cfe650a1", + "objects.sys_migration.fields.created_at.label": "1f02d416befb595b", + "objects.sys_migration.fields.details.help": "315f8003d77cf2b9", + "objects.sys_migration.fields.details.label": "974694e5c164374e", + "objects.sys_migration.fields.deviation_detail.help": "681fe28dc0c34386", + "objects.sys_migration.fields.deviation_detail.label": "5bf0f0f59a89e2fb", + "objects.sys_migration.fields.deviation_observed_at.help": "d6720808eb570acd", + "objects.sys_migration.fields.deviation_observed_at.label": "d68ffbef206322e0", + "objects.sys_migration.fields.id.help": "5cf4db327551205b", + "objects.sys_migration.fields.id.label": "349621d298d12bd4", + "objects.sys_migration.fields.last_run_at.help": "721c7ab4ce68bab9", + "objects.sys_migration.fields.last_run_at.label": "0540810f87bb4ac0", + "objects.sys_migration.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_migration.fields.verified_at.help": "ef3ed908c6407126", + "objects.sys_migration.fields.verified_at.label": "a1115e0d1677f1af", + "objects.sys_migration.label": "97fdaf1d2e0d8c2a", + "objects.sys_migration.pluralLabel": "91d61a84b5ff632b", + "objects.sys_notification._views.by_topic.label": "a0ddbe432474ed94", + "objects.sys_notification._views.recent.emptyState.message": "4a1e91a49eaf59d6", + "objects.sys_notification._views.recent.emptyState.title": "6f708a09bb4c9a5c", + "objects.sys_notification._views.recent.label": "62d27bb9d0349c99", + "objects.sys_notification.fields.actor_id.label": "b155813f8a7f06e3", + "objects.sys_notification.fields.dedup_key.help": "58a5e28b6a87e6d6", + "objects.sys_notification.fields.dedup_key.label": "33a740837f882c98", + "objects.sys_notification.fields.payload.help": "e1e8514cd730b06f", + "objects.sys_notification.fields.payload.label": "4488e1328e37dcb2", + "objects.sys_notification.fields.severity.help": "4925b29cf7e10346", + "objects.sys_notification.fields.severity.label": "d59e4345bdc57b59", + "objects.sys_notification.fields.severity.options.critical": "7ff0ff69c0abaf81", + "objects.sys_notification.fields.severity.options.info": "3e0c8611029f253b", + "objects.sys_notification.fields.severity.options.warning": "2673ee95caf83284", + "objects.sys_notification.fields.topic.help": "80e1790edfda49df", + "objects.sys_notification.fields.topic.label": "819afdb3853e9d80", + "objects.sys_oauth_access_token.fields.authorization_code_id.help": "84ad4d5d8c4c1cee", + "objects.sys_oauth_access_token.fields.authorization_code_id.label": "1c1d06cc01295862", + "objects.sys_oauth_access_token.fields.confirmation.help": "9d086fdc90baa01d", + "objects.sys_oauth_access_token.fields.confirmation.label": "64755493e6425b37", + "objects.sys_oauth_access_token.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_access_token.fields.requested_user_info_claims.help": "dc0cd8121cbde60f", + "objects.sys_oauth_access_token.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_access_token.fields.resources.help": "8c153517b30bd6a5", + "objects.sys_oauth_access_token.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_access_token.fields.revoked.help": "ff6a4b6dac031959", + "objects.sys_oauth_access_token.fields.revoked.label": "054f918e632528c7", + "objects.sys_oauth_access_token.fields.token.label": "6e8de3393d2cee54", + "objects.sys_oauth_application._actions.create_oauth_application.params.type.options.web": "2a7ead3da8c035c5", + "objects.sys_oauth_application._views.mine.label": "d1cb1174cd96176c", + "objects.sys_oauth_application.fields.backchannel_logout_session_required.help": "2d12d1e1f9461576", + "objects.sys_oauth_application.fields.backchannel_logout_session_required.label": "fbc6eee84fb8a1d7", + "objects.sys_oauth_application.fields.backchannel_logout_uri.help": "e7c8abeac494b145", + "objects.sys_oauth_application.fields.backchannel_logout_uri.label": "f78777a91cebd19f", + "objects.sys_oauth_application.fields.client_credentials_scopes.help": "bf69fe7f91bb78e7", + "objects.sys_oauth_application.fields.client_credentials_scopes.label": "e257098b80b760d2", + "objects.sys_oauth_application.fields.client_discovery_id.help": "6d7fff4d4e7fb705", + "objects.sys_oauth_application.fields.client_discovery_id.label": "2070f4b2f6456352", + "objects.sys_oauth_application.fields.dpop_bound_access_tokens.help": "e7257c5c6ea7043b", + "objects.sys_oauth_application.fields.dpop_bound_access_tokens.label": "3772715f1d9588af", + "objects.sys_oauth_application.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_application.fields.jwks.help": "6a6c5fe835f213b8", + "objects.sys_oauth_application.fields.jwks.label": "b2cf79f9d66c0dbc", + "objects.sys_oauth_application.fields.jwks_uri.help": "217cff7cca47a35e", + "objects.sys_oauth_application.fields.jwks_uri.label": "2a4339df734ab269", + "objects.sys_oauth_client_assertion.description": "82a3f5fec7571bd4", + "objects.sys_oauth_client_assertion.fields.expires_at.help": "90b732ea4d289003", + "objects.sys_oauth_client_assertion.fields.expires_at.label": "df0ef5fae02b2044", + "objects.sys_oauth_client_assertion.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_client_assertion.label": "964d685e79999dce", + "objects.sys_oauth_client_assertion.pluralLabel": "286f417fcbf2ab81", + "objects.sys_oauth_client_resource.description": "c63dda9f9c51165b", + "objects.sys_oauth_client_resource.fields.client_id.help": "8df79a6c07fc32b4", + "objects.sys_oauth_client_resource.fields.client_id.label": "09cb452151e7b1b6", + "objects.sys_oauth_client_resource.fields.created_at.label": "1f02d416befb595b", + "objects.sys_oauth_client_resource.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_client_resource.fields.metadata.help": "c51d0437d1b00c8b", + "objects.sys_oauth_client_resource.fields.metadata.label": "9cabc04013c80ef3", + "objects.sys_oauth_client_resource.fields.resource_id.help": "5c7d52276882432d", + "objects.sys_oauth_client_resource.fields.resource_id.label": "21668ca570b12d71", + "objects.sys_oauth_client_resource.label": "882f5ca283a58ed1", + "objects.sys_oauth_client_resource.pluralLabel": "ab6a91aa51de6ac0", + "objects.sys_oauth_consent.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_consent.fields.requested_user_info_claims.help": "6f1fc3c5e6b4031d", + "objects.sys_oauth_consent.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_consent.fields.resources.help": "f1e24c7b80ba287a", + "objects.sys_oauth_consent.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_refresh_token.fields.authorization_code_id.help": "b16f370e0a06cf43", + "objects.sys_oauth_refresh_token.fields.authorization_code_id.label": "1c1d06cc01295862", + "objects.sys_oauth_refresh_token.fields.confirmation.help": "9d086fdc90baa01d", + "objects.sys_oauth_refresh_token.fields.confirmation.label": "64755493e6425b37", + "objects.sys_oauth_refresh_token.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_refresh_token.fields.requested_user_info_claims.help": "dc0cd8121cbde60f", + "objects.sys_oauth_refresh_token.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_refresh_token.fields.resources.help": "8c153517b30bd6a5", + "objects.sys_oauth_refresh_token.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_refresh_token.fields.rotated_at.help": "704d34a1f46f4412", + "objects.sys_oauth_refresh_token.fields.rotated_at.label": "aa747daa25d440d1", + "objects.sys_oauth_refresh_token.fields.rotation_replay_expires_at.help": "7f5925e4b2d73586", + "objects.sys_oauth_refresh_token.fields.rotation_replay_expires_at.label": "f6a0bbe6ef73bded", + "objects.sys_oauth_refresh_token.fields.rotation_replay_response.help": "ea6d3f140afbbca7", + "objects.sys_oauth_refresh_token.fields.rotation_replay_response.label": "ddf6cf0ccba55c30", + "objects.sys_oauth_refresh_token.fields.token.label": "6e8de3393d2cee54", + "objects.sys_oauth_resource.description": "3c7f0cfae5b1a887", + "objects.sys_oauth_resource.fields.access_token_ttl.help": "293dc402a9669032", + "objects.sys_oauth_resource.fields.access_token_ttl.label": "afebf5750ed4d9ee", + "objects.sys_oauth_resource.fields.allowed_scopes.help": "af9195bf5efc4b27", + "objects.sys_oauth_resource.fields.allowed_scopes.label": "1a3202eecb314841", + "objects.sys_oauth_resource.fields.created_at.label": "1f02d416befb595b", + "objects.sys_oauth_resource.fields.custom_claims.help": "0afe2b2a099c7813", + "objects.sys_oauth_resource.fields.custom_claims.label": "c6182d11456325c4", + "objects.sys_oauth_resource.fields.disabled.label": "639459b8a7bbec04", + "objects.sys_oauth_resource.fields.dpop_bound_access_tokens_required.help": "d82b668d32d9d48f", + "objects.sys_oauth_resource.fields.dpop_bound_access_tokens_required.label": "715d383f9fc4fcfb", + "objects.sys_oauth_resource.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_resource.fields.identifier.help": "b69d2662e0438576", + "objects.sys_oauth_resource.fields.identifier.label": "85b28b4a09f37cd0", + "objects.sys_oauth_resource.fields.metadata.help": "896f351a40678f21", + "objects.sys_oauth_resource.fields.metadata.label": "9cabc04013c80ef3", + "objects.sys_oauth_resource.fields.name.label": "5274417ff1ccaf01", + "objects.sys_oauth_resource.fields.policy_version.help": "18b93d988a1d6107", + "objects.sys_oauth_resource.fields.policy_version.label": "55fdeadcaba6cd2a", + "objects.sys_oauth_resource.fields.refresh_token_ttl.help": "9bbb700667d7cee7", + "objects.sys_oauth_resource.fields.refresh_token_ttl.label": "e4b35b5501ac588f", + "objects.sys_oauth_resource.fields.signing_algorithm.help": "3f8231cc5435a673", + "objects.sys_oauth_resource.fields.signing_algorithm.label": "c26616cf69746887", + "objects.sys_oauth_resource.fields.signing_key_id.help": "8504e8cddac3b67c", + "objects.sys_oauth_resource.fields.signing_key_id.label": "50da11ac4977fa58", + "objects.sys_oauth_resource.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_oauth_resource.label": "5077fa14e8d7418e", + "objects.sys_oauth_resource.pluralLabel": "1c6ad6e11287866f", + "objects.sys_organization.fields.parent_organization_id.help": "2ad4ebe3782901b9", + "objects.sys_organization.fields.parent_organization_id.label": "24129608643b897f", + "objects.sys_organization.fields.slug.label": "fe4132df05ec637b", + "objects.sys_organization.fields.sort_order.help": "7355b98a96f85dfc", + "objects.sys_organization.fields.sort_order.label": "5f6b33fdc89e9d9f", + "objects.sys_scim_provider.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_provider.fields.provider_key.help": "6eba9e41bfb954ab", + "objects.sys_scim_provider.fields.provider_key.label": "fbc96a8b3ed4709d", + "objects.sys_secret._views.all.label": "20d032bd60c81773", + "objects.sys_secret.fields.id.label": "00b0385c9c152888", + "objects.sys_session.fields.last_activity_at.help": "f7851e9373505e73", + "objects.sys_session.fields.last_activity_at.label": "43bd2f1b231bc12b", + "objects.sys_session.fields.revoke_reason.help": "ff2c9a1aa9be356d", + "objects.sys_session.fields.revoke_reason.label": "13b0146153a2ecf8", + "objects.sys_session.fields.revoked_at.help": "62d1b62cde5ce487", + "objects.sys_session.fields.revoked_at.label": "054f918e632528c7", + "objects.sys_setting.fields.scope.options.global": "5e377106508d2ecd", + "objects.sys_setting_audit._views.recent.label": "62d27bb9d0349c99", + "objects.sys_setting_audit.fields.actor_id.label": "b155813f8a7f06e3", + "objects.sys_setting_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_setting_audit.fields.scope.options.global": "5e377106508d2ecd", + "objects.sys_setting_audit.fields.source.options.api": "4baeeff968e192d1", + "objects.sys_setting_audit.fields.source.options.ui": "95caad102a5c97ba", + "objects.sys_sso_provider._actions.register_saml_provider.params.identifierFormat.placeholder": "5945ff9a6b644855", + "objects.sys_sso_provider._actions.register_sso_provider.params.mapEmail.placeholder": "66309c7adf5c9436", + "objects.sys_sso_provider._actions.register_sso_provider.params.mapName.placeholder": "a484c34aaf624bd6", + "objects.sys_sso_provider._actions.register_sso_provider.params.scopes.placeholder": "58ac90cde28c764d", + "objects.sys_sso_provider.fields.id.label": "00b0385c9c152888", + "objects.sys_team.fields.member_count.help": "498d7d7119ffff12", + "objects.sys_team.fields.member_count.label": "7bb48f7c92af14e0", + "objects.sys_team_member.fields.membership_key.help": "08a5017306c3ac4e", + "objects.sys_team_member.fields.membership_key.label": "8fcb9f17bcc8cae9", + "objects.sys_two_factor.fields.failed_verification_count.help": "8cea41d6de7f4d5a", + "objects.sys_two_factor.fields.failed_verification_count.label": "e0438d2e39cb0500", + "objects.sys_two_factor.fields.locked_until.help": "059956062c752f2c", + "objects.sys_two_factor.fields.locked_until.label": "c7f013ad9b7208d2", + "objects.sys_two_factor.fields.verified.help": "0d351ba4c31608c2", + "objects.sys_two_factor.fields.verified.label": "866e3a245ef04734", + "objects.sys_user.fields.manager_id.help": "a53dec9ccd9d131f", + "objects.sys_user.fields.primary_business_unit_id.help": "e01690f4a9b83956", + "objects.sys_view_definition.fields.id.label": "00b0385c9c152888", +}; diff --git a/packages/platform-objects/src/apps/translations/index.ts b/packages/platform-objects/src/apps/translations/index.ts index 1018919316..2633cc7f6d 100644 --- a/packages/platform-objects/src/apps/translations/index.ts +++ b/packages/platform-objects/src/apps/translations/index.ts @@ -9,3 +9,23 @@ export { en } from './en.js'; export { zhCN } from './zh-CN.js'; export { jaJP } from './ja-JP.js'; export { esES } from './es-ES.js'; + +/** + * The staleness mechanism (#8765 Option B, extended to the generated bundles by + * the #12069 Option A ruling). Exported because `os i18n extract` writes the + * generated half of the records and must use THIS hash function and THIS rule — + * a second copy in the CLI would be the "second mechanism" the ruling forbids. + */ +export { + HAND_AUTHORED_SECTIONS, + GENERATED_SECTIONS, + hashSource, + collectSourceLeaves, + collectGeneratedLeaves, + collectSourceHashes, + collectFilledFromHashes, + findStaleLeaves, + findStaleFills, + withSourceFallback, +} from './source-hash.js'; +export type { SourceHashes, StaleLeaf, StaleFill } from './source-hash.js'; diff --git a/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.generated.ts b/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.generated.ts new file mode 100644 index 0000000000..a23d7d08d4 --- /dev/null +++ b/packages/platform-objects/src/apps/translations/ja-JP.source-hashes.generated.ts @@ -0,0 +1,410 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ja-JP'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const jaJPGeneratedSourceHashes: Readonly> = { + "metadataForms.action.fields.ai.helpText": "e8c9a5009eb96ef6", + "metadataForms.action.fields.ai.label": "17ad12a926c9f22d", + "metadataForms.action.fields.body.capabilities.helpText": "2db3f83342bd3fa1", + "metadataForms.action.fields.body.capabilities.label": "0b59005e10add41f", + "metadataForms.action.fields.body.language.helpText": "b0ad78a396675964", + "metadataForms.action.fields.body.language.label": "012664c233fc15b0", + "metadataForms.action.fields.body.memoryMb.helpText": "f53c826ce3c94ad9", + "metadataForms.action.fields.body.memoryMb.label": "a1e1612eff7f82ea", + "metadataForms.action.fields.body.source.helpText": "3a066e75948ec72c", + "metadataForms.action.fields.body.source.label": "8fe786e8e29c8cec", + "metadataForms.action.fields.body.timeoutMs.helpText": "47042e57f537d281", + "metadataForms.action.fields.body.timeoutMs.label": "2b887c62238d3532", + "metadataForms.api.description": "62421e8039e013f5", + "metadataForms.api.label": "589af3fe4aab4af5", + "metadataForms.book.description": "45e72c8c74617530", + "metadataForms.book.label": "b48c719759fca087", + "metadataForms.capability.description": "1130a8a487c35f3d", + "metadataForms.capability.label": "cb58b72c5cc77632", + "metadataForms.dataset.description": "ce0d0a4d536bbc1e", + "metadataForms.dataset.fields.description.helpText": "f82c5070e1f78508", + "metadataForms.dataset.fields.description.label": "779a4342c8eb6707", + "metadataForms.dataset.fields.dimensions.helpText": "a4df7393a6bd88c9", + "metadataForms.dataset.fields.dimensions.label": "d75e6dd9d87e93b4", + "metadataForms.dataset.fields.filter.helpText": "8d996f9121810664", + "metadataForms.dataset.fields.filter.label": "802895ec427fc6af", + "metadataForms.dataset.fields.include.helpText": "e8f9933ae4f99e77", + "metadataForms.dataset.fields.include.label": "45b27393fb9f3b2a", + "metadataForms.dataset.fields.label.helpText": "f0cbad8e334db1d3", + "metadataForms.dataset.fields.label.label": "c680364257b11569", + "metadataForms.dataset.fields.measures.label": "395dac46abcf1943", + "metadataForms.dataset.fields.name.helpText": "8c3cae6dba12b42a", + "metadataForms.dataset.fields.name.label": "5274417ff1ccaf01", + "metadataForms.dataset.fields.object.helpText": "09a85579054a7197", + "metadataForms.dataset.fields.object.label": "d0c49032d95ebb36", + "metadataForms.dataset.label": "19647dcb84ce82d3", + "metadataForms.dataset.sections.basics.description": "844b82a322f8edd6", + "metadataForms.dataset.sections.basics.label": "55562f900da68c5d", + "metadataForms.dataset.sections.dimensions.description": "da19c8faa0cb1023", + "metadataForms.dataset.sections.dimensions.label": "d75e6dd9d87e93b4", + "metadataForms.dataset.sections.measures.description": "8186d1c0fe6a86f4", + "metadataForms.dataset.sections.measures.label": "395dac46abcf1943", + "metadataForms.dataset.sections.source.description": "ee9be5509026b678", + "metadataForms.dataset.sections.source.label": "8fe786e8e29c8cec", + "metadataForms.doc.description": "f233af60238b0879", + "metadataForms.doc.label": "3f35cf5088b999ad", + "metadataForms.field.fields.placeholder.helpText": "07cecac0844860be", + "metadataForms.field.fields.placeholder.label": "44d62b55b63fe718", + "metadataForms.field.fields.summaryOperations.field.helpText": "b6897e7341b31c09", + "metadataForms.field.fields.summaryOperations.field.label": "e21c314685cd95ca", + "metadataForms.field.fields.summaryOperations.filter.helpText": "7a46d2abde5a25c4", + "metadataForms.field.fields.summaryOperations.filter.label": "802895ec427fc6af", + "metadataForms.field.fields.summaryOperations.function.helpText": "b1022db50ca584b3", + "metadataForms.field.fields.summaryOperations.function.label": "9ae3f998b780a051", + "metadataForms.field.fields.summaryOperations.object.helpText": "d937a7f5e5d922cf", + "metadataForms.field.fields.summaryOperations.object.label": "d0c49032d95ebb36", + "metadataForms.field.fields.summaryOperations.relationshipField.helpText": "5d8a9925c9a1b45a", + "metadataForms.field.fields.summaryOperations.relationshipField.label": "d8e119c564a5453a", + "metadataForms.hook.fields.body.memoryMb.helpText": "f53c826ce3c94ad9", + "metadataForms.hook.fields.body.memoryMb.label": "a1e1612eff7f82ea", + "metadataForms.hook.fields.retryPolicy.backoffMs.helpText": "42137a83459ed6b7", + "metadataForms.hook.fields.retryPolicy.backoffMs.label": "cad56dd875f84c22", + "metadataForms.hook.fields.retryPolicy.helpText": "de59f5fbb8ad7fda", + "metadataForms.hook.fields.retryPolicy.label": "7ec5dad15f687f05", + "metadataForms.hook.fields.retryPolicy.maxRetries.helpText": "f7e133cfc5e64240", + "metadataForms.hook.fields.retryPolicy.maxRetries.label": "422d764e2265642c", + "metadataForms.hook.fields.timeout.helpText": "31e114f26389aec7", + "metadataForms.hook.fields.timeout.label": "4e36a2d29b385090", + "metadataForms.mapping.description": "654a322ed6e264bb", + "metadataForms.mapping.label": "9baba989f46cd1e8", + "metadataForms.object.fields.enable.activities.label": "1091422f7f3133d3", + "metadataForms.object.fields.enable.apiEnabled.label": "34396a2f6cfda5d5", + "metadataForms.object.fields.enable.clone.label": "48cbbe43b82f32c2", + "metadataForms.object.fields.enable.feeds.label": "669c64671f632139", + "metadataForms.object.fields.enable.files.label": "411069282de23f9a", + "metadataForms.object.fields.enable.helpText": "554c6b16fa1e3a5a", + "metadataForms.object.fields.enable.label": "fdecf828d4e86eed", + "metadataForms.object.fields.enable.searchable.label": "d8f50099e7217830", + "metadataForms.object.fields.enable.trackHistory.label": "7bc28212817b2c43", + "metadataForms.object.fields.fields.autonumberFormat.helpText": "14efe56f7cf8812e", + "metadataForms.object.fields.fields.autonumberFormat.label": "ced754e3dde1e78f", + "metadataForms.object.fields.fields.deleteBehavior.helpText": "6d94538a80df113e", + "metadataForms.object.fields.fields.deleteBehavior.label": "01c3c66572ce674b", + "metadataForms.object.fields.fields.expression.helpText": "6c559ad697dbaa85", + "metadataForms.object.fields.fields.expression.label": "cd1b52ad1588075e", + "metadataForms.object.fields.fields.lookupFilters.helpText": "ce8004df6ef768f1", + "metadataForms.object.fields.fields.lookupFilters.label": "c2860cdcfe74ad9a", + "metadataForms.object.fields.fields.placeholder.helpText": "497ea6299e075744", + "metadataForms.object.fields.fields.placeholder.label": "44d62b55b63fe718", + "metadataForms.object.fields.fields.readonlyWhen.helpText": "1daba886fd4e7f25", + "metadataForms.object.fields.fields.readonlyWhen.label": "af1c3f486fb2fbf7", + "metadataForms.object.fields.fields.requiredWhen.helpText": "997a0d21e5e32203", + "metadataForms.object.fields.fields.requiredWhen.label": "89b4d31ea46e2e76", + "metadataForms.object.fields.fields.summaryOperations.field.helpText": "ff2f535b8f9cfffe", + "metadataForms.object.fields.fields.summaryOperations.field.label": "e21c314685cd95ca", + "metadataForms.object.fields.fields.summaryOperations.function.helpText": "b1022db50ca584b3", + "metadataForms.object.fields.fields.summaryOperations.function.label": "9ae3f998b780a051", + "metadataForms.object.fields.fields.summaryOperations.helpText": "4657cc4f93ecc549", + "metadataForms.object.fields.fields.summaryOperations.label": "bcf65520573738ee", + "metadataForms.object.fields.fields.summaryOperations.object.helpText": "7e6fc21663360844", + "metadataForms.object.fields.fields.summaryOperations.object.label": "d0c49032d95ebb36", + "metadataForms.object.fields.fields.visibleWhen.helpText": "46de2d3ff57b6667", + "metadataForms.object.fields.fields.visibleWhen.label": "c852d4249db93285", + "metadataForms.object.fields.lifecycle.archive.after.helpText": "e0e76ef140528e1c", + "metadataForms.object.fields.lifecycle.archive.after.label": "05e119362ddb98d6", + "metadataForms.object.fields.lifecycle.archive.helpText": "e1d3f49d818d0807", + "metadataForms.object.fields.lifecycle.archive.keep.helpText": "5c64726b12f2717a", + "metadataForms.object.fields.lifecycle.archive.keep.label": "a4a66d4839231b06", + "metadataForms.object.fields.lifecycle.archive.label": "dc721777496e2512", + "metadataForms.object.fields.lifecycle.archive.to.helpText": "b086a13b2c7cbd23", + "metadataForms.object.fields.lifecycle.archive.to.label": "ca6e09b7adfeb23f", + "metadataForms.object.fields.lifecycle.class.helpText": "a2a0c1b381c361cf", + "metadataForms.object.fields.lifecycle.class.label": "e9dba3be46bf7fea", + "metadataForms.object.fields.lifecycle.helpText": "608118f856cec104", + "metadataForms.object.fields.lifecycle.label": "aeb8410b6c716b40", + "metadataForms.object.fields.lifecycle.reclaim.helpText": "3214ee85bf5ec2d1", + "metadataForms.object.fields.lifecycle.reclaim.label": "a3be311d3ce0282c", + "metadataForms.object.fields.lifecycle.retention.helpText": "3d8f859e28eb37ec", + "metadataForms.object.fields.lifecycle.retention.label": "a672155e6c24e41a", + "metadataForms.object.fields.lifecycle.retention.maxAge.helpText": "7cd18232c490868c", + "metadataForms.object.fields.lifecycle.retention.maxAge.label": "3654cac5ba14e6ce", + "metadataForms.object.fields.lifecycle.storage.helpText": "dc5d38fadc7e273c", + "metadataForms.object.fields.lifecycle.storage.label": "6fb0804db64324af", + "metadataForms.object.fields.lifecycle.storage.shards.helpText": "d948a024425640ba", + "metadataForms.object.fields.lifecycle.storage.shards.label": "739988affb36b2e0", + "metadataForms.object.fields.lifecycle.storage.strategy.helpText": "93bf61da82533a75", + "metadataForms.object.fields.lifecycle.storage.strategy.label": "785c1dd353da380b", + "metadataForms.object.fields.lifecycle.storage.unit.helpText": "f43af4c056eab727", + "metadataForms.object.fields.lifecycle.storage.unit.label": "c53f07123f162a30", + "metadataForms.object.fields.lifecycle.ttl.expireAfter.helpText": "ed226b79ee7fd60f", + "metadataForms.object.fields.lifecycle.ttl.expireAfter.label": "27a86cf076af8e84", + "metadataForms.object.fields.lifecycle.ttl.field.helpText": "66edfde80e1ee2ff", + "metadataForms.object.fields.lifecycle.ttl.field.label": "e21c314685cd95ca", + "metadataForms.object.fields.lifecycle.ttl.helpText": "9b125bc1584a941f", + "metadataForms.object.fields.lifecycle.ttl.label": "e37616f70b0d157a", + "metadataForms.page.fields.interfaceConfig.addRecord.helpText": "3eb7b86c3a630db9", + "metadataForms.page.fields.interfaceConfig.addRecord.label": "01068706d8b5418a", + "metadataForms.page.fields.interfaceConfig.allowPrinting.helpText": "b8fa84e13cd1432c", + "metadataForms.page.fields.interfaceConfig.allowPrinting.label": "3d0f3f6f51215492", + "metadataForms.page.fields.interfaceConfig.appearance.helpText": "a6118f268ea9f841", + "metadataForms.page.fields.interfaceConfig.appearance.label": "e71255d10680838c", + "metadataForms.page.fields.interfaceConfig.buttons.helpText": "bdc9b266702b27d8", + "metadataForms.page.fields.interfaceConfig.buttons.label": "213eb6a492478539", + "metadataForms.page.fields.interfaceConfig.columns.helpText": "f2330254ad68acb8", + "metadataForms.page.fields.interfaceConfig.columns.label": "c849ea0a39dedbb6", + "metadataForms.page.fields.interfaceConfig.filterBy.helpText": "a73677b167a7379e", + "metadataForms.page.fields.interfaceConfig.filterBy.label": "141e52c71ef928bc", + "metadataForms.page.fields.interfaceConfig.helpText": "d99664c5cd130355", + "metadataForms.page.fields.interfaceConfig.label": "ebcdc9f327772aac", + "metadataForms.page.fields.interfaceConfig.levels.helpText": "6d33feacb82984ff", + "metadataForms.page.fields.interfaceConfig.levels.label": "f90c5b5c1457e991", + "metadataForms.page.fields.interfaceConfig.recordAction.helpText": "2356903762168e64", + "metadataForms.page.fields.interfaceConfig.recordAction.label": "b6e738e297b43df0", + "metadataForms.page.fields.interfaceConfig.showRecordCount.helpText": "23de51872ec0e7a5", + "metadataForms.page.fields.interfaceConfig.showRecordCount.label": "3f45f6242141d01a", + "metadataForms.page.fields.interfaceConfig.sort.helpText": "15ebb2c0a3d2390e", + "metadataForms.page.fields.interfaceConfig.sort.label": "4c590a0ed0c02eff", + "metadataForms.page.fields.interfaceConfig.source.helpText": "f3351049c2af63e7", + "metadataForms.page.fields.interfaceConfig.source.label": "8fe786e8e29c8cec", + "metadataForms.page.fields.interfaceConfig.userActions.helpText": "f720e5e6f4f35dae", + "metadataForms.page.fields.interfaceConfig.userActions.label": "3aea42abc7d6ffd0", + "metadataForms.page.fields.interfaceConfig.userFilters.helpText": "8791bc40505cac7d", + "metadataForms.page.fields.interfaceConfig.userFilters.label": "7e2bbb262bd29aa1", + "metadataForms.page.sections.interface.description": "7e60b977279a05dc", + "metadataForms.page.sections.interface.label": "e2280abc5267ddec", + "metadataForms.report.fields.dataset.helpText": "3cd157f274c985df", + "metadataForms.report.fields.dataset.label": "19647dcb84ce82d3", + "metadataForms.report.fields.drilldown.helpText": "778174eac8057764", + "metadataForms.report.fields.drilldown.label": "25ae77d240ced5f5", + "metadataForms.report.fields.rows.helpText": "4307edcc3f3097b5", + "metadataForms.report.fields.rows.label": "319af74cc41ea823", + "metadataForms.report.fields.runtimeFilter.helpText": "eb9ba8aeb55b7d40", + "metadataForms.report.fields.runtimeFilter.label": "90fe0e44293633fa", + "metadataForms.report.fields.values.helpText": "0cd8bc86069a89d1", + "metadataForms.report.fields.values.label": "3edea9880d8fee80", + "metadataForms.report.sections.dataset_binding.description": "54650ff5f09fd1bd", + "metadataForms.report.sections.dataset_binding.label": "34d97a3093d6fc56", + "metadataForms.seed.description": "c22921feb0f06273", + "metadataForms.seed.label": "60e0e22a54230cab", + "objects.sys_account._actions.link_social.params.provider.options.apple": "cfdc41e15ed6699b", + "objects.sys_account._actions.link_social.params.provider.options.discord": "12f931cc062e76ae", + "objects.sys_account._actions.link_social.params.provider.options.facebook": "7eea009178f5b807", + "objects.sys_account._actions.link_social.params.provider.options.github": "2971d247eb4e6abc", + "objects.sys_account._actions.link_social.params.provider.options.gitlab": "bc3dbbf4b650e600", + "objects.sys_account._actions.link_social.params.provider.options.google": "6fadcd05bb8da367", + "objects.sys_account._actions.link_social.params.provider.options.microsoft": "17309efbdb1ec122", + "objects.sys_account.fields.access_token.help": "814695a136172228", + "objects.sys_account.fields.id_token.help": "b545f62224b21f69", + "objects.sys_account.fields.previous_password_hashes.help": "3a98e8164b0daf11", + "objects.sys_account.fields.previous_password_hashes.label": "2aa498ce717c7d77", + "objects.sys_account.fields.refresh_token.help": "f337bcd15c9b4cb3", + "objects.sys_api_key.fields.active_organization_id.help": "74718b93fdaa52cc", + "objects.sys_api_key.fields.active_organization_id.label": "51bce7fbc99fbf87", + "objects.sys_email.fields.bcc_addresses.label": "8674899b7b6d5126", + "objects.sys_email.fields.cc_addresses.label": "02946d952cf15623", + "objects.sys_email.fields.message_id.label": "14cd089a4062f4b1", + "objects.sys_email.fields.reply_to.label": "90ad8f2ca791a95a", + "objects.sys_email_template.fields.customized.help": "fc75c32a37f906ea", + "objects.sys_email_template.fields.customized.label": "6990986111e53429", + "objects.sys_email_template.fields.id.label": "00b0385c9c152888", + "objects.sys_email_template.fields.managed_by.help": "0d7397e9e0e84cfe", + "objects.sys_email_template.fields.managed_by.label": "be9070801491e6af", + "objects.sys_email_template.fields.managed_by.options.admin": "f8c9f123caf6217d", + "objects.sys_email_template.fields.managed_by.options.package": "1c8c6168730324a3", + "objects.sys_email_template.fields.managed_by.options.platform": "820d30a5082ebc6c", + "objects.sys_email_template.fields.reply_to.label": "90ad8f2ca791a95a", + "objects.sys_invitation.fields.business_unit_id.help": "b4e37b1bfcc40f34", + "objects.sys_invitation.fields.business_unit_id.label": "bf0e0fdbda66ec9b", + "objects.sys_invitation.fields.positions.help": "abfa7911007e38f1", + "objects.sys_invitation.fields.positions.label": "3428d066379ee33b", + "objects.sys_invitation.fields.role.options.delegated_admin": "13f0e7f118c839e0", + "objects.sys_member._views.mine.emptyState.message": "36cf7e3a3eebed0b", + "objects.sys_member._views.mine.emptyState.title": "df533ee25aba6de9", + "objects.sys_member._views.mine.label": "46b6227bcdb2d02e", + "objects.sys_member.fields.role.options.delegated_admin": "13f0e7f118c839e0", + "objects.sys_metadata.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_audit.fields.lock_state.options.nodelete": "248b810d57fe8bb9", + "objects.sys_metadata_audit.fields.lock_state.options.nooverlay": "49a44e4a03ff124c", + "objects.sys_metadata_history.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_history.fields.recorded_by.help": "e293bd6cf3c47060", + "objects.sys_migration.description": "2124b4abfb243e56", + "objects.sys_migration.fields.advisory.help": "ff96ea989b140e52", + "objects.sys_migration.fields.advisory.label": "b32c5f9a93d20e02", + "objects.sys_migration.fields.applied_at.help": "7414bd643f6b30cf", + "objects.sys_migration.fields.applied_at.label": "e95c287d92cd1215", + "objects.sys_migration.fields.blocking.help": "e807388f81f23dda", + "objects.sys_migration.fields.blocking.label": "fde63021cfe650a1", + "objects.sys_migration.fields.created_at.label": "1f02d416befb595b", + "objects.sys_migration.fields.details.help": "315f8003d77cf2b9", + "objects.sys_migration.fields.details.label": "974694e5c164374e", + "objects.sys_migration.fields.deviation_detail.help": "681fe28dc0c34386", + "objects.sys_migration.fields.deviation_detail.label": "5bf0f0f59a89e2fb", + "objects.sys_migration.fields.deviation_observed_at.help": "d6720808eb570acd", + "objects.sys_migration.fields.deviation_observed_at.label": "d68ffbef206322e0", + "objects.sys_migration.fields.id.help": "5cf4db327551205b", + "objects.sys_migration.fields.id.label": "349621d298d12bd4", + "objects.sys_migration.fields.last_run_at.help": "721c7ab4ce68bab9", + "objects.sys_migration.fields.last_run_at.label": "0540810f87bb4ac0", + "objects.sys_migration.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_migration.fields.verified_at.help": "ef3ed908c6407126", + "objects.sys_migration.fields.verified_at.label": "a1115e0d1677f1af", + "objects.sys_migration.label": "97fdaf1d2e0d8c2a", + "objects.sys_migration.pluralLabel": "91d61a84b5ff632b", + "objects.sys_notification._views.by_topic.label": "a0ddbe432474ed94", + "objects.sys_notification._views.recent.emptyState.message": "4a1e91a49eaf59d6", + "objects.sys_notification._views.recent.emptyState.title": "6f708a09bb4c9a5c", + "objects.sys_notification._views.recent.label": "62d27bb9d0349c99", + "objects.sys_notification.fields.dedup_key.help": "58a5e28b6a87e6d6", + "objects.sys_notification.fields.dedup_key.label": "33a740837f882c98", + "objects.sys_notification.fields.payload.help": "e1e8514cd730b06f", + "objects.sys_notification.fields.payload.label": "4488e1328e37dcb2", + "objects.sys_notification.fields.severity.help": "4925b29cf7e10346", + "objects.sys_notification.fields.severity.label": "d59e4345bdc57b59", + "objects.sys_notification.fields.severity.options.critical": "7ff0ff69c0abaf81", + "objects.sys_notification.fields.severity.options.info": "3e0c8611029f253b", + "objects.sys_notification.fields.severity.options.warning": "2673ee95caf83284", + "objects.sys_notification.fields.topic.help": "80e1790edfda49df", + "objects.sys_notification.fields.topic.label": "819afdb3853e9d80", + "objects.sys_oauth_access_token.fields.authorization_code_id.help": "84ad4d5d8c4c1cee", + "objects.sys_oauth_access_token.fields.authorization_code_id.label": "1c1d06cc01295862", + "objects.sys_oauth_access_token.fields.confirmation.help": "9d086fdc90baa01d", + "objects.sys_oauth_access_token.fields.confirmation.label": "64755493e6425b37", + "objects.sys_oauth_access_token.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_access_token.fields.requested_user_info_claims.help": "dc0cd8121cbde60f", + "objects.sys_oauth_access_token.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_access_token.fields.resources.help": "8c153517b30bd6a5", + "objects.sys_oauth_access_token.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_access_token.fields.revoked.help": "ff6a4b6dac031959", + "objects.sys_oauth_access_token.fields.revoked.label": "054f918e632528c7", + "objects.sys_oauth_application._actions.create_oauth_application.params.type.options.web": "2a7ead3da8c035c5", + "objects.sys_oauth_application._views.mine.label": "d1cb1174cd96176c", + "objects.sys_oauth_application.fields.backchannel_logout_session_required.help": "2d12d1e1f9461576", + "objects.sys_oauth_application.fields.backchannel_logout_session_required.label": "fbc6eee84fb8a1d7", + "objects.sys_oauth_application.fields.backchannel_logout_uri.help": "e7c8abeac494b145", + "objects.sys_oauth_application.fields.backchannel_logout_uri.label": "f78777a91cebd19f", + "objects.sys_oauth_application.fields.client_credentials_scopes.help": "bf69fe7f91bb78e7", + "objects.sys_oauth_application.fields.client_credentials_scopes.label": "e257098b80b760d2", + "objects.sys_oauth_application.fields.client_discovery_id.help": "6d7fff4d4e7fb705", + "objects.sys_oauth_application.fields.client_discovery_id.label": "2070f4b2f6456352", + "objects.sys_oauth_application.fields.dpop_bound_access_tokens.help": "e7257c5c6ea7043b", + "objects.sys_oauth_application.fields.dpop_bound_access_tokens.label": "3772715f1d9588af", + "objects.sys_oauth_application.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_application.fields.jwks.help": "6a6c5fe835f213b8", + "objects.sys_oauth_application.fields.jwks.label": "b2cf79f9d66c0dbc", + "objects.sys_oauth_application.fields.jwks_uri.help": "217cff7cca47a35e", + "objects.sys_oauth_application.fields.jwks_uri.label": "2a4339df734ab269", + "objects.sys_oauth_client_assertion.description": "82a3f5fec7571bd4", + "objects.sys_oauth_client_assertion.fields.expires_at.help": "90b732ea4d289003", + "objects.sys_oauth_client_assertion.fields.expires_at.label": "df0ef5fae02b2044", + "objects.sys_oauth_client_assertion.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_client_assertion.label": "964d685e79999dce", + "objects.sys_oauth_client_assertion.pluralLabel": "286f417fcbf2ab81", + "objects.sys_oauth_client_resource.description": "c63dda9f9c51165b", + "objects.sys_oauth_client_resource.fields.client_id.help": "8df79a6c07fc32b4", + "objects.sys_oauth_client_resource.fields.client_id.label": "09cb452151e7b1b6", + "objects.sys_oauth_client_resource.fields.created_at.label": "1f02d416befb595b", + "objects.sys_oauth_client_resource.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_client_resource.fields.metadata.help": "c51d0437d1b00c8b", + "objects.sys_oauth_client_resource.fields.metadata.label": "9cabc04013c80ef3", + "objects.sys_oauth_client_resource.fields.resource_id.help": "5c7d52276882432d", + "objects.sys_oauth_client_resource.fields.resource_id.label": "21668ca570b12d71", + "objects.sys_oauth_client_resource.label": "882f5ca283a58ed1", + "objects.sys_oauth_client_resource.pluralLabel": "ab6a91aa51de6ac0", + "objects.sys_oauth_consent.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_consent.fields.requested_user_info_claims.help": "6f1fc3c5e6b4031d", + "objects.sys_oauth_consent.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_consent.fields.resources.help": "f1e24c7b80ba287a", + "objects.sys_oauth_consent.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_refresh_token.fields.authorization_code_id.help": "b16f370e0a06cf43", + "objects.sys_oauth_refresh_token.fields.authorization_code_id.label": "1c1d06cc01295862", + "objects.sys_oauth_refresh_token.fields.confirmation.help": "9d086fdc90baa01d", + "objects.sys_oauth_refresh_token.fields.confirmation.label": "64755493e6425b37", + "objects.sys_oauth_refresh_token.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_refresh_token.fields.requested_user_info_claims.help": "dc0cd8121cbde60f", + "objects.sys_oauth_refresh_token.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_refresh_token.fields.resources.help": "8c153517b30bd6a5", + "objects.sys_oauth_refresh_token.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_refresh_token.fields.rotated_at.help": "704d34a1f46f4412", + "objects.sys_oauth_refresh_token.fields.rotated_at.label": "aa747daa25d440d1", + "objects.sys_oauth_refresh_token.fields.rotation_replay_expires_at.help": "7f5925e4b2d73586", + "objects.sys_oauth_refresh_token.fields.rotation_replay_expires_at.label": "f6a0bbe6ef73bded", + "objects.sys_oauth_refresh_token.fields.rotation_replay_response.help": "ea6d3f140afbbca7", + "objects.sys_oauth_refresh_token.fields.rotation_replay_response.label": "ddf6cf0ccba55c30", + "objects.sys_oauth_resource.description": "3c7f0cfae5b1a887", + "objects.sys_oauth_resource.fields.access_token_ttl.help": "293dc402a9669032", + "objects.sys_oauth_resource.fields.access_token_ttl.label": "afebf5750ed4d9ee", + "objects.sys_oauth_resource.fields.allowed_scopes.help": "af9195bf5efc4b27", + "objects.sys_oauth_resource.fields.allowed_scopes.label": "1a3202eecb314841", + "objects.sys_oauth_resource.fields.created_at.label": "1f02d416befb595b", + "objects.sys_oauth_resource.fields.custom_claims.help": "0afe2b2a099c7813", + "objects.sys_oauth_resource.fields.custom_claims.label": "c6182d11456325c4", + "objects.sys_oauth_resource.fields.disabled.label": "639459b8a7bbec04", + "objects.sys_oauth_resource.fields.dpop_bound_access_tokens_required.help": "d82b668d32d9d48f", + "objects.sys_oauth_resource.fields.dpop_bound_access_tokens_required.label": "715d383f9fc4fcfb", + "objects.sys_oauth_resource.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_resource.fields.identifier.help": "b69d2662e0438576", + "objects.sys_oauth_resource.fields.identifier.label": "85b28b4a09f37cd0", + "objects.sys_oauth_resource.fields.metadata.help": "896f351a40678f21", + "objects.sys_oauth_resource.fields.metadata.label": "9cabc04013c80ef3", + "objects.sys_oauth_resource.fields.name.label": "5274417ff1ccaf01", + "objects.sys_oauth_resource.fields.policy_version.help": "18b93d988a1d6107", + "objects.sys_oauth_resource.fields.policy_version.label": "55fdeadcaba6cd2a", + "objects.sys_oauth_resource.fields.refresh_token_ttl.help": "9bbb700667d7cee7", + "objects.sys_oauth_resource.fields.refresh_token_ttl.label": "e4b35b5501ac588f", + "objects.sys_oauth_resource.fields.signing_algorithm.help": "3f8231cc5435a673", + "objects.sys_oauth_resource.fields.signing_algorithm.label": "c26616cf69746887", + "objects.sys_oauth_resource.fields.signing_key_id.help": "8504e8cddac3b67c", + "objects.sys_oauth_resource.fields.signing_key_id.label": "50da11ac4977fa58", + "objects.sys_oauth_resource.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_oauth_resource.label": "5077fa14e8d7418e", + "objects.sys_oauth_resource.pluralLabel": "1c6ad6e11287866f", + "objects.sys_organization.fields.parent_organization_id.help": "2ad4ebe3782901b9", + "objects.sys_organization.fields.parent_organization_id.label": "24129608643b897f", + "objects.sys_organization.fields.sort_order.help": "7355b98a96f85dfc", + "objects.sys_organization.fields.sort_order.label": "5f6b33fdc89e9d9f", + "objects.sys_scim_provider.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_provider.fields.provider_key.help": "6eba9e41bfb954ab", + "objects.sys_scim_provider.fields.provider_key.label": "fbc96a8b3ed4709d", + "objects.sys_secret._views.all.label": "20d032bd60c81773", + "objects.sys_secret.fields.id.label": "00b0385c9c152888", + "objects.sys_session.fields.last_activity_at.help": "f7851e9373505e73", + "objects.sys_session.fields.last_activity_at.label": "43bd2f1b231bc12b", + "objects.sys_session.fields.revoke_reason.help": "ff2c9a1aa9be356d", + "objects.sys_session.fields.revoke_reason.label": "13b0146153a2ecf8", + "objects.sys_session.fields.revoked_at.help": "62d1b62cde5ce487", + "objects.sys_session.fields.revoked_at.label": "054f918e632528c7", + "objects.sys_setting_audit._views.recent.label": "62d27bb9d0349c99", + "objects.sys_setting_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_setting_audit.fields.source.options.api": "4baeeff968e192d1", + "objects.sys_setting_audit.fields.source.options.ui": "95caad102a5c97ba", + "objects.sys_sso_provider._actions.register_saml_provider.params.entryPoint.label": "65c2606e2e467cf7", + "objects.sys_sso_provider._actions.register_saml_provider.params.identifierFormat.placeholder": "5945ff9a6b644855", + "objects.sys_sso_provider._actions.register_sso_provider.params.mapEmail.placeholder": "66309c7adf5c9436", + "objects.sys_sso_provider._actions.register_sso_provider.params.mapName.placeholder": "a484c34aaf624bd6", + "objects.sys_sso_provider._actions.register_sso_provider.params.scopes.placeholder": "58ac90cde28c764d", + "objects.sys_sso_provider.fields.id.label": "00b0385c9c152888", + "objects.sys_team.fields.member_count.help": "498d7d7119ffff12", + "objects.sys_team.fields.member_count.label": "7bb48f7c92af14e0", + "objects.sys_team_member.fields.membership_key.help": "08a5017306c3ac4e", + "objects.sys_team_member.fields.membership_key.label": "8fcb9f17bcc8cae9", + "objects.sys_two_factor.fields.failed_verification_count.help": "8cea41d6de7f4d5a", + "objects.sys_two_factor.fields.failed_verification_count.label": "e0438d2e39cb0500", + "objects.sys_two_factor.fields.locked_until.help": "059956062c752f2c", + "objects.sys_two_factor.fields.locked_until.label": "c7f013ad9b7208d2", + "objects.sys_two_factor.fields.verified.help": "0d351ba4c31608c2", + "objects.sys_two_factor.fields.verified.label": "866e3a245ef04734", + "objects.sys_user.fields.manager_id.help": "a53dec9ccd9d131f", + "objects.sys_user.fields.primary_business_unit_id.help": "e01690f4a9b83956", + "objects.sys_view_definition.fields.id.label": "00b0385c9c152888", +}; diff --git a/packages/platform-objects/src/apps/translations/setup.translation.ts b/packages/platform-objects/src/apps/translations/setup.translation.ts index c290574aae..e6ca4d7e4c 100644 --- a/packages/platform-objects/src/apps/translations/setup.translation.ts +++ b/packages/platform-objects/src/apps/translations/setup.translation.ts @@ -8,6 +8,9 @@ import { esES } from './es-ES.js'; import { zhCNSourceHashes } from './zh-CN.source-hashes.js'; import { jaJPSourceHashes } from './ja-JP.source-hashes.js'; import { esESSourceHashes } from './es-ES.source-hashes.js'; +import { zhCNGeneratedSourceHashes } from './zh-CN.source-hashes.generated.js'; +import { jaJPGeneratedSourceHashes } from './ja-JP.source-hashes.generated.js'; +import { esESGeneratedSourceHashes } from './es-ES.source-hashes.generated.js'; import { withSourceFallback } from './source-hash.js'; /** @@ -43,12 +46,26 @@ import { withSourceFallback } from './source-hash.js'; * * A leaf with NO recorded hash is legacy-trusted and served verbatim. * + * ## The generated half joined this seam in #11671 + * + * The fourth argument is the GENERATED provenance table + * (`.source-hashes.generated.ts`, written by `os i18n extract + * --source-hashes`). `zhCN` and friends carry `objects` as well as the + * hand-authored sections, so this one call now covers both halves — the third + * argument judging `apps`/`dashboards`/`pages`, the fourth judging `objects`. + * + * The module note in `source-hash.ts` records why the generated half was + * excluded until now and why that reason was wrong. On the day this landed no + * generated leaf was stale (every record equals the current source's digest by + * construction), so this changed what is SERVED for exactly zero leaves — it + * changes what happens the next time a source string moves underneath one. + * * `en` is not passed through: it is the source, not a translation of it, so * there is nothing for it to be stale against. */ export const SetupAppTranslations: TranslationBundle = { en, - 'zh-CN': withSourceFallback(zhCN, en, zhCNSourceHashes), - 'ja-JP': withSourceFallback(jaJP, en, jaJPSourceHashes), - 'es-ES': withSourceFallback(esES, en, esESSourceHashes), + 'zh-CN': withSourceFallback(zhCN, en, zhCNSourceHashes, zhCNGeneratedSourceHashes), + 'ja-JP': withSourceFallback(jaJP, en, jaJPSourceHashes, jaJPGeneratedSourceHashes), + 'es-ES': withSourceFallback(esES, en, esESSourceHashes, esESGeneratedSourceHashes), }; diff --git a/packages/platform-objects/src/apps/translations/source-hash.test.ts b/packages/platform-objects/src/apps/translations/source-hash.test.ts index a04e163ae3..49ce74a072 100644 --- a/packages/platform-objects/src/apps/translations/source-hash.test.ts +++ b/packages/platform-objects/src/apps/translations/source-hash.test.ts @@ -28,12 +28,17 @@ import { jaJPSourceHashes } from './ja-JP.source-hashes.js'; import { esESSourceHashes } from './es-ES.source-hashes.js'; import { SetupAppTranslations } from './setup.translation.js'; import { + collectFilledFromHashes, collectSourceHashes, collectSourceLeaves, + findStaleFills, findStaleLeaves, hashSource, withSourceFallback, } from './source-hash.js'; +import { zhCNGeneratedSourceHashes } from './zh-CN.source-hashes.generated.js'; +import { jaJPGeneratedSourceHashes } from './ja-JP.source-hashes.generated.js'; +import { esESGeneratedSourceHashes } from './es-ES.source-hashes.generated.js'; // A source bundle and a translation of it, at one nav leaf per app so the // per-locale independence claim has something to be independent about. @@ -225,3 +230,178 @@ describe('the shipped bundles', () => { // PURPOSE), and the second goes red on any un-re-translated source edit. // Staleness degrades what is served; it does not stop a build. }); + +// ─────────────────────────────────────────────────────────────────────────── +// The GENERATED half (#11671, maintainer ruling #12069 Option A) +// +// Same mechanism, different predicate: these leaves got their text by being +// COPIED from the source, so the bytes are evidence and the record only says +// WHICH revision they are a copy of. The cases below are the card's own recipe +// — extract with `--fill=default`, revise the source string, extract again — +// plus the two false-positive classes the extra conjunct exists to close. +// ─────────────────────────────────────────────────────────────────────────── + +const HELP = 'objects.sys_activity.fields.type.help'; +const SRC_V1 = 'The kind of activity. Readonly fields are skipped by validateRecord.'; +const SRC_V2 = 'The kind of activity.'; + +const genSource = (help: string): TranslationData => ({ + objects: { + sys_activity: { + label: 'Activity', + fields: { type: { label: 'Type', help } }, + }, + }, +}); + +const genTranslated = (help: string, label = '类型'): TranslationData => ({ + objects: { + sys_activity: { + label: '活动', + fields: { type: { label, help } }, + }, + }, +}); + +describe('a generated leaf filled from the source, then stranded when the source moved', () => { + // What `os i18n extract --fill=default --source-hashes` leaves behind on run 1. + const recorded = collectFilledFromHashes(genTranslated(SRC_V1), genSource(SRC_V1), undefined); + + it('records the fill, and records nothing for the leaves that were translated', () => { + expect(recorded[HELP]).toBe(hashSource(SRC_V1)); + // `.label` holds 类型 / 活动 — translations, not copies. No claim is made. + expect(recorded['objects.sys_activity.fields.type.label']).toBeUndefined(); + expect(recorded['objects.sys_activity.label']).toBeUndefined(); + }); + + it('is NOT stale while the source it was filled from is unchanged', () => { + expect(findStaleFills(genTranslated(SRC_V1), genSource(SRC_V1), recorded)).toEqual([]); + }); + + it('IS stale once the source is revised underneath it — the card\'s measured instance', () => { + const stale = findStaleFills(genTranslated(SRC_V1), genSource(SRC_V2), recorded); + expect(stale.map((s) => s.path)).toEqual([HELP]); + expect(stale[0]).toMatchObject({ recorded: hashSource(SRC_V1), current: hashSource(SRC_V2) }); + }); + + it('serves the CURRENT source string in its place, leaving the real translations alone', () => { + const servedBundle = withSourceFallback(genTranslated(SRC_V1), genSource(SRC_V2), undefined, recorded); + expect(served(servedBundle, HELP)).toBe(SRC_V2); + expect(served(servedBundle, 'objects.sys_activity.label')).toBe('活动'); + }); + + it('carries the record across a re-extract, which is the whole memory it has', () => { + // Run 2, after the source moved: the leaf is no longer a copy of the CURRENT + // source, so only the carried-forward record can keep the drift visible. + const next = collectFilledFromHashes(genTranslated(SRC_V1), genSource(SRC_V2), recorded); + expect(next[HELP]).toBe(hashSource(SRC_V1)); + expect(findStaleFills(genTranslated(SRC_V1), genSource(SRC_V2), next).map((s) => s.path)).toEqual([HELP]); + }); + + it('drops the record — and the report — the moment the leaf is re-translated', () => { + const fixed = genTranslated('活动的种类。'); + expect(findStaleFills(fixed, genSource(SRC_V2), recorded)).toEqual([]); + const next = collectFilledFromHashes(fixed, genSource(SRC_V2), recorded); + expect(next[HELP]).toBeUndefined(); + }); +}); + +describe('what is NOT drift', () => { + it('a leaf equal to the CURRENT source — an untranslated key, a proper noun, a symbol', () => { + const recorded = collectFilledFromHashes(genTranslated(SRC_V2, 'Type'), genSource(SRC_V2), undefined); + // Both the copied help AND the deliberately-English label are recorded... + expect(recorded[HELP]).toBe(hashSource(SRC_V2)); + expect(recorded['objects.sys_activity.fields.type.label']).toBe(hashSource('Type')); + // ...and neither is stale, because the source has not moved. + expect(findStaleFills(genTranslated(SRC_V2, 'Type'), genSource(SRC_V2), recorded)).toEqual([]); + }); + + it('a generated leaf with no record at all — legacy-trusted, per the ruling', () => { + expect(findStaleFills(genTranslated(SRC_V1), genSource(SRC_V2), {})).toEqual([]); + expect(findStaleFills(genTranslated(SRC_V1), genSource(SRC_V2), undefined)).toEqual([]); + }); + + it('a hand-authored leaf — the generated predicate never reaches those sections', () => { + const handAuthored = translated(); + expect(findStaleFills(handAuthored, source(EDITED), collectSourceHashes(source(ORIGINAL)))).toEqual([]); + }); +}); + +describe('a leaf stranded in ONE locale alone — the reason the ruling chose Option A', () => { + // The gate that shipped first for this class (`check:i18n-stale-fill`) infers + // provenance from TWO locales holding byte-identical text: two different + // target languages do not independently produce the same prose, so agreement + // between them is evidence that neither translated it. That inference is + // sound, and it is structurally blind to a leaf stranded in exactly ONE + // locale — there is no second witness to agree with. A RECORDED hash needs no + // witness, which is the whole of what Option A buys over the status quo. + // + // Not a hypothetical population: on the tree this landed against, 18 generated + // leaves are recorded in exactly one locale (zh-CN 2, ja-JP 3, es-ES 13) — + // English-looking terms one locale left as a fill while the others translated + // them ('Variables (JSON)', 'Reply-To', 'Checksum'). Every one of those is a + // leaf only this mechanism can ever speak about. + const LABEL = 'objects.sys_activity.fields.type.label'; + + // One locale left the source label in English (a fill); another translated it. + const filledLocale = genTranslated(SRC_V1, 'Type'); + const translatedLocale = genTranslated(SRC_V1, '\u7c7b\u578b'); + + const filledRecords = collectFilledFromHashes(filledLocale, genSource(SRC_V1), undefined); + const translatedRecords = collectFilledFromHashes(translatedLocale, genSource(SRC_V1), undefined); + + // The source label alone moves; the help string is deliberately left alone so + // the only thing either locale can be stale about is the single-locale leaf. + const movedSource: TranslationData = { + objects: { + sys_activity: { + label: 'Activity', + fields: { type: { label: 'Kind', help: SRC_V1 } }, + }, + }, + }; + + it('records the filled label in the locale that left it, and in no other', () => { + expect(filledRecords[LABEL]).toBe(hashSource('Type')); + expect(translatedRecords[LABEL]).toBeUndefined(); + }); + + it('reports it stale in that locale alone once the source label moves', () => { + expect(findStaleFills(filledLocale, movedSource, filledRecords).map((s) => s.path)).toEqual([LABEL]); + expect(findStaleFills(translatedLocale, movedSource, translatedRecords)).toEqual([]); + }); + + it('serves the current source label there, and leaves the real translation untouched', () => { + const servedFilled = withSourceFallback(filledLocale, movedSource, undefined, filledRecords); + expect(served(servedFilled, LABEL)).toBe('Kind'); + const servedTranslated = withSourceFallback(translatedLocale, movedSource, undefined, translatedRecords); + expect(served(servedTranslated, LABEL)).toBe('\u7c7b\u578b'); + }); +}); + +describe('the committed provenance companions', () => { + const tables = { + 'zh-CN': zhCNGeneratedSourceHashes, + 'ja-JP': jaJPGeneratedSourceHashes, + 'es-ES': esESGeneratedSourceHashes, + } as const; + + // ⚠️ Deliberately NOT asserted here: "no shipped leaf is stale". That claim + // goes red the moment somebody edits one source string without re-translating + // three locales, which is Option C — the option the ruling rejected. Staleness + // is a SERVING rule, not a gate. What is asserted is only what a content edit + // cannot trip. + for (const [locale, table] of Object.entries(tables)) { + it(`${locale} — every recorded digest is well-formed`, () => { + const entries = Object.entries(table); + expect(entries.length).toBeGreaterThan(0); + for (const [, digest] of entries) expect(digest).toMatch(/^[0-9a-f]{16}$/); + }); + + it(`${locale} — every recorded path names a generated leaf, never a hand-authored one`, () => { + for (const key of Object.keys(table)) { + expect(key.startsWith('objects.') || key.startsWith('metadataForms.')).toBe(true); + } + }); + } +}); diff --git a/packages/platform-objects/src/apps/translations/source-hash.ts b/packages/platform-objects/src/apps/translations/source-hash.ts index e41dc894a1..630ea615d1 100644 --- a/packages/platform-objects/src/apps/translations/source-hash.ts +++ b/packages/platform-objects/src/apps/translations/source-hash.ts @@ -78,24 +78,101 @@ * So hashing `en` transitively hashes the declared source, and this module needs * to import nothing but the bundle sitting next to it. * - * ## Scope: the hand-authored sections only + * ## Scope: BOTH halves — and the correction that put the generated half here * - * `objects` and `metadataForms` are GENERATED (`*.generated.ts`) and are - * deliberately out of {@link HAND_AUTHORED_SECTIONS}. This hole cannot occur - * there: `os i18n extract` rewrites the `en` bundle from the source on every - * run and does not merge the default locale (#8543), so a source edit either - * lands in the generated bundle or fails `check:i18n` as drift. + * This note used to end with a claim that is FALSE, and #11671 is its + * counterexample. It read: + * + * > `objects` and `metadataForms` are GENERATED (`*.generated.ts`) and are + * > deliberately out of `HAND_AUTHORED_SECTIONS`. **This hole cannot occur + * > there**: `os i18n extract` rewrites the `en` bundle from the source on + * > every run and does not merge the default locale (#8543), so a source edit + * > either lands in the generated bundle or fails `check:i18n` as drift. + * + * Every clause of that is true except the conclusion. Rewriting `en` catches + * drift **in `en`**. The TRANSLATED locales keep merge semantics + * (`i18n-extract.ts`: a non-empty existing value in a non-default locale wins), + * so the ordinary sequence — extract with `--fill=default`, revise the source + * string, extract again — rewrites `en` and STRANDS the previous source text in + * every other locale. The bundle is still in sync by key, so `check:i18n` + * reports OK; the leaf is still present, so `check:i18n-coverage` counts it + * translated. Measured on #11659 at `bbe0b17`: three locales serving a 602-char + * superseded draft of a 411-char help string under 31 green checks. + * + * A written-down impossibility is what stops the next reader from looking, + * which is why correcting it was made part of the ruling rather than left as a + * comment cleanup. + * + * Maintainer ruling on #12069 (2026-08-25, Option A): extend THIS module to the + * generated bundles. So {@link GENERATED_SECTIONS} joins + * {@link HAND_AUTHORED_SECTIONS} here — one mechanism, one hash function, one + * recorded table shape. + * + * ## The two halves need two PREDICATES, and that is not two mechanisms + * + * What a recorded hash MEANS is the same in both halves — "the source revision + * this leaf was last reconciled against". What the leaf's VALUE is differs, and + * the predicate has to follow it: + * + * - **hand-authored** — the value is a TRANSLATION, whose bytes say nothing + * about the source's. Stale is decided on the record alone: + * `recorded !== hash(currentSource)`. {@link findStaleLeaves}, unchanged. + * - **generated** — the value got there by being COPIED from the source + * (`--fill=default`). So the bytes themselves are evidence, and the record + * only certifies WHICH source revision they are a copy of. Stale is + * `hash(value) === recorded && hash(currentSource) !== recorded`. + * {@link findStaleFills}. + * + * The extra conjunct in the generated predicate is not caution, it is what + * makes the mechanism self-healing where the hand-authored half cannot be. The + * generated hash tables are themselves generated (`.source-hashes. + * generated.ts`), so a translator CANNOT be asked to update a digest by hand + * the way `.source-hashes.ts`'s header asks. Without the conjunct, + * re-translating a filled leaf after its source moved would leave the stale + * record standing and the gate would report the fresh translation as stale + * forever — a false positive on precisely the action the mechanism exists to + * provoke. With it, editing the value clears the flag by itself: the value is + * no longer a copy of the recorded revision, so no claim is being made about it + * any more. + * + * ## Why the generated tables can be BACKFILLED with no history + * + * The generated predicate only ever fires on a leaf whose value is still a byte + * copy of the recorded revision. So the only records worth writing are for + * leaves that ARE currently source copies, and those are identifiable from the + * committed tree alone: `value === currentSource ⇒ record hash(value)`. A leaf + * that differs from the current source is left with NO record — legacy-trusted, + * exactly as the ruling's property 1 requires — because nothing in the tree says + * which revision it was made from. + * + * Measured on this tree when the mechanism landed: 9030 translated leaves across + * the nine bundle sets, 1543 of them byte-equal to `en` (records written) and + * 7487 differing (legacy-trusted). Day-one stale count is **0 by construction** — + * every record written equals the hash of the current source, so the second + * conjunct is false for all of them. This mechanism cannot arrive red. */ import type { TranslationData } from '@objectstack/spec/system'; /** - * The `TranslationData` sections whose leaves carry staleness checking — the - * hand-authored half. See the module note for why the generated sections - * (`objects`, `metadataForms`) are excluded rather than merely unlisted. + * The `TranslationData` sections written by hand in `.ts`, judged by + * {@link findStaleLeaves}. Their recorded digests live in the hand-maintained + * `.source-hashes.ts`. */ export const HAND_AUTHORED_SECTIONS = ['apps', 'dashboards', 'pages'] as const; +/** + * The `TranslationData` sections written by `os i18n extract` into + * `.objects.generated.ts` / `.metadata-forms.generated.ts`, + * judged by {@link findStaleFills}. Their recorded digests live in the + * generated `.source-hashes.generated.ts`. + * + * These were excluded from this module until #11671 measured that the hole it + * closes occurs here too — see the module note for the claim that was wrong and + * why it was wrong. + */ +export const GENERATED_SECTIONS = ['objects', 'metadataForms'] as const; + /** A recorded map of dotted leaf path → the source hash translated from. */ export type SourceHashes = Readonly>; @@ -152,6 +229,27 @@ function isPlainObject(value: unknown): value is Record { * declared-but-unwalked failure this module exists to close. */ export function collectSourceLeaves(data: TranslationData | undefined): Map { + return collectLeavesOf(data, HAND_AUTHORED_SECTIONS); +} + +/** + * The same walk over {@link GENERATED_SECTIONS} — every string leaf of the + * `objects` / `metadataForms` sub-trees, keyed by dotted path + * (`objects.sys_user.fields.email.help`). + * + * A separate entry point rather than a parameter on {@link collectSourceLeaves} + * because the two populations are judged by two different predicates, and a + * single call that could return either is one `??` away from applying the wrong + * one to the wrong half. + */ +export function collectGeneratedLeaves(data: TranslationData | undefined): Map { + return collectLeavesOf(data, GENERATED_SECTIONS); +} + +function collectLeavesOf( + data: TranslationData | undefined, + sections: readonly string[], +): Map { const leaves = new Map(); if (!data) return leaves; @@ -166,7 +264,7 @@ export function collectSourceLeaves(data: TranslationData | undefined): Map)[section], section); } return leaves; @@ -190,6 +288,48 @@ export function collectSourceHashes(source: TranslationData | undefined): Record return hashes; } +/** + * The record `os i18n extract` writes for one translated locale's generated + * sections — the pure rule behind `.source-hashes.generated.ts`. + * + * One entry per leaf that IS currently a byte copy of some source revision, and + * nothing else: + * + * - `value === currentSource` — the leaf is a copy of the CURRENT source + * (a fresh `--fill=default`, or a term deliberately left in English). Record + * `hash(value)`, which is also `hash(currentSource)`, so it is not stale. + * - `previous[path] === hash(value)` — the leaf is still the copy the last run + * recorded, whatever the source has done since. Carry the record forward; + * that is the whole memory this mechanism has, and dropping it is how the + * drift becomes undetectable again. + * - otherwise — record NOTHING. Either the leaf was translated (its bytes are + * not a copy of anything we recorded) or it predates the mechanism. Both are + * legacy-trusted, per the ruling's property 1. + * + * Note the third bullet is also the self-healing step: a translator who edits a + * stale filled leaf makes `hash(value)` stop matching the record, so the record + * is dropped on the next extract and the leaf goes back to legacy-trusted + * instead of being reported stale forever. + * + * Pure and total: same inputs, same table. That is what lets `os i18n extract + * --check` compare the committed companion byte-for-byte. + */ +export function collectFilledFromHashes( + translated: TranslationData | undefined, + source: TranslationData | undefined, + previous: SourceHashes | undefined, +): Record { + const sourceLeaves = collectGeneratedLeaves(source); + const hashes: Record = {}; + for (const [path, value] of collectGeneratedLeaves(translated)) { + const digest = hashSource(value); + const isCurrentCopy = sourceLeaves.get(path) === value; + const wasRecordedCopy = previous?.[path] === digest; + if (isCurrentCopy || wasRecordedCopy) hashes[path] = digest; + } + return hashes; +} + /** A leaf whose recorded hash disagrees with the current source. */ export interface StaleLeaf { /** Dotted leaf path, e.g. `dashboards.system_overview.widgets.w1.title`. */ @@ -236,6 +376,59 @@ export function findStaleLeaves( return stale; } +/** A generated leaf still holding a byte copy of a source revision that has moved on. */ +export interface StaleFill { + /** Dotted leaf path, e.g. `objects.sys_user.fields.email.help`. */ + path: string; + /** The digest of the source revision this leaf is a copy of. */ + recorded: string; + /** The digest of the source string as it reads now. */ + current: string; +} + +/** + * The stale FILLS of one translated bundle's generated sections, in walk order. + * + * Three conjuncts, and each one is load-bearing: + * + * 1. a digest is recorded for the path — no record is legacy-trusted, never + * stale (the ruling's property 1); + * 2. `hash(value) === recorded` — the leaf is STILL the copy that was + * recorded. A leaf someone has since re-translated fails here and is not + * reported, which is what keeps a real translation from being called stale + * merely because it once started life as a fill; + * 3. `hash(currentSource) !== recorded` — the source has actually moved. A + * leaf equal to the CURRENT source is not drift: an untranslated key, a + * proper noun, a symbol or a term left in English on purpose all live here, + * and reporting them would be restating the gap `check:i18n-coverage` + * already owns. + * + * A path whose source string no longer exists is not reported — that is a + * REMOVED key, which `check:i18n`'s key-set comparison owns. + */ +export function findStaleFills( + translated: TranslationData | undefined, + source: TranslationData | undefined, + recorded: SourceHashes | undefined, +): StaleFill[] { + if (!translated || !recorded) return []; + const sourceLeaves = collectGeneratedLeaves(source); + const stale: StaleFill[] = []; + + for (const [path, value] of collectGeneratedLeaves(translated)) { + const recordedHash = recorded[path]; + if (recordedHash === undefined) continue; // legacy-trusted + if (hashSource(value) !== recordedHash) continue; // re-translated since — not our claim + const sourceValue = sourceLeaves.get(path); + if (sourceValue === undefined) continue; // removed key — not this rule's + const currentHash = hashSource(sourceValue); + if (currentHash !== recordedHash) { + stale.push({ path, recorded: recordedHash, current: currentHash }); + } + } + return stale; +} + function setDeep(target: Record, path: string, value: string): void { const segments = path.split('.'); let node = target; @@ -257,6 +450,12 @@ function setDeep(target: Record, path: string, value: string): * other leaf — including every leaf with no recorded hash — is carried through * untouched. * + * `recorded` judges the hand-authored sections ({@link findStaleLeaves}); the + * optional `filledFrom` judges the generated ones ({@link findStaleFills}). + * Omitting `filledFrom` leaves the generated sections entirely legacy-trusted, + * which is what every caller did before #11671 and is still the honest default + * for a bundle with no committed `.source-hashes.generated.ts`. + * * The input is never mutated. Returns the same reference when nothing is stale, * so the common case allocates nothing. */ @@ -264,14 +463,21 @@ export function withSourceFallback( translated: TranslationData, source: TranslationData | undefined, recorded: SourceHashes | undefined, + filledFrom?: SourceHashes, ): TranslationData { const stale = findStaleLeaves(translated, source, recorded); - if (stale.length === 0) return translated; + const staleFills = findStaleFills(translated, source, filledFrom); + if (stale.length === 0 && staleFills.length === 0) return translated; - const sourceLeaves = collectSourceLeaves(source); + const handAuthored = collectSourceLeaves(source); + const generated = collectGeneratedLeaves(source); const next: Record = { ...translated }; for (const { path } of stale) { - const sourceValue = sourceLeaves.get(path); + const sourceValue = handAuthored.get(path); + if (sourceValue !== undefined) setDeep(next, path, sourceValue); + } + for (const { path } of staleFills) { + const sourceValue = generated.get(path); if (sourceValue !== undefined) setDeep(next, path, sourceValue); } return next as TranslationData; diff --git a/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.generated.ts b/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.generated.ts new file mode 100644 index 0000000000..750fb397ba --- /dev/null +++ b/packages/platform-objects/src/apps/translations/zh-CN.source-hashes.generated.ts @@ -0,0 +1,357 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-CN'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (#11671, maintainer ruling #12069 Option A, extending #8765 Option B). + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhCNGeneratedSourceHashes: Readonly> = { + "metadataForms.action.fields.ai.helpText": "e8c9a5009eb96ef6", + "metadataForms.action.fields.ai.label": "17ad12a926c9f22d", + "metadataForms.action.fields.body.capabilities.helpText": "2db3f83342bd3fa1", + "metadataForms.action.fields.body.capabilities.label": "0b59005e10add41f", + "metadataForms.action.fields.body.language.helpText": "b0ad78a396675964", + "metadataForms.action.fields.body.language.label": "012664c233fc15b0", + "metadataForms.action.fields.body.memoryMb.helpText": "f53c826ce3c94ad9", + "metadataForms.action.fields.body.memoryMb.label": "a1e1612eff7f82ea", + "metadataForms.action.fields.body.source.helpText": "3a066e75948ec72c", + "metadataForms.action.fields.body.source.label": "8fe786e8e29c8cec", + "metadataForms.action.fields.body.timeoutMs.helpText": "47042e57f537d281", + "metadataForms.action.fields.body.timeoutMs.label": "2b887c62238d3532", + "metadataForms.api.description": "62421e8039e013f5", + "metadataForms.api.label": "589af3fe4aab4af5", + "metadataForms.book.description": "45e72c8c74617530", + "metadataForms.book.label": "b48c719759fca087", + "metadataForms.capability.description": "1130a8a487c35f3d", + "metadataForms.capability.label": "cb58b72c5cc77632", + "metadataForms.dataset.description": "ce0d0a4d536bbc1e", + "metadataForms.dataset.fields.description.helpText": "f82c5070e1f78508", + "metadataForms.dataset.fields.description.label": "779a4342c8eb6707", + "metadataForms.dataset.fields.dimensions.helpText": "a4df7393a6bd88c9", + "metadataForms.dataset.fields.dimensions.label": "d75e6dd9d87e93b4", + "metadataForms.dataset.fields.filter.helpText": "8d996f9121810664", + "metadataForms.dataset.fields.filter.label": "802895ec427fc6af", + "metadataForms.dataset.fields.include.helpText": "e8f9933ae4f99e77", + "metadataForms.dataset.fields.include.label": "45b27393fb9f3b2a", + "metadataForms.dataset.fields.label.helpText": "f0cbad8e334db1d3", + "metadataForms.dataset.fields.label.label": "c680364257b11569", + "metadataForms.dataset.fields.measures.label": "395dac46abcf1943", + "metadataForms.dataset.fields.name.helpText": "8c3cae6dba12b42a", + "metadataForms.dataset.fields.name.label": "5274417ff1ccaf01", + "metadataForms.dataset.fields.object.helpText": "09a85579054a7197", + "metadataForms.dataset.fields.object.label": "d0c49032d95ebb36", + "metadataForms.dataset.label": "19647dcb84ce82d3", + "metadataForms.dataset.sections.basics.description": "844b82a322f8edd6", + "metadataForms.dataset.sections.basics.label": "55562f900da68c5d", + "metadataForms.dataset.sections.dimensions.description": "da19c8faa0cb1023", + "metadataForms.dataset.sections.dimensions.label": "d75e6dd9d87e93b4", + "metadataForms.dataset.sections.measures.description": "8186d1c0fe6a86f4", + "metadataForms.dataset.sections.measures.label": "395dac46abcf1943", + "metadataForms.dataset.sections.source.description": "ee9be5509026b678", + "metadataForms.dataset.sections.source.label": "8fe786e8e29c8cec", + "metadataForms.doc.description": "f233af60238b0879", + "metadataForms.doc.label": "3f35cf5088b999ad", + "metadataForms.field.fields.placeholder.helpText": "07cecac0844860be", + "metadataForms.field.fields.placeholder.label": "44d62b55b63fe718", + "metadataForms.field.fields.summaryOperations.field.helpText": "b6897e7341b31c09", + "metadataForms.field.fields.summaryOperations.field.label": "e21c314685cd95ca", + "metadataForms.field.fields.summaryOperations.filter.helpText": "7a46d2abde5a25c4", + "metadataForms.field.fields.summaryOperations.filter.label": "802895ec427fc6af", + "metadataForms.field.fields.summaryOperations.function.helpText": "b1022db50ca584b3", + "metadataForms.field.fields.summaryOperations.function.label": "9ae3f998b780a051", + "metadataForms.field.fields.summaryOperations.object.helpText": "d937a7f5e5d922cf", + "metadataForms.field.fields.summaryOperations.object.label": "d0c49032d95ebb36", + "metadataForms.field.fields.summaryOperations.relationshipField.helpText": "5d8a9925c9a1b45a", + "metadataForms.field.fields.summaryOperations.relationshipField.label": "d8e119c564a5453a", + "metadataForms.hook.fields.body.memoryMb.helpText": "f53c826ce3c94ad9", + "metadataForms.hook.fields.body.memoryMb.label": "a1e1612eff7f82ea", + "metadataForms.hook.fields.retryPolicy.backoffMs.helpText": "42137a83459ed6b7", + "metadataForms.hook.fields.retryPolicy.backoffMs.label": "cad56dd875f84c22", + "metadataForms.hook.fields.retryPolicy.helpText": "de59f5fbb8ad7fda", + "metadataForms.hook.fields.retryPolicy.label": "7ec5dad15f687f05", + "metadataForms.hook.fields.retryPolicy.maxRetries.helpText": "f7e133cfc5e64240", + "metadataForms.hook.fields.retryPolicy.maxRetries.label": "422d764e2265642c", + "metadataForms.hook.fields.timeout.helpText": "31e114f26389aec7", + "metadataForms.hook.fields.timeout.label": "4e36a2d29b385090", + "metadataForms.mapping.description": "654a322ed6e264bb", + "metadataForms.mapping.label": "9baba989f46cd1e8", + "metadataForms.object.fields.enable.activities.label": "1091422f7f3133d3", + "metadataForms.object.fields.enable.apiEnabled.label": "34396a2f6cfda5d5", + "metadataForms.object.fields.enable.clone.label": "48cbbe43b82f32c2", + "metadataForms.object.fields.enable.feeds.label": "669c64671f632139", + "metadataForms.object.fields.enable.files.label": "411069282de23f9a", + "metadataForms.object.fields.enable.helpText": "554c6b16fa1e3a5a", + "metadataForms.object.fields.enable.label": "fdecf828d4e86eed", + "metadataForms.object.fields.enable.searchable.label": "d8f50099e7217830", + "metadataForms.object.fields.enable.trackHistory.label": "7bc28212817b2c43", + "metadataForms.object.fields.fields.autonumberFormat.helpText": "14efe56f7cf8812e", + "metadataForms.object.fields.fields.autonumberFormat.label": "ced754e3dde1e78f", + "metadataForms.object.fields.fields.deleteBehavior.helpText": "6d94538a80df113e", + "metadataForms.object.fields.fields.deleteBehavior.label": "01c3c66572ce674b", + "metadataForms.object.fields.fields.expression.helpText": "6c559ad697dbaa85", + "metadataForms.object.fields.fields.expression.label": "cd1b52ad1588075e", + "metadataForms.object.fields.fields.lookupFilters.helpText": "ce8004df6ef768f1", + "metadataForms.object.fields.fields.lookupFilters.label": "c2860cdcfe74ad9a", + "metadataForms.object.fields.fields.placeholder.helpText": "497ea6299e075744", + "metadataForms.object.fields.fields.placeholder.label": "44d62b55b63fe718", + "metadataForms.object.fields.fields.readonlyWhen.helpText": "1daba886fd4e7f25", + "metadataForms.object.fields.fields.readonlyWhen.label": "af1c3f486fb2fbf7", + "metadataForms.object.fields.fields.requiredWhen.helpText": "997a0d21e5e32203", + "metadataForms.object.fields.fields.requiredWhen.label": "89b4d31ea46e2e76", + "metadataForms.object.fields.fields.summaryOperations.field.helpText": "ff2f535b8f9cfffe", + "metadataForms.object.fields.fields.summaryOperations.field.label": "e21c314685cd95ca", + "metadataForms.object.fields.fields.summaryOperations.function.helpText": "b1022db50ca584b3", + "metadataForms.object.fields.fields.summaryOperations.function.label": "9ae3f998b780a051", + "metadataForms.object.fields.fields.summaryOperations.helpText": "4657cc4f93ecc549", + "metadataForms.object.fields.fields.summaryOperations.label": "bcf65520573738ee", + "metadataForms.object.fields.fields.summaryOperations.object.helpText": "7e6fc21663360844", + "metadataForms.object.fields.fields.summaryOperations.object.label": "d0c49032d95ebb36", + "metadataForms.object.fields.fields.visibleWhen.helpText": "46de2d3ff57b6667", + "metadataForms.object.fields.fields.visibleWhen.label": "c852d4249db93285", + "metadataForms.page.fields.interfaceConfig.addRecord.helpText": "3eb7b86c3a630db9", + "metadataForms.page.fields.interfaceConfig.addRecord.label": "01068706d8b5418a", + "metadataForms.page.fields.interfaceConfig.allowPrinting.helpText": "b8fa84e13cd1432c", + "metadataForms.page.fields.interfaceConfig.allowPrinting.label": "3d0f3f6f51215492", + "metadataForms.page.fields.interfaceConfig.appearance.helpText": "a6118f268ea9f841", + "metadataForms.page.fields.interfaceConfig.appearance.label": "e71255d10680838c", + "metadataForms.page.fields.interfaceConfig.buttons.helpText": "bdc9b266702b27d8", + "metadataForms.page.fields.interfaceConfig.buttons.label": "213eb6a492478539", + "metadataForms.page.fields.interfaceConfig.columns.helpText": "f2330254ad68acb8", + "metadataForms.page.fields.interfaceConfig.columns.label": "c849ea0a39dedbb6", + "metadataForms.page.fields.interfaceConfig.filterBy.helpText": "a73677b167a7379e", + "metadataForms.page.fields.interfaceConfig.filterBy.label": "141e52c71ef928bc", + "metadataForms.page.fields.interfaceConfig.helpText": "d99664c5cd130355", + "metadataForms.page.fields.interfaceConfig.label": "ebcdc9f327772aac", + "metadataForms.page.fields.interfaceConfig.levels.helpText": "6d33feacb82984ff", + "metadataForms.page.fields.interfaceConfig.levels.label": "f90c5b5c1457e991", + "metadataForms.page.fields.interfaceConfig.recordAction.helpText": "2356903762168e64", + "metadataForms.page.fields.interfaceConfig.recordAction.label": "b6e738e297b43df0", + "metadataForms.page.fields.interfaceConfig.showRecordCount.helpText": "23de51872ec0e7a5", + "metadataForms.page.fields.interfaceConfig.showRecordCount.label": "3f45f6242141d01a", + "metadataForms.page.fields.interfaceConfig.sort.helpText": "15ebb2c0a3d2390e", + "metadataForms.page.fields.interfaceConfig.sort.label": "4c590a0ed0c02eff", + "metadataForms.page.fields.interfaceConfig.source.helpText": "f3351049c2af63e7", + "metadataForms.page.fields.interfaceConfig.source.label": "8fe786e8e29c8cec", + "metadataForms.page.fields.interfaceConfig.userActions.helpText": "f720e5e6f4f35dae", + "metadataForms.page.fields.interfaceConfig.userActions.label": "3aea42abc7d6ffd0", + "metadataForms.page.fields.interfaceConfig.userFilters.helpText": "8791bc40505cac7d", + "metadataForms.page.fields.interfaceConfig.userFilters.label": "7e2bbb262bd29aa1", + "metadataForms.page.sections.interface.description": "7e60b977279a05dc", + "metadataForms.page.sections.interface.label": "e2280abc5267ddec", + "metadataForms.report.fields.dataset.helpText": "3cd157f274c985df", + "metadataForms.report.fields.dataset.label": "19647dcb84ce82d3", + "metadataForms.report.fields.drilldown.helpText": "778174eac8057764", + "metadataForms.report.fields.drilldown.label": "25ae77d240ced5f5", + "metadataForms.report.fields.rows.helpText": "4307edcc3f3097b5", + "metadataForms.report.fields.rows.label": "319af74cc41ea823", + "metadataForms.report.fields.runtimeFilter.helpText": "eb9ba8aeb55b7d40", + "metadataForms.report.fields.runtimeFilter.label": "90fe0e44293633fa", + "metadataForms.report.fields.values.helpText": "0cd8bc86069a89d1", + "metadataForms.report.fields.values.label": "3edea9880d8fee80", + "metadataForms.report.sections.dataset_binding.description": "54650ff5f09fd1bd", + "metadataForms.report.sections.dataset_binding.label": "34d97a3093d6fc56", + "metadataForms.seed.description": "c22921feb0f06273", + "metadataForms.seed.label": "60e0e22a54230cab", + "objects.sys_account._actions.link_social.params.provider.options.apple": "cfdc41e15ed6699b", + "objects.sys_account._actions.link_social.params.provider.options.discord": "12f931cc062e76ae", + "objects.sys_account._actions.link_social.params.provider.options.facebook": "7eea009178f5b807", + "objects.sys_account._actions.link_social.params.provider.options.github": "2971d247eb4e6abc", + "objects.sys_account._actions.link_social.params.provider.options.gitlab": "bc3dbbf4b650e600", + "objects.sys_account._actions.link_social.params.provider.options.google": "6fadcd05bb8da367", + "objects.sys_account._actions.link_social.params.provider.options.microsoft": "17309efbdb1ec122", + "objects.sys_account.fields.access_token.help": "814695a136172228", + "objects.sys_account.fields.id_token.help": "b545f62224b21f69", + "objects.sys_account.fields.previous_password_hashes.help": "3a98e8164b0daf11", + "objects.sys_account.fields.previous_password_hashes.label": "2aa498ce717c7d77", + "objects.sys_account.fields.refresh_token.help": "f337bcd15c9b4cb3", + "objects.sys_api_key.fields.active_organization_id.help": "74718b93fdaa52cc", + "objects.sys_api_key.fields.active_organization_id.label": "51bce7fbc99fbf87", + "objects.sys_email.fields.message_id.label": "14cd089a4062f4b1", + "objects.sys_email_template.fields.customized.help": "fc75c32a37f906ea", + "objects.sys_email_template.fields.customized.label": "6990986111e53429", + "objects.sys_email_template.fields.id.label": "00b0385c9c152888", + "objects.sys_email_template.fields.managed_by.help": "0d7397e9e0e84cfe", + "objects.sys_email_template.fields.managed_by.label": "be9070801491e6af", + "objects.sys_email_template.fields.managed_by.options.admin": "f8c9f123caf6217d", + "objects.sys_email_template.fields.managed_by.options.package": "1c8c6168730324a3", + "objects.sys_email_template.fields.managed_by.options.platform": "820d30a5082ebc6c", + "objects.sys_invitation.fields.business_unit_id.help": "b4e37b1bfcc40f34", + "objects.sys_invitation.fields.business_unit_id.label": "bf0e0fdbda66ec9b", + "objects.sys_invitation.fields.positions.help": "abfa7911007e38f1", + "objects.sys_invitation.fields.positions.label": "3428d066379ee33b", + "objects.sys_invitation.fields.role.options.delegated_admin": "13f0e7f118c839e0", + "objects.sys_member._views.mine.emptyState.message": "36cf7e3a3eebed0b", + "objects.sys_member._views.mine.emptyState.title": "df533ee25aba6de9", + "objects.sys_member._views.mine.label": "46b6227bcdb2d02e", + "objects.sys_member.fields.role.options.delegated_admin": "13f0e7f118c839e0", + "objects.sys_metadata.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_audit.fields.lock_state.options.nodelete": "248b810d57fe8bb9", + "objects.sys_metadata_audit.fields.lock_state.options.nooverlay": "49a44e4a03ff124c", + "objects.sys_metadata_history.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_history.fields.recorded_by.help": "e293bd6cf3c47060", + "objects.sys_migration.fields.deviation_detail.help": "681fe28dc0c34386", + "objects.sys_migration.fields.deviation_detail.label": "5bf0f0f59a89e2fb", + "objects.sys_migration.fields.deviation_observed_at.help": "d6720808eb570acd", + "objects.sys_migration.fields.deviation_observed_at.label": "d68ffbef206322e0", + "objects.sys_notification._views.by_topic.label": "a0ddbe432474ed94", + "objects.sys_notification._views.recent.emptyState.message": "4a1e91a49eaf59d6", + "objects.sys_notification._views.recent.emptyState.title": "6f708a09bb4c9a5c", + "objects.sys_notification._views.recent.label": "62d27bb9d0349c99", + "objects.sys_notification.fields.dedup_key.help": "58a5e28b6a87e6d6", + "objects.sys_notification.fields.dedup_key.label": "33a740837f882c98", + "objects.sys_notification.fields.payload.help": "e1e8514cd730b06f", + "objects.sys_notification.fields.payload.label": "4488e1328e37dcb2", + "objects.sys_notification.fields.severity.help": "4925b29cf7e10346", + "objects.sys_notification.fields.severity.label": "d59e4345bdc57b59", + "objects.sys_notification.fields.severity.options.critical": "7ff0ff69c0abaf81", + "objects.sys_notification.fields.severity.options.info": "3e0c8611029f253b", + "objects.sys_notification.fields.severity.options.warning": "2673ee95caf83284", + "objects.sys_notification.fields.topic.help": "80e1790edfda49df", + "objects.sys_notification.fields.topic.label": "819afdb3853e9d80", + "objects.sys_oauth_access_token.fields.authorization_code_id.help": "84ad4d5d8c4c1cee", + "objects.sys_oauth_access_token.fields.authorization_code_id.label": "1c1d06cc01295862", + "objects.sys_oauth_access_token.fields.confirmation.help": "9d086fdc90baa01d", + "objects.sys_oauth_access_token.fields.confirmation.label": "64755493e6425b37", + "objects.sys_oauth_access_token.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_access_token.fields.requested_user_info_claims.help": "dc0cd8121cbde60f", + "objects.sys_oauth_access_token.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_access_token.fields.resources.help": "8c153517b30bd6a5", + "objects.sys_oauth_access_token.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_access_token.fields.revoked.help": "ff6a4b6dac031959", + "objects.sys_oauth_access_token.fields.revoked.label": "054f918e632528c7", + "objects.sys_oauth_application._actions.create_oauth_application.params.type.options.web": "2a7ead3da8c035c5", + "objects.sys_oauth_application._actions.create_oauth_application.resultDialog.fields.client.client_id": "09cb452151e7b1b6", + "objects.sys_oauth_application._actions.create_oauth_application.resultDialog.fields.client.client_secret": "8c703fe05bf2ed5d", + "objects.sys_oauth_application._views.mine.label": "d1cb1174cd96176c", + "objects.sys_oauth_application.fields.backchannel_logout_session_required.help": "2d12d1e1f9461576", + "objects.sys_oauth_application.fields.backchannel_logout_session_required.label": "fbc6eee84fb8a1d7", + "objects.sys_oauth_application.fields.backchannel_logout_uri.help": "e7c8abeac494b145", + "objects.sys_oauth_application.fields.backchannel_logout_uri.label": "f78777a91cebd19f", + "objects.sys_oauth_application.fields.client_credentials_scopes.help": "bf69fe7f91bb78e7", + "objects.sys_oauth_application.fields.client_credentials_scopes.label": "e257098b80b760d2", + "objects.sys_oauth_application.fields.client_discovery_id.help": "6d7fff4d4e7fb705", + "objects.sys_oauth_application.fields.client_discovery_id.label": "2070f4b2f6456352", + "objects.sys_oauth_application.fields.dpop_bound_access_tokens.help": "e7257c5c6ea7043b", + "objects.sys_oauth_application.fields.dpop_bound_access_tokens.label": "3772715f1d9588af", + "objects.sys_oauth_application.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_application.fields.jwks.help": "6a6c5fe835f213b8", + "objects.sys_oauth_application.fields.jwks.label": "b2cf79f9d66c0dbc", + "objects.sys_oauth_application.fields.jwks_uri.help": "217cff7cca47a35e", + "objects.sys_oauth_application.fields.jwks_uri.label": "2a4339df734ab269", + "objects.sys_oauth_client_assertion.description": "82a3f5fec7571bd4", + "objects.sys_oauth_client_assertion.fields.expires_at.help": "90b732ea4d289003", + "objects.sys_oauth_client_assertion.fields.expires_at.label": "df0ef5fae02b2044", + "objects.sys_oauth_client_assertion.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_client_assertion.label": "964d685e79999dce", + "objects.sys_oauth_client_assertion.pluralLabel": "286f417fcbf2ab81", + "objects.sys_oauth_client_resource.description": "c63dda9f9c51165b", + "objects.sys_oauth_client_resource.fields.client_id.help": "8df79a6c07fc32b4", + "objects.sys_oauth_client_resource.fields.client_id.label": "09cb452151e7b1b6", + "objects.sys_oauth_client_resource.fields.created_at.label": "1f02d416befb595b", + "objects.sys_oauth_client_resource.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_client_resource.fields.metadata.help": "c51d0437d1b00c8b", + "objects.sys_oauth_client_resource.fields.metadata.label": "9cabc04013c80ef3", + "objects.sys_oauth_client_resource.fields.resource_id.help": "5c7d52276882432d", + "objects.sys_oauth_client_resource.fields.resource_id.label": "21668ca570b12d71", + "objects.sys_oauth_client_resource.label": "882f5ca283a58ed1", + "objects.sys_oauth_client_resource.pluralLabel": "ab6a91aa51de6ac0", + "objects.sys_oauth_consent.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_consent.fields.requested_user_info_claims.help": "6f1fc3c5e6b4031d", + "objects.sys_oauth_consent.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_consent.fields.resources.help": "f1e24c7b80ba287a", + "objects.sys_oauth_consent.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_refresh_token.fields.authorization_code_id.help": "b16f370e0a06cf43", + "objects.sys_oauth_refresh_token.fields.authorization_code_id.label": "1c1d06cc01295862", + "objects.sys_oauth_refresh_token.fields.confirmation.help": "9d086fdc90baa01d", + "objects.sys_oauth_refresh_token.fields.confirmation.label": "64755493e6425b37", + "objects.sys_oauth_refresh_token.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_refresh_token.fields.requested_user_info_claims.help": "dc0cd8121cbde60f", + "objects.sys_oauth_refresh_token.fields.requested_user_info_claims.label": "5a6338a357257661", + "objects.sys_oauth_refresh_token.fields.resources.help": "8c153517b30bd6a5", + "objects.sys_oauth_refresh_token.fields.resources.label": "f5753a2fa351119e", + "objects.sys_oauth_refresh_token.fields.rotated_at.help": "704d34a1f46f4412", + "objects.sys_oauth_refresh_token.fields.rotated_at.label": "aa747daa25d440d1", + "objects.sys_oauth_refresh_token.fields.rotation_replay_expires_at.help": "7f5925e4b2d73586", + "objects.sys_oauth_refresh_token.fields.rotation_replay_expires_at.label": "f6a0bbe6ef73bded", + "objects.sys_oauth_refresh_token.fields.rotation_replay_response.help": "ea6d3f140afbbca7", + "objects.sys_oauth_refresh_token.fields.rotation_replay_response.label": "ddf6cf0ccba55c30", + "objects.sys_oauth_resource.description": "3c7f0cfae5b1a887", + "objects.sys_oauth_resource.fields.access_token_ttl.help": "293dc402a9669032", + "objects.sys_oauth_resource.fields.access_token_ttl.label": "afebf5750ed4d9ee", + "objects.sys_oauth_resource.fields.allowed_scopes.help": "af9195bf5efc4b27", + "objects.sys_oauth_resource.fields.allowed_scopes.label": "1a3202eecb314841", + "objects.sys_oauth_resource.fields.created_at.label": "1f02d416befb595b", + "objects.sys_oauth_resource.fields.custom_claims.help": "0afe2b2a099c7813", + "objects.sys_oauth_resource.fields.custom_claims.label": "c6182d11456325c4", + "objects.sys_oauth_resource.fields.disabled.label": "639459b8a7bbec04", + "objects.sys_oauth_resource.fields.dpop_bound_access_tokens_required.help": "d82b668d32d9d48f", + "objects.sys_oauth_resource.fields.dpop_bound_access_tokens_required.label": "715d383f9fc4fcfb", + "objects.sys_oauth_resource.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_resource.fields.identifier.help": "b69d2662e0438576", + "objects.sys_oauth_resource.fields.identifier.label": "85b28b4a09f37cd0", + "objects.sys_oauth_resource.fields.metadata.help": "896f351a40678f21", + "objects.sys_oauth_resource.fields.metadata.label": "9cabc04013c80ef3", + "objects.sys_oauth_resource.fields.name.label": "5274417ff1ccaf01", + "objects.sys_oauth_resource.fields.policy_version.help": "18b93d988a1d6107", + "objects.sys_oauth_resource.fields.policy_version.label": "55fdeadcaba6cd2a", + "objects.sys_oauth_resource.fields.refresh_token_ttl.help": "9bbb700667d7cee7", + "objects.sys_oauth_resource.fields.refresh_token_ttl.label": "e4b35b5501ac588f", + "objects.sys_oauth_resource.fields.signing_algorithm.help": "3f8231cc5435a673", + "objects.sys_oauth_resource.fields.signing_algorithm.label": "c26616cf69746887", + "objects.sys_oauth_resource.fields.signing_key_id.help": "8504e8cddac3b67c", + "objects.sys_oauth_resource.fields.signing_key_id.label": "50da11ac4977fa58", + "objects.sys_oauth_resource.fields.updated_at.label": "aba63dc2a9c79b8d", + "objects.sys_oauth_resource.label": "5077fa14e8d7418e", + "objects.sys_oauth_resource.pluralLabel": "1c6ad6e11287866f", + "objects.sys_organization.fields.parent_organization_id.help": "2ad4ebe3782901b9", + "objects.sys_organization.fields.parent_organization_id.label": "24129608643b897f", + "objects.sys_organization.fields.sort_order.help": "7355b98a96f85dfc", + "objects.sys_organization.fields.sort_order.label": "5f6b33fdc89e9d9f", + "objects.sys_scim_provider.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_provider.fields.provider_key.help": "6eba9e41bfb954ab", + "objects.sys_scim_provider.fields.provider_key.label": "fbc96a8b3ed4709d", + "objects.sys_secret._views.all.label": "20d032bd60c81773", + "objects.sys_secret.fields.id.label": "00b0385c9c152888", + "objects.sys_session.fields.last_activity_at.help": "f7851e9373505e73", + "objects.sys_session.fields.last_activity_at.label": "43bd2f1b231bc12b", + "objects.sys_session.fields.revoke_reason.help": "ff2c9a1aa9be356d", + "objects.sys_session.fields.revoke_reason.label": "13b0146153a2ecf8", + "objects.sys_session.fields.revoked_at.help": "62d1b62cde5ce487", + "objects.sys_session.fields.revoked_at.label": "054f918e632528c7", + "objects.sys_setting_audit._views.recent.label": "62d27bb9d0349c99", + "objects.sys_setting_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_setting_audit.fields.source.options.api": "4baeeff968e192d1", + "objects.sys_setting_audit.fields.source.options.ui": "95caad102a5c97ba", + "objects.sys_sso_provider._actions.register_saml_provider.params.entryPoint.label": "65c2606e2e467cf7", + "objects.sys_sso_provider._actions.register_saml_provider.params.identifierFormat.placeholder": "5945ff9a6b644855", + "objects.sys_sso_provider._actions.register_sso_provider.params.mapEmail.placeholder": "66309c7adf5c9436", + "objects.sys_sso_provider._actions.register_sso_provider.params.mapName.placeholder": "a484c34aaf624bd6", + "objects.sys_sso_provider._actions.register_sso_provider.params.scopes.placeholder": "58ac90cde28c764d", + "objects.sys_sso_provider.fields.id.label": "00b0385c9c152888", + "objects.sys_team.fields.member_count.help": "498d7d7119ffff12", + "objects.sys_team.fields.member_count.label": "7bb48f7c92af14e0", + "objects.sys_team_member.fields.membership_key.help": "08a5017306c3ac4e", + "objects.sys_team_member.fields.membership_key.label": "8fcb9f17bcc8cae9", + "objects.sys_two_factor.fields.failed_verification_count.help": "8cea41d6de7f4d5a", + "objects.sys_two_factor.fields.failed_verification_count.label": "e0438d2e39cb0500", + "objects.sys_two_factor.fields.locked_until.help": "059956062c752f2c", + "objects.sys_two_factor.fields.locked_until.label": "c7f013ad9b7208d2", + "objects.sys_two_factor.fields.verified.help": "0d351ba4c31608c2", + "objects.sys_two_factor.fields.verified.label": "866e3a245ef04734", + "objects.sys_user.fields.manager_id.help": "a53dec9ccd9d131f", + "objects.sys_user.fields.primary_business_unit_id.help": "e01690f4a9b83956", + "objects.sys_view_definition.fields.id.label": "00b0385c9c152888", +}; diff --git a/packages/platform-objects/src/metadata-translations/index.ts b/packages/platform-objects/src/metadata-translations/index.ts index a880b0ff87..4bb29fde97 100644 --- a/packages/platform-objects/src/metadata-translations/index.ts +++ b/packages/platform-objects/src/metadata-translations/index.ts @@ -5,6 +5,13 @@ import { enMetadataForms } from '../apps/translations/en.metadata-forms.generate import { zhCNMetadataForms } from '../apps/translations/zh-CN.metadata-forms.generated.js'; import { jaJPMetadataForms } from '../apps/translations/ja-JP.metadata-forms.generated.js'; import { esESMetadataForms } from '../apps/translations/es-ES.metadata-forms.generated.js'; +import { zhCNGeneratedSourceHashes } from '../apps/translations/zh-CN.source-hashes.generated.js'; +import { jaJPGeneratedSourceHashes } from '../apps/translations/ja-JP.source-hashes.generated.js'; +import { esESGeneratedSourceHashes } from '../apps/translations/es-ES.source-hashes.generated.js'; +import { withSourceFallback } from '../apps/translations/source-hash.js'; + +/** The source bundle these three are judged against — `en` is a copy of it. */ +const enSource = { metadataForms: enMetadataForms }; /** * `MetadataFormsTranslations` @@ -20,10 +27,20 @@ import { esESMetadataForms } from '../apps/translations/es-ES.metadata-forms.gen * * preserves existing translations (via `--merge`) and only fills newly * added schema keys per `--fill=default`. + * + * ## Staleness (#11671) + * + * "Only fills newly added keys" is exactly the sticky drift the source-hash + * mechanism exists for: a leaf filled from the source and then left behind when + * the source was revised keeps serving a superseded draft, present and in sync + * by key, forever. The three translated locales therefore pass through + * `withSourceFallback`, judged by the generated provenance tables. The THIRD + * argument is `undefined` on purpose — the hand-authored table judges + * `apps`/`dashboards`/`pages`, and this bundle carries none of those. */ export const MetadataFormsTranslations: TranslationBundle = { en: { metadataForms: enMetadataForms }, - 'zh-CN': { metadataForms: zhCNMetadataForms }, - 'ja-JP': { metadataForms: jaJPMetadataForms }, - 'es-ES': { metadataForms: esESMetadataForms }, + 'zh-CN': withSourceFallback({ metadataForms: zhCNMetadataForms }, enSource, undefined, zhCNGeneratedSourceHashes), + 'ja-JP': withSourceFallback({ metadataForms: jaJPMetadataForms }, enSource, undefined, jaJPGeneratedSourceHashes), + 'es-ES': withSourceFallback({ metadataForms: esESMetadataForms }, enSource, undefined, esESGeneratedSourceHashes), }; diff --git a/scripts/check-i18n-stale-fill.mjs b/scripts/check-i18n-stale-fill.mjs index 3490452b2e..48360c196b 100644 --- a/scripts/check-i18n-stale-fill.mjs +++ b/scripts/check-i18n-stale-fill.mjs @@ -124,6 +124,37 @@ const at = (p) => join(ROOT, p); const BASELINE_PATH = 'scripts/i18n-stale-fill-baseline.json'; const PACKAGES_DIR = 'packages'; +/** + * The one `*.generated.ts` in an out-dir that is NOT a translation bundle. + * + * `os i18n extract --source-hashes` writes `.source-hashes.generated.ts` + * beside the bundles — the provenance sidecar from maintainer ruling #12069 + * Option A, whose leaves are 16-hex digests of source strings, not prose. + * + * Excluded by NAME rather than by shape, and this gate's population is the + * reason it has to be excluded at all: the discovery groups an out-dir's + * `*.generated.ts` files by kind, so without this the three companions form a + * fourth "bundle set" with no `en` member. Measured before the exclusion + * landed: the gate exits 1 on the first one with "could not be parsed as a + * bundle literal" (its refuse-rather-than-skip rule doing its job on a file + * that is not a bundle). Had the sidecar happened to parse, the outcome would + * have been worse and quieter — one digest per source string means the SAME + * path carries the SAME digest in every locale, so condition 1 (>=2 locales + * byte-identical) holds for hundreds of paths, and a hex digest is + * script-disjoint from `zh-CN` and differs from an absent `en` by more than + * case, so conditions 3 and 4 hold too. That is the shape this constant exists + * to keep out of the population. + * + * ⛔ Not a way to shrink what the gate judges: the sidecar contains no + * translated leaf, so no leaf leaves this gate's population with it. + */ +const PROVENANCE_KIND = 'source-hashes.generated.ts'; + +/** Is this `.` suffix a translation bundle rather than the sidecar? */ +export function isTranslationBundleKind(kind) { + return kind !== PROVENANCE_KIND; +} + /** The locale the extractor copies the source into. `os i18n extract`'s default. */ const DEFAULT_LOCALE = 'en'; @@ -260,7 +291,8 @@ function discoverBundleSets() { if (!existsSync(at(out))) continue; const wanted = new Set([DEFAULT_LOCALE, ...locales.split(',')]); const files = readdirSync(at(out)).filter((f) => f.endsWith('.generated.ts')); - for (const kind of [...new Set(files.map((f) => f.replace(/^[^.]+\./, '')))].sort()) { + const kinds = [...new Set(files.map((f) => f.replace(/^[^.]+\./, '')))].filter(isTranslationBundleKind).sort(); + for (const kind of kinds) { const members = files .filter((f) => f.endsWith(kind)) .map((f) => ({ locale: f.slice(0, f.length - kind.length - 1), file: `${out}/${f}` })) @@ -379,6 +411,18 @@ function selfTest() { expect('ratchet reports a NEW id', both.added.length === 1 && both.added[0] === 'c'); expect('ratchet reports a REPAIRED id (ratchet down)', both.removed.length === 1 && both.removed[0] === 'b'); + // The provenance sidecar is not a bundle. Pinned because the discovery groups + // an out-dir by filename kind, so a future companion file lands in the + // population by default and this is the only thing that keeps it out. + expect( + 'the source-hashes provenance companion is NOT a translation bundle kind', + isTranslationBundleKind('source-hashes.generated.ts') === false, + ); + expect( + 'the real bundle kinds still are', + isTranslationBundleKind('objects.generated.ts') && isTranslationBundleKind('metadata-forms.generated.ts'), + ); + console.log(failures === 0 ? '\ncheck-i18n-stale-fill: self-test OK\n' : `\ncheck-i18n-stale-fill: self-test FAILED (${failures})\n`); process.exit(failures === 0 ? 0 : 1); } diff --git a/scripts/i18n-bundle-surface.mjs b/scripts/i18n-bundle-surface.mjs index 609a9dcc31..b606568b24 100644 --- a/scripts/i18n-bundle-surface.mjs +++ b/scripts/i18n-bundle-surface.mjs @@ -155,11 +155,17 @@ export function findMetadataFormModules(absDir, rel, out = []) { * Read the regenerate command a config documents about itself. Every flag the * gate passes comes from there, so a package that changes its locales or output * directory updates one place and both readers follow. + * + * `--source-hashes` (#11671) is in the recognised set for the same reason + * `--no-metadata-forms` is: it decides WHICH FILES the extract emits, so a gate + * that dropped it would run `--check` against a different file set than the one + * the package commits and report drift on a tree that is in sync. A flag that + * only changed a message would not belong here. */ export function flagsFromDocstring(configPath) { const src = readFileSync(configPath, 'utf8'); const head = src.slice(0, src.indexOf('*/') + 2); - const flags = head.match(/--(?:locales|fill|out)=[^\s\\*]+|--(?:objects-only|no-metadata-forms|no-merge)\b/g) ?? []; + const flags = head.match(/--(?:locales|fill|out)=[^\s\\*]+|--(?:objects-only|no-metadata-forms|no-merge|source-hashes)\b/g) ?? []; return [...new Set(flags)]; }