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
10 changes: 10 additions & 0 deletions .changeset/30512-service-realtime-locales.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@objectstack/service-realtime': patch
---

i18n(service-realtime): the presence object's labels, help and option names ship in all eleven platform languages

Clause-②: no
Metadata-protocol: none

`sys_presence` now carries Traditional Chinese (`zh-Hant`), Arabic (`ar`), French (`fr`), Brazilian Portuguese (`pt-BR`), Russian (`ru`), German (`de`) and Korean (`ko`) beside the existing `en`, `zh-CN`, `ja-JP` and `es-ES`, so a reader in one of those languages sees the object's labels in their own language instead of English. Each new locale is registered under its platform-table tag and carries its source-hash companion, and the package's extract command lists the seven tags, so the existing `check:i18n` bundle check covers them. Part of #30512.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* strings were seeded from @objectstack/platform-objects.)
*
* os i18n extract packages/services/service-realtime/scripts/i18n-extract.config.ts \
* --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \
* --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar,fr,pt-BR,ru,de,ko --fill=default --objects-only --no-metadata-forms \
* --source-hashes \
* --out=packages/services/service-realtime/src/translations
*
Expand Down Expand Up @@ -42,6 +42,13 @@ import { enObjects } from '../src/translations/en.objects.generated.js';
import { zhCNObjects } from '../src/translations/zh-CN.objects.generated.js';
import { jaJPObjects } from '../src/translations/ja-JP.objects.generated.js';
import { esESObjects } from '../src/translations/es-ES.objects.generated.js';
import { zhHantObjects } from '../src/translations/zh-Hant.objects.generated.js';
import { arObjects } from '../src/translations/ar.objects.generated.js';
import { frObjects } from '../src/translations/fr.objects.generated.js';
import { ptBRObjects } from '../src/translations/pt-BR.objects.generated.js';
import { ruObjects } from '../src/translations/ru.objects.generated.js';
import { deObjects } from '../src/translations/de.objects.generated.js';
import { koObjects } from '../src/translations/ko.objects.generated.js';

/**
* The annotation is load-bearing, not decoration (#10868). `defineStack`
Expand All @@ -64,6 +71,13 @@ const config: ObjectStackDefinition = defineStack({
{ 'zh-CN': { objects: zhCNObjects } },
{ 'ja-JP': { objects: jaJPObjects } },
{ 'es-ES': { objects: esESObjects } },
{ 'zh-Hant': { objects: zhHantObjects } },
{ 'ar': { objects: arObjects } },
{ 'fr': { objects: frObjects } },
{ 'pt-BR': { objects: ptBRObjects } },
{ 'ru': { objects: ruObjects } },
{ 'de': { objects: deObjects } },
{ 'ko': { objects: koObjects } },
],
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Auto-generated by 'os i18n extract' for locale 'ar'.
* Edit translations in place; re-run extract (with --merge) to fill new gaps.
* Do not hand-edit the structure — only the leaf string values.
* Merge only fills gaps: correcting a source label/description does not
* push the correction into a leaf here that already holds a translation —
* a present-but-stale string is not a gap, so it is left as-is. Re-translate
* it by hand when its source changes; nothing here or in `os i18n check`
* tells a leaf a translator updated on purpose from one nobody has looked
* at since the source moved.
*/

import type { TranslationData } from '@objectstack/spec/system';

export const arObjects: NonNullable<TranslationData['objects']> = {
sys_presence: {
label: "الحضور",
pluralLabel: "حالات الحضور",
description: "تتبّع حضور المستخدمين ونشاطهم في الوقت الفعلي",
fields: {
id: {
label: "معرّف الحضور"
},
display_title: {
label: "العنوان",
help: "عنوان السجل: المستخدم وحالة حضوره (يُحسب عند القراءة)"
},
created_at: {
label: "تاريخ الإنشاء"
},
updated_at: {
label: "تاريخ التحديث"
},
user_id: {
label: "المستخدم"
},
session_id: {
label: "الجلسة"
},
status: {
label: "الحالة",
options: {
online: "متصل",
away: "بعيد",
busy: "مشغول",
offline: "غير متصل"
}
},
last_seen: {
label: "آخر ظهور"
},
current_location: {
label: "الموقع الحالي"
},
device: {
label: "الجهاز",
options: {
desktop: "سطح المكتب",
mobile: "الهاتف المحمول",
tablet: "الجهاز اللوحي",
other: "أخرى"
}
},
custom_status: {
label: "الحالة المخصّصة"
},
metadata: {
label: "البيانات الوصفية",
help: "بيانات وصفية عشوائية بصيغة JSON مرتبطة بحالة الحضور (تطابق PresenceStateSchema.metadata)."
}
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Auto-generated by 'os i18n extract' for locale 'ar'. 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 (commit 09b4f4e4e): a leaf whose digest no longer matches its source is stale and serves the source text instead.
*
* An entry exists only while the leaf IS such a copy. Re-translate the leaf in
* `<locale>.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 arGeneratedSourceHashes: Readonly<Record<string, string>> = {
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('objects translation bundle ownership (ADR-0029 D8)', () => {
strays,
`bundle carries objects this package's extract config does not own: ${strays.join(', ')} — ` +
'their curated translations would be silently deleted on the next `os i18n extract`. ' +
'Remove the dead block from the four locale bundles, or add the object to the extract config + this list.',
'Remove the dead block from every locale bundle, or add the object to the extract config + this list.',
).toEqual([]);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Auto-generated by 'os i18n extract' for locale 'de'.
* Edit translations in place; re-run extract (with --merge) to fill new gaps.
* Do not hand-edit the structure — only the leaf string values.
* Merge only fills gaps: correcting a source label/description does not
* push the correction into a leaf here that already holds a translation —
* a present-but-stale string is not a gap, so it is left as-is. Re-translate
* it by hand when its source changes; nothing here or in `os i18n check`
* tells a leaf a translator updated on purpose from one nobody has looked
* at since the source moved.
*/

import type { TranslationData } from '@objectstack/spec/system';

export const deObjects: NonNullable<TranslationData['objects']> = {
sys_presence: {
label: "Anwesenheit",
pluralLabel: "Anwesenheiten",
description: "Echtzeit-Verfolgung von Anwesenheit und Aktivität der Benutzer",
fields: {
id: {
label: "Anwesenheits-ID"
},
display_title: {
label: "Titel",
help: "Datensatztitel: der Benutzer und sein Anwesenheitsstatus (beim Lesen berechnet)"
},
created_at: {
label: "Erstellt am"
},
updated_at: {
label: "Aktualisiert am"
},
user_id: {
label: "Benutzer"
},
session_id: {
label: "Sitzung"
},
status: {
label: "Status",
options: {
online: "Online",
away: "Abwesend",
busy: "Beschäftigt",
offline: "Offline"
}
},
last_seen: {
label: "Zuletzt gesehen"
},
current_location: {
label: "Aktueller Ort"
},
device: {
label: "Gerät",
options: {
desktop: "Desktop",
mobile: "Mobilgerät",
tablet: "Tablet",
other: "Sonstiges"
}
},
custom_status: {
label: "Benutzerdefinierter Status"
},
metadata: {
label: "Metadaten",
help: "Beliebige JSON-Metadaten zum Anwesenheitsstatus (entspricht PresenceStateSchema.metadata)."
}
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Auto-generated by 'os i18n extract' for locale 'de'. 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 (commit 09b4f4e4e): a leaf whose digest no longer matches its source is stale and serves the source text instead.
*
* An entry exists only while the leaf IS such a copy. Re-translate the leaf in
* `<locale>.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 deGeneratedSourceHashes: Readonly<Record<string, string>> = {
"objects.sys_presence.fields.device.options.desktop": "cc1bf941a947048d",
"objects.sys_presence.fields.device.options.tablet": "7a8f4fab8baceb72",
"objects.sys_presence.fields.status.label": "64a420165247654b",
"objects.sys_presence.fields.status.options.offline": "322a7ca59145b5d8",
"objects.sys_presence.fields.status.options.online": "ac0927911fe6b7c1",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Auto-generated by 'os i18n extract' for locale 'fr'.
* Edit translations in place; re-run extract (with --merge) to fill new gaps.
* Do not hand-edit the structure — only the leaf string values.
* Merge only fills gaps: correcting a source label/description does not
* push the correction into a leaf here that already holds a translation —
* a present-but-stale string is not a gap, so it is left as-is. Re-translate
* it by hand when its source changes; nothing here or in `os i18n check`
* tells a leaf a translator updated on purpose from one nobody has looked
* at since the source moved.
*/

import type { TranslationData } from '@objectstack/spec/system';

export const frObjects: NonNullable<TranslationData['objects']> = {
sys_presence: {
label: "Présence",
pluralLabel: "Présences",
description: "Suivi en temps réel de la présence et de l’activité des utilisateurs",
fields: {
id: {
label: "ID de présence"
},
display_title: {
label: "Titre",
help: "Titre de l’enregistrement : l’utilisateur et son état de présence (calculé à la lecture)"
},
created_at: {
label: "Créé le"
},
updated_at: {
label: "Mis à jour le"
},
user_id: {
label: "Utilisateur"
},
session_id: {
label: "Session"
},
status: {
label: "Statut",
options: {
online: "En ligne",
away: "Absent",
busy: "Occupé",
offline: "Hors ligne"
}
},
last_seen: {
label: "Vu pour la dernière fois"
},
current_location: {
label: "Emplacement actuel"
},
device: {
label: "Appareil",
options: {
desktop: "Ordinateur",
mobile: "Mobile",
tablet: "Tablette",
other: "Autre"
}
},
custom_status: {
label: "Statut personnalisé"
},
metadata: {
label: "Métadonnées",
help: "Métadonnées JSON arbitraires associées à l’état de présence (correspond à PresenceStateSchema.metadata)."
}
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Auto-generated by 'os i18n extract' for locale 'fr'. 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 (commit 09b4f4e4e): a leaf whose digest no longer matches its source is stale and serves the source text instead.
*
* An entry exists only while the leaf IS such a copy. Re-translate the leaf in
* `<locale>.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 frGeneratedSourceHashes: Readonly<Record<string, string>> = {
"objects.sys_presence.fields.device.options.mobile": "b93f18e1a3526425",
"objects.sys_presence.fields.session_id.label": "2650b5c24a1853d7",
};
21 changes: 21 additions & 0 deletions packages/services/service-realtime/src/translations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,23 @@ import { enObjects } from './en.objects.generated.js';
import { zhCNObjects } from './zh-CN.objects.generated.js';
import { jaJPObjects } from './ja-JP.objects.generated.js';
import { esESObjects } from './es-ES.objects.generated.js';
import { zhHantObjects } from './zh-Hant.objects.generated.js';
import { arObjects } from './ar.objects.generated.js';
import { frObjects } from './fr.objects.generated.js';
import { ptBRObjects } from './pt-BR.objects.generated.js';
import { ruObjects } from './ru.objects.generated.js';
import { deObjects } from './de.objects.generated.js';
import { koObjects } from './ko.objects.generated.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 { zhHantGeneratedSourceHashes } from './zh-Hant.source-hashes.generated.js';
import { arGeneratedSourceHashes } from './ar.source-hashes.generated.js';
import { frGeneratedSourceHashes } from './fr.source-hashes.generated.js';
import { ptBRGeneratedSourceHashes } from './pt-BR.source-hashes.generated.js';
import { ruGeneratedSourceHashes } from './ru.source-hashes.generated.js';
import { deGeneratedSourceHashes } from './de.source-hashes.generated.js';
import { koGeneratedSourceHashes } from './ko.source-hashes.generated.js';

/**
* ## The provenance companions are READ here, not merely recorded
Expand Down Expand Up @@ -54,4 +68,11 @@ export const RealtimeTranslations: TranslationBundle = {
'zh-CN': withSourceFallback({ objects: zhCNObjects }, enSource, undefined, zhCNGeneratedSourceHashes),
'ja-JP': withSourceFallback({ objects: jaJPObjects }, enSource, undefined, jaJPGeneratedSourceHashes),
'es-ES': withSourceFallback({ objects: esESObjects }, enSource, undefined, esESGeneratedSourceHashes),
'zh-Hant': withSourceFallback({ objects: zhHantObjects }, enSource, undefined, zhHantGeneratedSourceHashes),
'ar': withSourceFallback({ objects: arObjects }, enSource, undefined, arGeneratedSourceHashes),
'fr': withSourceFallback({ objects: frObjects }, enSource, undefined, frGeneratedSourceHashes),
'pt-BR': withSourceFallback({ objects: ptBRObjects }, enSource, undefined, ptBRGeneratedSourceHashes),
'ru': withSourceFallback({ objects: ruObjects }, enSource, undefined, ruGeneratedSourceHashes),
'de': withSourceFallback({ objects: deObjects }, enSource, undefined, deGeneratedSourceHashes),
'ko': withSourceFallback({ objects: koObjects }, enSource, undefined, koGeneratedSourceHashes),
};
Loading
Loading