Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/dead-i18n-namespaces-workflow-publicform-demo.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
'@object-ui/i18n': patch
---

Delete two dead i18n namespaces — `workflow.*` (58 keys) and `publicForm.demo.*` (36 keys) — from all ten locale packs.

940 translated strings (94 keys x 10 packs) with **no reader anywhere in the
repo**. Measured with `node scripts/check-i18n-dead-keys.mjs`, the reverse sweep
from objectui#4658: it subtracts the referenced key set (the AST walk that
`check-i18n-call-site-keys.mjs` already uses, plus plural suffixes, plus
`returnObjects` branches, plus every dynamic template head) from the pack key
set, then re-checks each survivor with a fixed-string grep over the whole repo.
`workflow.*` scored 54/58 CONFIRMED with the other 4 appearing only in two i18n
test fixtures — mentions, not consumers; `publicForm.demo.*` scored 36/36 with
zero textual footprint anywhere outside the packs.

`workflow.*` is a complete BPMN/workflow-designer vocabulary (`userTask`,
`serviceTask`, `parallelGateway`, `boundaryEvent`, `importBpmn`/`exportBpmn`,
`undo`/`redo`, …). Its one plausible consumer,
`packages/plugin-designer/src/ProcessDesigner.tsx`, hardcodes English
(`{ label: 'User Task', value: 'user-task' }`) and imports no translation hook
at all, while six sibling components in the same package do use one — the
vocabulary was never wired up. `publicForm.demo.*` is demo content (contact and
support form titles, field labels, industry/issue-type/priority options) for a
public-form showcase page that does not exist in this repo.

No behaviour changes: a key with no reader cannot be read. The `publicForm.*`
parent namespace and every other namespace are untouched.
31 changes: 22 additions & 9 deletions packages/i18n/src/__tests__/ellipsis-glyph-3878.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,13 +88,26 @@ const PACKS: Record<Lang, Array<[string, string]>> = Object.fromEntries(
) as Record<Lang, Array<[string, string]>>;

/**
* The 35 keys this pass converged: every key that held an ASCII ellipsis in at
* least one of the ten packs on the commit before the fix. Pinned by name so the
* scan below cannot go green by the keys quietly disappearing, and so the census
* is readable next to the rule rather than only in the PR that made it.
* The keys this pass converged that the packs still define: every key that held
* an ASCII ellipsis in at least one of the ten packs on the commit before the
* fix, minus the ones later deleted outright. Pinned by name so the scan below
* cannot go green by the keys quietly disappearing, and so the census is
* readable next to the rule rather than only in the PR that made it.
*
* 312 pack values changed in all: 34 in `en` (33 trailing + the one mid-sentence
* `collaboration.commentPlaceholder`) and 278 across the nine.
* `collaboration.commentPlaceholder`) and 278 across the nine. That historical
* count does not move when a converged key is later retired — only this pin does.
*
* The census was 35 keys at landing and is 33 today: `workflow.fromPlaceholder`
* and `workflow.toPlaceholder` went with the whole `workflow.*` namespace in
* objectui#4742, which deleted it as dead (no call site, no textual reference
* anywhere outside the packs, and its plausible consumer
* `packages/plugin-designer/src/ProcessDesigner.tsx` imports no translation hook
* at all). Their rows are deleted rather than swapped for live keys on purpose:
* this list is a census of which keys that pass actually touched, so naming a key
* it never converged would make the record say something untrue. A key that
* disappears WITHOUT this list being edited still fails, which is the guard
* working — deleting a row is a deliberate act, and this note is its receipt.
*/
const CONVERGED_KEYS = [
'appManagement.searchPlaceholder',
Expand DownExpand Up@@ -130,8 +143,6 @@ const CONVERGED_KEYS = [
'table.search',
'topbar.connection.connecting',
'topbar.connection.reconnecting',
'workflow.fromPlaceholder',
'workflow.toPlaceholder',
] as const;

describe('objectui#3878 — the ten packs spell the ellipsis U+2026 and only U+2026', () => {
Expand DownExpand Up@@ -159,11 +170,13 @@ describe('objectui#3878 — the ten packs spell the ellipsis U+2026 and only U+2
).toEqual([]);
});

it('keeps an ellipsis in every pack for each of the 35 keys this pass converged', () => {
it('keeps an ellipsis in every pack for each converged key the packs still define', () => {
// The complement of the rule above. Without it, deleting the ellipsis
// outright would pass the ASCII scan — green because nothing is produced,
// which is not the same fact as green because the copy is right.
expect(CONVERGED_KEYS).toHaveLength(35);
// 33, not the 35 this pass converged: see the census note on CONVERGED_KEYS
// for the two `workflow.*` rows objectui#4742 retired with the namespace.
expect(CONVERGED_KEYS).toHaveLength(33);

const missing: string[] = [];
for (const lang of LANGS) {
Expand Down
16 changes: 13 additions & 3 deletions packages/i18n/src/__tests__/untranslated-identity-4376.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,10 +52,15 @@
*
* ## The allowlist is the cost, and it is keyed by KEY, not by key+lang
*
* 22 keys below are byte-identical to `en` in at least one non-Latin pack and
* The keys below are byte-identical to `en` in at least one non-Latin pack and
* are RIGHT that way — proper nouns, protocol and format names, example
* addresses, generated filenames, and pure interpolation. Each carries a
* one-line reason so the next reader can re-judge it instead of trusting it.
* (This sentence used to state the entry count. It said 22 while the list held
* 23, because nothing asserts the number and additions did not have to touch it;
* the count is deleted rather than corrected so the prose cannot drift again —
* the list itself is the count, and `no dead entries` below is what keeps it
* honest.)
*
* Key-level rather than key+lang is a deliberate trade: it is looser (an entry
* excuses the identity in all five packs, not just the one that has it today)
Expand DownExpand Up@@ -88,8 +93,13 @@ const LATIN_RUN = /[A-Za-z]{4,}/;
const LEGITIMATE_IDENTITIES: Record<string, string> = {
// Proper nouns and product/protocol/format names — the same token in every language.
'layout.metadata.jsonBadge': 'JSON — the format name, not a word.',
'workflow.webhook': 'Webhook — the protocol term these packs use untranslated.',
'workflow.webhookEvent': 'Webhook — same term, event-typed.',
// `workflow.webhook` and `workflow.webhookEvent` sat here until objectui#4742
// deleted the whole `workflow.*` namespace as dead. Their entries went with
// them rather than being re-pointed at another key: an entry is a fact about
// one key's value, so it cannot outlive the key or be transferred to a
// different one. Both assertions in `keeps the allowlist honest` would have
// failed on a leftover entry — dead (no identity left to excuse) and unknown
// (no such key in `en`) — which is that guard doing its job.
'publicForm.poweredBy': 'Powered by ObjectStack — product attribution; ja keeps the English wordmark line.',
'connectAgent.apiKey.badge': 'headless — the literal mode name the CLI and API use.',
'marketplace.pricing.freemium': 'Freemium — the pricing-tier term of art; ru keeps the loanword.',
Expand Down
106 changes: 0 additions & 106 deletions packages/i18n/src/locales/ar.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -1034,66 +1034,6 @@ const ar = {
invalidCoordinates: "{{count}} سجل بإحداثيات مفقودة أو غير صالحة مستبعد من الخريطة.",
invalidCoordinatesPlural: "{{count}} سجلات بإحداثيات مفقودة أو غير صالحة مستبعدة من الخريطة.",
},
workflow: {
draft: "مسودة",
active: "نشط",
paused: "متوقف مؤقتاً",
completed: "مكتمل",
cancelled: "ملغي",
save: "حفظ",
publish: "نشر",
addNode: "إضافة عقدة",
workflowFlow: "سير العمل",
properties: "الخصائص",
connections: "الاتصالات",
quickConnect: "اتصال سريع",
fromPlaceholder: "من…",
toPlaceholder: "إلى…",
connect: "ربط",
label: "التسمية",
type: "النوع",
description: "الوصف",
assignee: "المسؤول",
assigneeType: "نوع المسؤول",
user: "مستخدم",
role: "دور",
group: "مجموعة",
expression: "تعبير",
timeoutMinutes: "المهلة (دقائق)",
selectNodeToEdit: "حدد عقدة لتعديل خصائصها",
workflowTitle: "عنوان سير العمل",
newWorkflow: "سير عمل جديد",
outConnections: "{{count}} مخرج",
start: "بداية",
end: "نهاية",
task: "مهمة",
userTask: "مهمة مستخدم",
serviceTask: "مهمة خدمة",
scriptTask: "مهمة نصية",
approval: "موافقة",
condition: "شرط",
parallelGateway: "بوابة متوازية",
joinGateway: "بوابة دمج",
boundaryEvent: "حدث حدودي",
delay: "تأخير",
notification: "إشعار",
webhook: "Webhook",
allowConcurrency: "السماح (تنفيذ متزامن)",
forbidConcurrency: "منع (تخطي الجديد)",
replaceConcurrency: "استبدال (إلغاء الحالي)",
queueConcurrency: "طابور (تنفيذ بعد الحالي)",
conditionEvent: "شرط",
manualEvent: "يدوي",
webhookEvent: "Webhook",
timerEvent: "مؤقت",
signalEvent: "إشارة",
versionHistory: "تاريخ الإصدارات",
importBpmn: "استيراد BPMN",
exportBpmn: "تصدير BPMN",
undo: "تراجع",
redo: "إعادة",
resetZoom: "إعادة تعيين التكبير",
},
dashboard: {
noRows: "لا توجد صفوف",
loading: "جارٍ التحميل…",
Expand DownExpand Up@@ -2785,52 +2725,6 @@ const ar = {
consentRequired: "يرجى قبول سياسة الخصوصية للمتابعة.",
rateLimited: "خذ لحظة لمراجعة إجاباتك قبل الإرسال.",
redirectBlocked: "تم قبول الإرسال، لكن رابط إعادة التوجيه تم حظره لأسباب أمنية.",
demo: {
contactTitle: "تواصل معنا",
contactDescription: "أخبرنا عن مشروعك وسيتواصل معك أحد ممثلي المبيعات خلال يوم عمل.",
supportTitle: "إرسال طلب دعم",
supportDescription: "صف لنا المشكلة وسيرد فريقنا خلال يوم عمل.",
thankYouSalesTitle: "شكراً — لقد استلمنا رسالتك!",
thankYouSalesMessage: "سيتواصل معك أحد ممثلي المبيعات خلال يوم عمل.",
thankYouSupportTitle: "تم — طلبك في قائمة الانتظار.",
thankYouSupportMessage: "سيتابع معك مهندس الدعم قريباً. احفظ هذه الصفحة إذا كان لديك لقطات شاشة إضافية.",
field: {
firstName: "الاسم الأول",
lastName: "اسم العائلة",
email: "البريد الإلكتروني للعمل",
phone: "الهاتف",
jobTitle: "المسمى الوظيفي",
company: "الشركة",
website: "الموقع الإلكتروني",
industry: "القطاع",
companySize: "حجم الشركة",
howCanWeHelp: "كيف يمكننا مساعدتك؟",
subject: "الموضوع",
description: "الوصف",
issueType: "نوع المشكلة",
priority: "الأولوية",
},
industry: {
technology: "التكنولوجيا",
software: "البرمجيات / SaaS",
finance: "المالية",
healthcare: "الرعاية الصحية",
retail: "التجزئة",
other: "أخرى",
},
issueType: {
question: "سؤال",
problem: "مشكلة",
bug: "خطأ برمجي",
feature_request: "طلب ميزة",
},
priority: {
low: "منخفضة",
medium: "متوسطة",
high: "عالية",
critical: "حرجة",
},
},
},
connectAgent: {
disabled: {
Expand Down
106 changes: 0 additions & 106 deletions packages/i18n/src/locales/de.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -1027,66 +1027,6 @@ const de = {
invalidCoordinates: "{{count}} Datensatz mit fehlenden oder ungültigen Koordinaten von der Karte ausgeschlossen.",
invalidCoordinatesPlural: "{{count}} Datensätze mit fehlenden oder ungültigen Koordinaten von der Karte ausgeschlossen.",
},
workflow: {
draft: "Entwurf",
active: "Aktiv",
paused: "Pausiert",
completed: "Abgeschlossen",
cancelled: "Abgebrochen",
save: "Speichern",
publish: "Veröffentlichen",
addNode: "Knoten hinzufügen",
workflowFlow: "Workflow-Ablauf",
properties: "Eigenschaften",
connections: "Verbindungen",
quickConnect: "Schnellverbindung",
fromPlaceholder: "Von…",
toPlaceholder: "Nach…",
connect: "Verbinden",
label: "Bezeichnung",
type: "Typ",
description: "Beschreibung",
assignee: "Zuständiger",
assigneeType: "Zuständigkeitstyp",
user: "Benutzer",
role: "Rolle",
group: "Gruppe",
expression: "Ausdruck",
timeoutMinutes: "Zeitlimit (Minuten)",
selectNodeToEdit: "Knoten auswählen, um Eigenschaften zu bearbeiten",
workflowTitle: "Workflow-Titel",
newWorkflow: "Neuer Workflow",
outConnections: "{{count}} Ausgänge",
start: "Start",
end: "Ende",
task: "Aufgabe",
userTask: "Benutzeraufgabe",
serviceTask: "Serviceaufgabe",
scriptTask: "Skriptaufgabe",
approval: "Genehmigung",
condition: "Bedingung",
parallelGateway: "Paralleles Gateway",
joinGateway: "Zusammenführungs-Gateway",
boundaryEvent: "Grenzereignis",
delay: "Verzögerung",
notification: "Benachrichtigung",
webhook: "Webhook",
allowConcurrency: "Erlauben (gleichzeitig ausführen)",
forbidConcurrency: "Verbieten (neue überspringen)",
replaceConcurrency: "Ersetzen (bestehende abbrechen)",
queueConcurrency: "Warteschlange (nach aktuellem ausführen)",
conditionEvent: "Bedingung",
manualEvent: "Manuell",
webhookEvent: "Webhook",
timerEvent: "Timer",
signalEvent: "Signal",
versionHistory: "Versionshistorie",
importBpmn: "BPMN importieren",
exportBpmn: "BPMN exportieren",
undo: "Rückgängig",
redo: "Wiederholen",
resetZoom: "Zoom zurücksetzen",
},
dashboard: {
noRows: "Keine Zeilen",
loading: "Wird geladen…",
Expand DownExpand Up@@ -2778,52 +2718,6 @@ const de = {
consentRequired: "Bitte akzeptieren Sie die Datenschutzrichtlinie, um fortzufahren.",
rateLimited: "Bitte nehmen Sie sich einen Moment, um Ihre Antworten zu überprüfen, bevor Sie absenden.",
redirectBlocked: "Einreichung akzeptiert, aber die Weiterleitungs-URL wurde aus Sicherheitsgründen blockiert.",
demo: {
contactTitle: "Kontakt aufnehmen",
contactDescription: "Erzählen Sie uns von Ihrem Projekt, und ein Vertriebsmitarbeiter wird sich innerhalb eines Werktages bei Ihnen melden.",
supportTitle: "Support-Anfrage einreichen",
supportDescription: "Schildern Sie uns das Problem — unser Team antwortet innerhalb eines Werktages.",
thankYouSalesTitle: "Danke — wir haben Ihre Nachricht erhalten!",
thankYouSalesMessage: "Ein Vertriebsmitarbeiter wird sich innerhalb eines Werktages bei Ihnen melden.",
thankYouSupportTitle: "Alles klar — Ihre Anfrage ist in der Warteschlange.",
thankYouSupportMessage: "Ein Support-Ingenieur wird sich in Kürze melden. Speichern Sie diese Seite, falls Sie weitere Screenshots hinzufügen möchten.",
field: {
firstName: "Vorname",
lastName: "Nachname",
email: "Geschäfts-E-Mail",
phone: "Telefon",
jobTitle: "Berufsbezeichnung",
company: "Unternehmen",
website: "Website",
industry: "Branche",
companySize: "Unternehmensgröße",
howCanWeHelp: "Wie können wir helfen?",
subject: "Betreff",
description: "Beschreibung",
issueType: "Problemart",
priority: "Priorität",
},
industry: {
technology: "Technologie",
software: "Software / SaaS",
finance: "Finanzen",
healthcare: "Gesundheitswesen",
retail: "Einzelhandel",
other: "Sonstiges",
},
issueType: {
question: "Frage",
problem: "Problem",
bug: "Fehler",
feature_request: "Funktionsanfrage",
},
priority: {
low: "Niedrig",
medium: "Mittel",
high: "Hoch",
critical: "Kritisch",
},
},
},
connectAgent: {
disabled: {
Expand Down
Loading
Loading