Skip to content

Commit 5ea211c

Browse files
fix(app-showcase): translate the zh-CN first screens the bundle left in English (#25535)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuACdPWFJTLDcTrByFCHLzqb)_ Fixes #25505 Clause-②: no Metadata-protocol: none ## What changed In a zh-CN session the showcase's sidebar was translated, but the screens it opens were mostly English. This PR adds the zh-CN keys for those first screens to `examples/app-showcase/src/system/translations/index.ts`, and moves the showcase row of `scripts/i18n-coverage-baseline.json` down to the measured count, **414 → 320**. | Surface on the card | Keys added (zh-CN) | |---|---| | Page headings: Capability Map, My Work (+ subtitle), Approvals, Task Board, Active Projects | `pages.NAME.label`, plus `pages.showcase_my_work.subtitle` | | Delivery Operations dashboard: label, description and all 9 widget titles (Active Projects, At-Risk (Red), Awaiting Review, Total Budget, Projects by Health, …) | `dashboards.showcase_ops_dashboard.*` | | Expense Reports, Business Units, Cascading Selects: label, plural, description, every field and option | `objects.showcase_expense_report`, `objects.showcase_business_unit`, `objects.showcase_cascade` | | Contacts: the list-header description and the `stage` options | `objects.showcase_contact` | | Projects: status / health / labels options, plus the Labels, Team Members, Budget Remaining, Tasks and Total Estimate (h) columns, plus the description | `objects.showcase_project` | | Task actions and toasts: Mark Done (+ "Task marked done."), Log Time, Archive (+ toast), Reassign…, Open Docs, plus Quick View and New Task | `objects.showcase_task._actions.*` | The wording reuses the words the bundle already has for the same ideas. Each page heading and the dashboard label use its sidebar entry's word (能力地图, 我的工作, 审批, 任务看板, 进行中的项目, 交付运营). Project `planned` is 计划中, as in the existing `project_status_flow` message. Awaiting Review is 待评审, matching the task's 评审中 option. Total Budget is 总预算, as in the command center's `cc_k6`. 工时 and 重新分配 come from the recalc actions and the reassign flow. 合计 is the invoice's `total`. Quick View and New Task are not on the card. They sit in the same task row and toolbar as the actions the card lists, so leaving them out would have left English next to Chinese in the same menu. The Contacts `stage` options are on the same Contacts list the card names. **Landing:** this is the file surface the claim names. Measured with `os lint examples/app-showcase/objectstack.config.ts --json`, all 94 keys that disappear are on the surfaces listed above, and no new key shows up. The non-i18n issue count is unchanged (79 before and after). Seven `field-no-consumers` suggestions only change from "inert" to "carrier-only", because the bundle now names those fields. **No contract gap.** The card guessed that page `title` and action `successMessage` might have no key. Both do have keys. `TranslationBundle` declares `successMessage` (`packages/spec/src/system/translation.zod.ts`), and `translatePage` reads `pages.NAME.title` / `subtitle` (`packages/spec/src/system/i18n-resolver.ts`). The page heading the card saw is each page's `label`, and the My Work header's `title` resolves through the label key because it equals the label (`resolvePageHeaderTitle`). `packages/spec` is not touched. ## Tests The showcase already has a coverage-pin pattern: `test/seed.test.ts`'s section sweep (#8231), which requires a non-empty, non-ASCII zh-CN value for every named section. I extended that pattern with `showcase zh-CN first screens are translated (#25505)`. It names the five objects, five pages, the ops dashboard and the task's actions. From the composed stack it derives every label, plural, description, field label, option, widget title, action label and `successMessage` that each one declares, and it fails on any of them that is missing or echoed in ASCII. There is also one vacuity guard per surface. The baseline is a single count, so it cannot see a first-screen key traded for an obscure one, and this pin can. - Reverse verification, done after committing the fix. I restored the translation file to the merge base `b2411c001d`, tree only, and the blob matched the base. The sweep then went red: **12 failed / 1 passed** (every surface red, guard green). Restored with `git checkout HEAD -- PATH`, the blob matched HEAD again: `cb0f979923`. - At final head `4ddea0ee7d`, after merging `origin/main`: `pnpm --filter @objectstack/example-showcase typecheck` exit 0 (the tsc program includes `test/seed.test.ts`). `pnpm --filter @objectstack/example-showcase test`: **Test Files 38 passed (38), Tests 474 passed (474)**. - `pnpm check:i18n-coverage` at `4ddea0ee7d`: `check-i18n-coverage: OK (14 config(s), 527 baselined untranslated string(s), none new).` The baseline was written by the script's own `--update`, and only the showcase row moved. ## Gates `node scripts/pm/dispatch-gates.mjs --commands` at `4ddea0ee7d` derives 54 commands. I ran all 54 locally, and all 54 exit 0. `pnpm check:dual-build-cjs-loads` first exited 3 with PREREQUISITE NOT MET: seven unrelated packages had no `dist/` in this worktree. After building them it exited 0. Reconciliation with `--ran`: `54 derived famil(ies) accounted for — 54 run, 0 NOT-MEASURED (a DERIVED zero — all 54 recorded an exit code and none of them is 3)`. The families CI runs are read from this PR's check-runs. ## Changeset None, and the PR carries `skip-changeset`. `@objectstack/example-showcase` is `private: true` and publishes nothing. `scripts/i18n-coverage-baseline.json` is repo-root tooling. This matches the precedent of the last showcase fix, #25525. ## Acceptance notes - The showcase still has 320 untranslated declared strings, and all of them are inside the baseline. Not on this card: the project list's six `bulkActions` (label, confirmText and params, 17 keys); the expense / invoice line objects; the Field Zoo, semantic-zoo, inquiry, private-note, announcement and external-datasource objects; the other two dashboards' widgets; the datasets; and the remaining page labels. - Two different words for one task status remain. The ops dashboard's `task_status` filter options (待办池 / 待处理 / 审核中, from earlier work) differ from the task's own options (待规划 / 待办 / 评审中). This PR does not change them, because the card does not name them. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d7ccdfc commit 5ea211c

3 files changed

Lines changed: 255 additions & 14 deletions

File tree

‎examples/app-showcase/src/system/translations/index.ts‎

Lines changed: 154 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -372,16 +372,38 @@ export const ShowcaseTranslationBundle = {
372372
showcase_project: {
373373
label: '项目',
374374
pluralLabel: '项目',
375+
description: '为某个客户交付的项目。',
376+
// Every field the project grid and record surface as a column, and
377+
// every option a status / health / labels cell renders — the grid
378+
// that shows 进行中 for a task must not show "Active" for a project.
379+
// `planned` reads 计划中 because `_validations.project_status_flow`
380+
// below already says so; `active` reads 进行中 for the same reason
381+
// `apps…nav_active_projects` does. `task_count` is a count column,
382+
// hence 任务数; `total_estimate` reuses showcase_task's 预计工时.
375383
fields: {
376384
name: { label: '项目名称' },
377385
account: { label: '客户' },
378386
owner: { label: '负责人' },
379-
status: { label: '状态' },
380-
health: { label: '健康度' },
387+
status: {
388+
label: '状态',
389+
options: { planned: '计划中', active: '进行中', on_hold: '已暂停', completed: '已完成', cancelled: '已取消' },
390+
},
391+
health: {
392+
label: '健康度',
393+
options: { green: '绿色', yellow: '黄色', red: '红色' },
394+
},
381395
budget: { label: '预算' },
382396
spent: { label: '已花费' },
397+
budget_remaining: { label: '剩余预算' },
383398
start_date: { label: '开始日期' },
384399
end_date: { label: '结束日期' },
400+
labels: {
401+
label: '标签',
402+
options: { frontend: '前端', backend: '后端', design: '设计', qa: '测试', devops: '运维' },
403+
},
404+
team_members: { label: '团队成员' },
405+
task_count: { label: '任务数' },
406+
total_estimate: { label: '总预计工时' },
385407
},
386408
// The zh-CN mirror of the `en` `_validations` block — see the note
387409
// there. Without these two keys the write path's own refusals arrive in
@@ -503,6 +525,18 @@ export const ShowcaseTranslationBundle = {
503525
// The hook-refusal specimen: every click is refused by
504526
// showcase_guard_task_reopen, so it carries no successMessage.
505527
showcase_reopen_task: { label: '重新打开' },
528+
// The rest of the task's row / toolbar / bulk actions, so a row menu
529+
// never lists 重算工时 beside an English "Mark Done". 工时 is the
530+
// word the recalc entries above use; 重新分配 is the reassign
531+
// wizard's own flow label (`flows.showcase_reassign_wizard`); 新建…
532+
// is the shape a create entry takes in this bundle.
533+
showcase_mark_done: { label: '标记为完成', successMessage: '任务已标记为完成。' },
534+
showcase_open_docs: { label: '打开文档' },
535+
showcase_bulk_reassign: { label: '重新分配…' },
536+
showcase_quick_view: { label: '快速查看' },
537+
showcase_log_time: { label: '记录工时' },
538+
showcase_new_task: { label: '新建任务' },
539+
showcase_archive_task: { label: '归档', successMessage: '任务已归档(演示——未修改任何数据)。' },
506540
},
507541
// Section headings of the six form-view projections in
508542
// `ui/views/task.view.ts` (edit / tabbed / wizard / split / quick).
@@ -579,14 +613,23 @@ export const ShowcaseTranslationBundle = {
579613
showcase_contact: {
580614
label: '联系人',
581615
pluralLabel: '联系人',
616+
// The list header renders the object's description, so leaving it out
617+
// put one English sentence above an otherwise translated grid. The
618+
// ADR reference is kept, as `showcase_client_brief.description` keeps
619+
// its own.
620+
description:
621+
'演示「默认推导 + 稀疏覆盖」表单:一套扁平、分组、带意图标记的字段集,同时投影为完整的编辑表单和精简的新建表单(ADR-0047)。',
582622
fields: {
583623
name: { label: '姓名' },
584624
email: { label: '邮箱' },
585625
phone: { label: '电话' },
586626
company: { label: '公司' },
587627
title: { label: '职务' },
588628
account: { label: '客户' },
589-
stage: { label: '阶段' },
629+
stage: {
630+
label: '阶段',
631+
options: { new: '新建', working: '跟进中', qualified: '合格', closed: '已关闭' },
632+
},
590633
lead_score: { label: '线索评分' },
591634
notes: { label: '备注' },
592635
},
@@ -700,8 +743,35 @@ export const ShowcaseTranslationBundle = {
700743
},
701744
},
702745
},
746+
// The expense report list is a sidebar entry (`nav_expense_reports`,
747+
// 报销单), so its object label, description, every column and every
748+
// status option are translated here — the sidebar's word and the list's
749+
// header now agree. 草稿 / 已提交 / 已批准 follow the 已… shape this
750+
// bundle uses for a reached state; 合计 is showcase_invoice's `total`.
751+
showcase_expense_report: {
752+
label: '报销单',
753+
pluralLabel: '报销单',
754+
description: '报销单的汇总金额由其明细行按条件汇总而来。',
755+
fields: {
756+
name: { label: '报销单标题' },
757+
employee: { label: '员工' },
758+
status: {
759+
label: '状态',
760+
options: { draft: '草稿', submitted: '已提交', approved: '已批准', reimbursed: '已报销' },
761+
},
762+
submitted_on: { label: '提交日期' },
763+
total_amount: { label: '合计' },
764+
approved_amount: { label: '已批准金额' },
765+
reimbursable_amount: { label: '可报销金额' },
766+
line_count: { label: '明细行数' },
767+
rejected_count: { label: '已驳回行数' },
768+
over_limit_count: { label: '超过 $500' },
769+
},
770+
},
703771
// See the `en` side for why this entry translates exactly ONE field and
704-
// no more (check-i18n-coverage is a two-sided ratchet).
772+
// no more (check-i18n-coverage is a two-sided ratchet). The line's other
773+
// labels are still in the baseline: only the report list is a first
774+
// screen.
705775
showcase_expense_line: {
706776
fields: {
707777
incurred_at: { label: '发生时间' },
@@ -751,10 +821,11 @@ export const ShowcaseTranslationBundle = {
751821
label: '分类', pluralLabel: '分类',
752822
fields: { name: { label: '名称' }, parent: { label: '上级' }, color: { label: '颜色' }, sort_order: { label: '排序' } },
753823
},
754-
// `_views` only: these two objects have no zh-CN block of their own, and
755-
// their object/field debt is inside the ratchet's baseline. Translating
756-
// the VIEW labels the coverage fix newly surfaces is what keeps the gate
757-
// from widening; the rest is left exactly as it was.
824+
// `_views` only: this object has no zh-CN block of its own, and its
825+
// object/field debt is inside the ratchet's baseline. Translating the
826+
// VIEW labels the coverage fix newly surfaces is what kept the gate from
827+
// widening; the rest is left exactly as it was. (showcase_business_unit
828+
// below started the same way and now carries its object block too.)
758829
// (`default` = the container's DEFAULT list — see showcase_project.)
759830
showcase_inquiry: {
760831
_views: {
@@ -769,7 +840,24 @@ export const ShowcaseTranslationBundle = {
769840
tell_us_about_yourself: { label: '介绍一下您自己' },
770841
},
771842
},
843+
// Object, fields and `kind` options as well as the views below: the
844+
// sidebar entry (`nav_business_units`, 业务单元 — the platform bundle's
845+
// word for `sys_business_unit`) opens this list. 上级单元 follows
846+
// showcase_category's 上级.
772847
showcase_business_unit: {
848+
label: '业务单元',
849+
pluralLabel: '业务单元',
850+
description: '组织架构层级——在自引用对象上演示树形 / 树形表格视图。',
851+
fields: {
852+
name: { label: '名称' },
853+
parent: { label: '上级单元' },
854+
kind: {
855+
label: '类型',
856+
options: { company: '公司', division: '事业部', department: '部门', team: '团队' },
857+
},
858+
manager: { label: '经理' },
859+
headcount: { label: '人数' },
860+
},
773861
_views: {
774862
default: { label: '全部单元' },
775863
org_chart: { label: '组织架构图' },
@@ -781,6 +869,31 @@ export const ShowcaseTranslationBundle = {
781869
unit: { label: '单元' },
782870
},
783871
},
872+
// The B3 dynamic-options fixture, opened from `nav_cascade` (级联选择).
873+
// Its description is a technical statement about the fixture, so the
874+
// zh-CN copy states the same thing and keeps the metadata keys
875+
// (`visibleWhen`, `dependsOn`) in their source spelling.
876+
showcase_cascade: {
877+
label: '级联选择',
878+
pluralLabel: '级联选择',
879+
description:
880+
'B3 动态选项夹具:国家 → 省份级联(逐选项 visibleWhen + dependsOn),外加按角色门控的等级;在客户端(可选集合)和服务端(objectql 拒绝集合外的提交)同时强制执行。',
881+
fields: {
882+
name: { label: '名称' },
883+
country: {
884+
label: '国家',
885+
options: { cn: '中国', us: '美国' },
886+
},
887+
province: {
888+
label: '省 / 州',
889+
options: { zj: '浙江', gd: '广东', ca: '加利福尼亚', tx: '得克萨斯' },
890+
},
891+
tier: {
892+
label: '等级',
893+
options: { standard: '标准', restricted: '受限(仅管理员)' },
894+
},
895+
},
896+
},
784897
// `_sections` only, on the same footing as the two `_views`-only blocks
785898
// above: this ADR-0085 fixture has no zh-CN block of its own and its
786899
// object/field debt sits inside the ratchet's baseline. Its two
@@ -945,9 +1058,9 @@ export const ShowcaseTranslationBundle = {
9451058
nav_accounts: { label: '客户' },
9461059
nav_contacts: { label: '联系人' },
9471060
nav_invoices: { label: '发票' },
948-
// `showcase_expense_report` has no translated object label (it sits
949-
// inside check-i18n-coverage's frozen baseline), so this renders the
950-
// nav label directly; 报销单 is the standard word for the document.
1061+
// 报销单 is the standard word for the document, and the word
1062+
// `objects.showcase_expense_report` above gives the object, so the
1063+
// sidebar entry and the list it opens read the same.
9511064
nav_expense_reports: { label: '报销单' },
9521065
nav_products: { label: '产品' },
9531066
nav_teams: { label: '团队' },
@@ -1050,6 +1163,25 @@ export const ShowcaseTranslationBundle = {
10501163
},
10511164
},
10521165
showcase_ops_dashboard: {
1166+
// The first dashboard a visitor opens (`nav_ops`, 交付运营 — the same
1167+
// word here). Every widget title, so the KPI row never reads English
1168+
// under a translated header. Each title says the word of the value it
1169+
// filters on: 进行中的项目 is `fields.status.options.active` on
1170+
// showcase_project, 红色 its health option, 待评审 the task's
1171+
// 评审中, 总预算 the command center's `cc_k6`.
1172+
label: '交付运营',
1173+
description: '运营首页——核心 KPI、项目健康度与任务吞吐量。',
1174+
widgets: {
1175+
kpi_active_projects: { title: '进行中的项目' },
1176+
kpi_at_risk: { title: '风险项目(红色)' },
1177+
kpi_awaiting_review: { title: '待评审' },
1178+
kpi_total_budget: { title: '总预算' },
1179+
col_health: { title: '项目健康度分布' },
1180+
bar_status: { title: '任务状态分布' },
1181+
donut_priority: { title: '优先级构成' },
1182+
line_created: { title: '任务吞吐量(按月)' },
1183+
table_spend: { title: '各客户预算与已花费' },
1184+
},
10531185
globalFilters: {
10541186
task_status: {
10551187
label: '任务状态',
@@ -1071,9 +1203,18 @@ export const ShowcaseTranslationBundle = {
10711203
// Page component copy became declared surface with `pages.<name>.components`
10721204
// (#6080), so these keys are born under the ratchet: leaving any of them
10731205
// untranslated widens the frozen baseline and fails `check-i18n-coverage`.
1074-
// The pages' own label/title/subtitle predate the ratchet and stay in the
1075-
// frozen baseline, same as Revenue Pulse's older widget titles above.
1206+
// Most pages' own label/title/subtitle predate the ratchet and stay in the
1207+
// frozen baseline, same as Revenue Pulse's older widget titles above. The
1208+
// exceptions are the pages a visitor lands on first, whose `label` is the
1209+
// page heading: each reads the word its sidebar entry already uses
1210+
// (`apps.showcase_app.navigation`), so the heading matches the link that
1211+
// opened it.
10761212
pages: {
1213+
showcase_capability_map: { label: '能力地图' },
1214+
showcase_my_work: { label: '我的工作', subtitle: '你的待办队列、团队产出,以及需要关注的事项。' },
1215+
showcase_review_queue: { label: '审批' },
1216+
showcase_task_board: { label: '任务看板' },
1217+
showcase_active_projects: { label: '进行中的项目' },
10771218
// `ui/pages/invoice-document.page.ts` — the invoice print page.
10781219
showcase_invoice_document: { label: '发票' },
10791220
showcase_contact_form: {

‎examples/app-showcase/test/seed.test.ts‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,106 @@ describe('showcase form + page section i18n coverage (#8231)', () => {
314314
}
315315
});
316316

317+
/**
318+
* #25505 — the same two halves as the section sweep above, for the screens a
319+
* zh-CN visitor opens first: the landing pages, the first dashboard, the
320+
* object lists the sidebar links to, and the task's row / toolbar actions.
321+
* They used to read English under a fully translated sidebar.
322+
*
323+
* `check:i18n-coverage` measures this app with ONE count, so it cannot see a
324+
* first-screen key traded for an obscure one. This names the surfaces and
325+
* DERIVES what each declares from the composed stack — a field, option, widget
326+
* or action added to one of them later has to arrive translated too.
327+
*/
328+
describe('showcase zh-CN first screens are translated (#25505)', () => {
329+
const zh = (stack.translations?.[0] as any)?.['zh-CN'] as Record<string, any> | undefined;
330+
331+
const OBJECTS = [
332+
'showcase_project',
333+
'showcase_contact',
334+
'showcase_expense_report',
335+
'showcase_business_unit',
336+
'showcase_cascade',
337+
];
338+
const PAGES = [
339+
'showcase_capability_map',
340+
'showcase_my_work',
341+
'showcase_review_queue',
342+
'showcase_task_board',
343+
'showcase_active_projects',
344+
];
345+
const DASHBOARDS = ['showcase_ops_dashboard'];
346+
const ACTIONS_OF = ['showcase_task'];
347+
348+
/** One declared string: its bundle path, and what the zh-CN bundle holds there. */
349+
interface Entry { path: string; value: unknown }
350+
const entry = (path: string): Entry => ({
351+
path,
352+
value: path.split('.').reduce<any>((node, key) => node?.[key], zh),
353+
});
354+
355+
function declaredEntries(): Map<string, Entry[]> {
356+
const out = new Map<string, Entry[]>();
357+
for (const name of OBJECTS) {
358+
const obj = (stack.objects ?? []).find((o: any) => o.name === name) as any;
359+
const list: Entry[] = [];
360+
for (const attr of ['label', 'pluralLabel', 'description']) {
361+
if (obj?.[attr]) list.push(entry(`objects.${name}.${attr}`));
362+
}
363+
for (const [field, def] of Object.entries<any>(obj?.fields ?? {})) {
364+
if (def?.label) list.push(entry(`objects.${name}.fields.${field}.label`));
365+
for (const opt of Array.isArray(def?.options) ? def.options : []) {
366+
if (opt?.label) list.push(entry(`objects.${name}.fields.${field}.options.${opt.value}`));
367+
}
368+
}
369+
out.set(`object ${name}`, list);
370+
}
371+
for (const name of PAGES) {
372+
const page = (stack.pages ?? []).find((p: any) => p.name === name) as any;
373+
out.set(`page ${name}`, page?.label ? [entry(`pages.${name}.label`)] : []);
374+
}
375+
for (const name of DASHBOARDS) {
376+
const dash = (stack.dashboards ?? []).find((d: any) => d.name === name) as any;
377+
const list: Entry[] = [];
378+
for (const attr of ['label', 'description']) {
379+
if (dash?.[attr]) list.push(entry(`dashboards.${name}.${attr}`));
380+
}
381+
for (const w of Array.isArray(dash?.widgets) ? dash.widgets : []) {
382+
if (w?.title) list.push(entry(`dashboards.${name}.widgets.${w.id}.title`));
383+
}
384+
out.set(`dashboard ${name}`, list);
385+
}
386+
for (const object of ACTIONS_OF) {
387+
const list: Entry[] = [];
388+
for (const action of (stack.actions ?? []) as any[]) {
389+
if (action.objectName !== object) continue;
390+
for (const attr of ['label', 'successMessage']) {
391+
if (action[attr]) list.push(entry(`objects.${object}._actions.${action.name}.${attr}`));
392+
}
393+
}
394+
out.set(`actions of ${object}`, list);
395+
}
396+
return out;
397+
}
398+
399+
const surfaces = declaredEntries();
400+
401+
it('every named surface exists and declares strings (guards against a vacuous sweep)', () => {
402+
for (const [surface, list] of surfaces) {
403+
expect(list.length, `${surface} declares nothing — renamed or removed?`).toBeGreaterThan(0);
404+
}
405+
});
406+
407+
for (const [surface, list] of surfaces) {
408+
it(`${surface}: every declared string has a real zh-CN translation`, () => {
409+
const untranslated = list
410+
.filter((e) => typeof e.value !== 'string' || !e.value || /^[\x20-\x7e]+$/.test(e.value))
411+
.map((e) => e.path);
412+
expect(untranslated, 'missing from the zh-CN bundle, or echoed as ASCII').toEqual([]);
413+
});
414+
}
415+
});
416+
317417
/**
318418
* Static shadow of the seed contract after #3433: a seed write is a curated
319419
* end-state fact, so the platform EXEMPTS it from the object's `state_machine`

‎scripts/i18n-coverage-baseline.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"examples/app-crm/objectstack.config.ts": 101,
33
"examples/app-multi-package/objectstack.config.ts": 0,
4-
"examples/app-showcase/objectstack.config.ts": 414,
4+
"examples/app-showcase/objectstack.config.ts": 320,
55
"examples/app-todo/objectstack.config.ts": 106,
66
"packages/platform-objects/scripts/i18n-extract.config.ts": 0,
77
"packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts": 0,

0 commit comments

Comments
 (0)