diff --git a/.changeset/30512-plugin-sharing-locales.md b/.changeset/30512-plugin-sharing-locales.md new file mode 100644 index 00000000000..528e5232fa2 --- /dev/null +++ b/.changeset/30512-plugin-sharing-locales.md @@ -0,0 +1,10 @@ +--- +'@objectstack/plugin-sharing': patch +--- + +i18n(plugin-sharing): the sharing and queue objects ship in all eleven platform languages + +Clause-②: no +Metadata-protocol: none + +The sharing objects (`sys_record_share`, `sys_sharing_rule`, `sys_share_link`) and the queue objects (`sys_queue`, `sys_queue_member`) now carry `zh-Hant`, `ar`, `fr`, `pt-BR`, `ru`, `de` and `ko` beside the existing `en`, `zh-CN`, `ja-JP` and `es-ES`, so an administrator in one of those languages reads the sharing and queue screens in their own language instead of English. Each recipient option reads the same on every object within a locale. The queue record page (`sys_queue_detail`) carries its title in the seven languages too, the same word as the queue object's own label. Each new locale is registered under its platform-table tag with its source-hash companion, and the package's extract command lists the tags, so the existing `check:i18n` bundle check covers them. Package 8 of #30512. diff --git a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts index 4a98208ce67..7d1d3de8465 100644 --- a/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts +++ b/packages/plugins/plugin-sharing/scripts/i18n-extract.config.ts @@ -8,7 +8,7 @@ * strings were seeded from @objectstack/platform-objects.) * * os i18n extract packages/plugins/plugin-sharing/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/plugins/plugin-sharing/src/translations * @@ -48,6 +48,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'; import { SysQueueDetailPage } from '../src/sys-queue.page.js'; import { sharingPageTranslations } from '../src/translations/pages.js'; @@ -73,6 +80,13 @@ const config: ObjectStackDefinition = defineStack({ { 'zh-CN': { objects: zhCNObjects, pages: sharingPageTranslations['zh-CN'] } }, { 'ja-JP': { objects: jaJPObjects, pages: sharingPageTranslations['ja-JP'] } }, { 'es-ES': { objects: esESObjects, pages: sharingPageTranslations['es-ES'] } }, + { 'zh-Hant': { objects: zhHantObjects, pages: sharingPageTranslations['zh-Hant'] } }, + { 'ar': { objects: arObjects, pages: sharingPageTranslations['ar'] } }, + { 'fr': { objects: frObjects, pages: sharingPageTranslations['fr'] } }, + { 'pt-BR': { objects: ptBRObjects, pages: sharingPageTranslations['pt-BR'] } }, + { 'ru': { objects: ruObjects, pages: sharingPageTranslations['ru'] } }, + { 'de': { objects: deObjects, pages: sharingPageTranslations['de'] } }, + { 'ko': { objects: koObjects, pages: sharingPageTranslations['ko'] } }, ], }); diff --git a/packages/plugins/plugin-sharing/src/translations/ar.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/ar.objects.generated.ts new file mode 100644 index 00000000000..1e2d855a43d --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/ar.objects.generated.ts @@ -0,0 +1,347 @@ +// 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 = { + sys_record_share: { + label: "مشاركة السجل", + pluralLabel: "مشاركات السجلات", + description: "سجلات مُشارَكة مع غير مالكها، صف واحد لكل منح.", + fields: { + id: { + label: "معرّف المشاركة" + }, + object_name: { + label: "الكائن", + help: "الاسم المختصر لكائن السجل المُشارَك" + }, + record_id: { + label: "السجل", + help: "المفتاح الأساسي للسجل المُشارَك ضمن object_name" + }, + recipient_type: { + label: "نوع المستلِم", + help: "نوع الجهة التي تحمل المنح", + options: { + user: "المستخدم", + group: "المجموعة", + position: "المنصب", + unit_and_subordinates: "وحدة الأعمال والوحدات التابعة لها" + } + }, + recipient_id: { + label: "المستلِم", + help: "معرّف المستخدم أو المجموعة أو المنصب الذي يحصل على الوصول" + }, + access_level: { + label: "مستوى الوصول", + help: "ما يمكن للمستلِم فعله — القراءة، أو القراءة والتحرير", + options: { + read: "قراءة", + edit: "قراءة وتحرير" + } + }, + source: { + label: "المصدر", + help: "سبب وجود هذا المنح — يستخدمه مقيِّم القواعد للمطابقة", + options: { + manual: "يدوي", + rule: "قاعدة", + team: "الفريق", + inherited: "موروث" + } + }, + source_id: { + label: "معرّف المصدر", + help: "اسم القاعدة أو معرّف الفريق عندما يكون source != manual" + }, + granted_by: { + label: "مَن منحه", + help: "المستخدم الذي أنشأ المنح (يدويًا فقط)" + }, + reason: { + label: "السبب", + help: "شرح نصي حر اختياري يُعرض للمستلِم" + }, + created_at: { + label: "تاريخ الإنشاء" + }, + updated_at: { + label: "تاريخ التحديث" + } + }, + _views: { + all_shares: { + label: "الكل" + }, + granted_to_me: { + label: "الممنوحة لي" + }, + granted_by_me: { + label: "التي منحتُها" + }, + by_object: { + label: "حسب الكائن" + }, + manual_grants: { + label: "منح يدوية" + }, + rule_grants: { + label: "منح بالقواعد" + } + } + }, + sys_sharing_rule: { + label: "قاعدة المشاركة", + pluralLabel: "قواعد المشاركة", + description: "يمنح مجموعة من الأشخاص الوصول إلى السجلات المطابقة لشرط.", + fields: { + id: { + label: "معرّف القاعدة" + }, + organization_id: { + label: "المؤسسة", + help: "المؤسسة التي تنتمي إليها هذه القاعدة." + }, + name: { + label: "الاسم", + help: "يعرّف القاعدة. أحرف لاتينية صغيرة وأرقام وشرطات سفلية فقط." + }, + label: { + label: "التسمية المعروضة" + }, + description: { + label: "الوصف" + }, + object_name: { + label: "الكائن", + help: "الكائن الذي تشارك هذه القاعدة سجلاته." + }, + criteria_json: { + label: "المعايير", + help: "أي السجلات تُشارَك. مطلوب — يجب أن تضيّق القاعدة السجلات التي تشاركها، لذا لا يوجد إعداد «مشاركة كل السجلات»." + }, + recipient_type: { + label: "نوع المستلِم", + help: "مَن يحصل على الوصول. اختيار فريق أو وحدة أعمال أو منصب أو قائمة انتظار يمنح الوصول لكل من فيها. ويشمل «وحدة الأعمال والوحدات التابعة لها» أيضًا كل وحدة أدنى من الوحدة المختارة. أما «الحقل» فيشارك كل سجل مع المستخدم أو المستخدمين المحفوظين في أحد حقوله — اذكر ذلك الحقل بوصفه المستلِم.", + options: { + user: "المستخدم", + team: "الفريق", + business_unit: "وحدة الأعمال", + position: "المنصب", + unit_and_subordinates: "وحدة الأعمال والوحدات التابعة لها", + queue: "قائمة الانتظار", + field: "الحقل" + } + }, + recipient_id: { + label: "المستلِم", + help: "المستخدم أو الفريق أو وحدة الأعمال أو المنصب أو قائمة الانتظار المحدَّد الذي يحصل على الوصول — أو، لنوع المستلِم «الحقل»، اسم حقل السجل الذي يحفظ المستخدم أو المستخدمين المُشارَك معهم." + }, + access_level: { + label: "مستوى الوصول", + help: "ما يمكن للمستلمين فعله بالسجلات المطابقة — قراءتها، أو قراءتها وتحريرها.", + options: { + read: "قراءة", + edit: "قراءة وتحرير" + } + }, + active: { + label: "نشِط", + help: "أوقفه لسحب الوصول الذي منحته هذه القاعدة دون حذفها." + }, + managed_by: { + label: "مصدر الإدارة", + help: "مصدر هذه القاعدة: مدمجة في المنصة، أو مثبّتة مع تطبيق، أو أُنشئت هنا في الإعداد.", + options: { + platform: "المنصة", + package: "الحزمة", + admin: "المسؤول" + } + }, + customized: { + label: "مخصَّصة", + help: "يُضبط عند تحريرك قاعدة جاءت مع تطبيق، فتُحفظ تغييراتك عند تحديث التطبيق." + }, + created_at: { + label: "تاريخ الإنشاء" + }, + updated_at: { + label: "تاريخ التحديث" + } + }, + _views: { + active: { + label: "نشِط" + }, + inactive: { + label: "غير نشِط" + }, + by_object: { + label: "حسب الكائن" + }, + all_rules: { + label: "الكل" + } + } + }, + sys_share_link: { + label: "رابط المشاركة", + pluralLabel: "روابط المشاركة", + description: "رمز قدرة مُبهم يمنح الوصول إلى سجل واحد. مشاركة عبر رابط عام على طريقة Notion/Figma.", + fields: { + id: { + label: "معرّف الرابط" + }, + token: { + label: "الرمز", + help: "رمز عشوائي مُبهم آمن للاستخدام في URL (≥ 22 حرفًا). السر الوحيد في هذا الصف." + }, + object_name: { + label: "الكائن", + help: "الاسم المختصر لكائن السجل المُشارَك (مثل ai_conversation و contracts_contract)" + }, + record_id: { + label: "السجل", + help: "المفتاح الأساسي للسجل المُشارَك ضمن object_name" + }, + permission: { + label: "الإذن", + help: "ما يمكن لحامل الرابط فعله بالسجل", + options: { + view: "عرض", + comment: "تعليق", + edit: "تحرير" + } + }, + audience: { + label: "الجمهور", + help: "طبقة تقييد تُطبَّق فوق التحقق من الرمز", + options: { + public: "عام (قابل للفهرسة)", + link_only: "أي شخص لديه الرابط", + signed_in: "المستخدمون المسجّلون الدخول", + email: "عناوين بريد محددة" + } + }, + expires_at: { + label: "تاريخ الانتهاء", + help: "عند ضبطه، يعيد resolveToken القيمة null بعد هذا الطابع الزمني" + }, + email_allowlist: { + label: "قائمة البريد المسموح", + help: "عناوين بأحرف صغيرة يُتحقق منها عندما audience=email" + }, + password_hash: { + label: "تجزئة كلمة المرور", + help: "تجزئة Argon2/bcrypt. عند ضبطها، تطلب الواجهة كلمة مرور قبل العرض." + }, + redact_fields: { + label: "الحقول المحجوبة لكل رابط", + help: "حقول إضافية تُزال من الاستجابة، فوق المجموعة الافتراضية للكائن" + }, + label: { + label: "التسمية", + help: "نص حر يظهر في نافذة المشاركة (مثل «عقد ACME Q3»)" + }, + revoked_at: { + label: "تاريخ الإلغاء", + help: "عند ضبطه، يُعطَّل الرابط نهائيًا" + }, + created_by: { + label: "المُنشئ", + help: "مُصدِر الرابط" + }, + created_at: { + label: "تاريخ الإنشاء" + }, + last_used_at: { + label: "آخر استخدام", + help: "يختمه resolveToken؛ وتستخدمه لوحة المعلومات لإبراز الروابط النشطة" + }, + use_count: { + label: "عدد مرات الاستخدام", + help: "يزيده resolveToken عند كل تحليل ناجح" + } + }, + _views: { + active_links: { + label: "نشطة" + }, + by_me: { + label: "أنشأتُها" + }, + revoked: { + label: "ملغاة" + }, + all_links: { + label: "الكل" + } + } + }, + sys_queue: { + label: "قائمة الانتظار", + pluralLabel: "قوائم الانتظار", + description: "مجموعة مسمّاة من الأشخاص تنتظر فيها السجلات غير المسندة حتى يتولاها أحدهم.", + fields: { + name: { + label: "الاسم", + help: "يعرّف قائمة الانتظار أينما استُخدمت — في السجلات وقواعد الإسناد والموافقات وقواعد المشاركة. أحرف لاتينية صغيرة وأرقام وشرطات سفلية فقط." + }, + label: { + label: "الاسم المعروض" + }, + description: { + label: "الوصف" + } + }, + _views: { + all_queues: { + label: "الكل" + } + } + }, + sys_queue_member: { + label: "عضو قائمة الانتظار", + pluralLabel: "أعضاء قائمة الانتظار", + description: "مَن ينتمي إلى قائمة الانتظار: مستخدم أو فريق أو منصب أو وحدة أعمال.", + fields: { + queue: { + label: "قائمة الانتظار", + help: "اسم قائمة الانتظار التي ينتمي إليها هذا العضو." + }, + recipient_type: { + label: "نوع العضو", + help: "نوع هذا العضو. الفريق أو المنصب أو وحدة الأعمال يجعل كل من فيه عضوًا. ويشمل «وحدة الأعمال والوحدات التابعة لها» أيضًا كل وحدة أدنى من الوحدة المختارة.", + options: { + user: "المستخدم", + team: "الفريق", + position: "المنصب", + business_unit: "وحدة الأعمال", + unit_and_subordinates: "وحدة الأعمال والوحدات التابعة لها" + } + }, + recipient_id: { + label: "العضو", + help: "المستخدم أو الفريق أو المنصب أو وحدة الأعمال المحدَّد." + } + }, + _views: { + by_queue: { + label: "حسب قائمة الانتظار" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/ar.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/ar.source-hashes.generated.ts new file mode 100644 index 00000000000..728daa2a100 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/ar.source-hashes.generated.ts @@ -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 + * `.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> = { +}; diff --git a/packages/plugins/plugin-sharing/src/translations/de.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/de.objects.generated.ts new file mode 100644 index 00000000000..30965e449a5 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/de.objects.generated.ts @@ -0,0 +1,347 @@ +// 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 = { + sys_record_share: { + label: "Datensatzfreigabe", + pluralLabel: "Datensatzfreigaben", + description: "Über den Eigentümer hinaus geteilte Datensätze, eine Zeile pro Gewährung.", + fields: { + id: { + label: "Freigabe-ID" + }, + object_name: { + label: "Objekt", + help: "Kurzer Objektname des geteilten Datensatzes" + }, + record_id: { + label: "Datensatz", + help: "Primärschlüssel des geteilten Datensatzes in object_name" + }, + recipient_type: { + label: "Empfängertyp", + help: "Art des Prinzipals, der die Gewährung hält", + options: { + user: "Benutzer", + group: "Gruppe", + position: "Position", + unit_and_subordinates: "Geschäftsbereich und untergeordnete" + } + }, + recipient_id: { + label: "Empfänger", + help: "ID des Benutzers, der Gruppe oder der Position, die Zugriff erhält" + }, + access_level: { + label: "Zugriffsebene", + help: "Was der Empfänger tun kann — lesen oder lesen und bearbeiten", + options: { + read: "Lesen", + edit: "Lesen und Bearbeiten" + } + }, + source: { + label: "Quelle", + help: "Warum diese Gewährung besteht — vom Regelauswerter zum Abgleich genutzt", + options: { + manual: "Manuell", + rule: "Regel", + team: "Team", + inherited: "Geerbt" + } + }, + source_id: { + label: "Quell-ID", + help: "Regelname / Team-ID, wenn source != manual" + }, + granted_by: { + label: "Gewährt von", + help: "Benutzer, der die Gewährung erstellt hat (nur manuell)" + }, + reason: { + label: "Grund", + help: "Optionale Freitext-Erklärung, die dem Empfänger angezeigt wird" + }, + created_at: { + label: "Erstellt am" + }, + updated_at: { + label: "Aktualisiert am" + } + }, + _views: { + all_shares: { + label: "Alle" + }, + granted_to_me: { + label: "Mir gewährt" + }, + granted_by_me: { + label: "Von mir gewährt" + }, + by_object: { + label: "Nach Objekt" + }, + manual_grants: { + label: "Manuelle Gewährungen" + }, + rule_grants: { + label: "Regelgewährungen" + } + } + }, + sys_sharing_rule: { + label: "Freigaberegel", + pluralLabel: "Freigaberegeln", + description: "Gibt einer Gruppe von Personen Zugriff auf die Datensätze, die eine Bedingung erfüllen.", + fields: { + id: { + label: "Regel-ID" + }, + organization_id: { + label: "Organisation", + help: "Die Organisation, zu der diese Regel gehört." + }, + name: { + label: "Name", + help: "Kennzeichnet die Regel. Nur Kleinbuchstaben, Ziffern und Unterstriche." + }, + label: { + label: "Anzeigebezeichnung" + }, + description: { + label: "Beschreibung" + }, + object_name: { + label: "Objekt", + help: "Das Objekt, dessen Datensätze diese Regel teilt." + }, + criteria_json: { + label: "Kriterien", + help: "Welche Datensätze geteilt werden. Pflicht — eine Regel muss die geteilten Datensätze eingrenzen, daher gibt es keine Einstellung „alle Datensätze teilen“." + }, + recipient_type: { + label: "Empfängertyp", + help: "Wer Zugriff erhält. Wählen Sie ein Team, einen Geschäftsbereich, eine Position oder eine Warteschlange, erhält jede Person darin Zugriff. „Geschäftsbereich und untergeordnete“ umfasst zusätzlich jeden Bereich unter dem gewählten. „Feld“ teilt jeden Datensatz mit dem oder den Benutzern, die in einem seiner eigenen Felder stehen — geben Sie dieses Feld als Empfänger an.", + options: { + user: "Benutzer", + team: "Team", + business_unit: "Geschäftsbereich", + position: "Position", + unit_and_subordinates: "Geschäftsbereich und untergeordnete", + queue: "Warteschlange", + field: "Feld" + } + }, + recipient_id: { + label: "Empfänger", + help: "Der konkrete Benutzer, das Team, der Geschäftsbereich, die Position oder die Warteschlange, die Zugriff erhält — oder beim Empfängertyp „Feld“ der Name des Datensatzfelds, das den oder die Benutzer zum Teilen enthält." + }, + access_level: { + label: "Zugriffsebene", + help: "Was die Empfänger mit den passenden Datensätzen tun dürfen — sie lesen oder lesen und bearbeiten.", + options: { + read: "Lesen", + edit: "Lesen und Bearbeiten" + } + }, + active: { + label: "Aktiv", + help: "Ausschalten, um den von dieser Regel gewährten Zugriff zu entziehen, ohne die Regel zu löschen." + }, + managed_by: { + label: "Verwaltet von", + help: "Woher diese Regel stammt: in die Plattform integriert, mit einer App installiert oder hier im Setup erstellt.", + options: { + platform: "Plattform", + package: "Paket", + admin: "Administrator" + } + }, + customized: { + label: "Angepasst", + help: "Wird gesetzt, sobald Sie eine mit einer App gelieferte Regel bearbeiten, damit Ihre Änderungen bei einem App-Update erhalten bleiben." + }, + created_at: { + label: "Erstellt am" + }, + updated_at: { + label: "Aktualisiert am" + } + }, + _views: { + active: { + label: "Aktiv" + }, + inactive: { + label: "Inaktiv" + }, + by_object: { + label: "Nach Objekt" + }, + all_rules: { + label: "Alle" + } + } + }, + sys_share_link: { + label: "Freigabelink", + pluralLabel: "Freigabelinks", + description: "Undurchsichtiges Berechtigungstoken für den Zugriff auf einen einzelnen Datensatz. Öffentliche Linkfreigabe im Stil von Notion/Figma.", + fields: { + id: { + label: "Link-ID" + }, + token: { + label: "Token", + help: "Undurchsichtiges, URL-sicheres Zufallstoken (≥ 22 Zeichen). Das einzige Geheimnis in dieser Zeile." + }, + object_name: { + label: "Objekt", + help: "Kurzer Objektname des geteilten Datensatzes (z. B. ai_conversation, contracts_contract)" + }, + record_id: { + label: "Datensatz", + help: "Primärschlüssel des geteilten Datensatzes in object_name" + }, + permission: { + label: "Berechtigung", + help: "Was der Linkinhaber mit dem Datensatz tun kann", + options: { + view: "Ansehen", + comment: "Kommentieren", + edit: "Bearbeiten" + } + }, + audience: { + label: "Zielgruppe", + help: "Zugangsschicht, die zusätzlich zur Tokenprüfung gilt", + options: { + public: "Öffentlich (indexierbar)", + link_only: "Alle mit dem Link", + signed_in: "Angemeldete Benutzer", + email: "Bestimmte E-Mail-Adressen" + } + }, + expires_at: { + label: "Läuft ab am", + help: "Wenn gesetzt, gibt resolveToken nach diesem Zeitpunkt null zurück" + }, + email_allowlist: { + label: "E-Mail-Positivliste", + help: "Kleingeschriebene Adressen, geprüft bei audience=email" + }, + password_hash: { + label: "Passwort-Hash", + help: "Argon2/bcrypt-Hash. Wenn gesetzt, fragt die Oberfläche vor der Anzeige nach einem Passwort." + }, + redact_fields: { + label: "Schwärzungen pro Link", + help: "Zusätzliche Felder, die über den Standardsatz des Objekts hinaus aus der Antwort entfernt werden" + }, + label: { + label: "Bezeichnung", + help: "Freitext im Freigabedialog (z. B. „ACME-Vertrag Q3“)" + }, + revoked_at: { + label: "Widerrufen am", + help: "Wenn gesetzt, ist der Link dauerhaft deaktiviert" + }, + created_by: { + label: "Erstellt von", + help: "Aussteller des Links" + }, + created_at: { + label: "Erstellt am" + }, + last_used_at: { + label: "Zuletzt verwendet", + help: "Von resolveToken gestempelt; das Dashboard hebt damit aktive Links hervor" + }, + use_count: { + label: "Nutzungszähler", + help: "Wird von resolveToken bei jeder erfolgreichen Auflösung erhöht" + } + }, + _views: { + active_links: { + label: "Aktiv" + }, + by_me: { + label: "Von mir erstellt" + }, + revoked: { + label: "Widerrufen" + }, + all_links: { + label: "Alle" + } + } + }, + sys_queue: { + label: "Warteschlange", + pluralLabel: "Warteschlangen", + description: "Eine benannte Gruppe von Personen, in der nicht zugewiesene Datensätze warten, bis eine davon sie übernimmt.", + fields: { + name: { + label: "Name", + help: "Kennzeichnet die Warteschlange überall, wo sie verwendet wird — an Datensätzen, in Zuweisungsregeln, Genehmigungen und Freigaberegeln. Nur Kleinbuchstaben, Ziffern und Unterstriche." + }, + label: { + label: "Anzeigebezeichnung" + }, + description: { + label: "Beschreibung" + } + }, + _views: { + all_queues: { + label: "Alle" + } + } + }, + sys_queue_member: { + label: "Warteschlangenmitglied", + pluralLabel: "Warteschlangenmitglieder", + description: "Wer zu einer Warteschlange gehört: ein Benutzer, ein Team, eine Position oder ein Geschäftsbereich.", + fields: { + queue: { + label: "Warteschlange", + help: "Der Name der Warteschlange, zu der dieses Mitglied gehört." + }, + recipient_type: { + label: "Mitgliedstyp", + help: "Welche Art Mitglied dies ist. Ein Team, eine Position oder ein Geschäftsbereich macht jede Person darin zum Mitglied. „Geschäftsbereich und untergeordnete“ umfasst zusätzlich jeden Bereich unter dem gewählten.", + options: { + user: "Benutzer", + team: "Team", + position: "Position", + business_unit: "Geschäftsbereich", + unit_and_subordinates: "Geschäftsbereich und untergeordnete" + } + }, + recipient_id: { + label: "Mitglied", + help: "Der konkrete Benutzer, das Team, die Position oder der Geschäftsbereich." + } + }, + _views: { + by_queue: { + label: "Nach Warteschlange" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/de.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/de.source-hashes.generated.ts new file mode 100644 index 00000000000..9616e0665f1 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/de.source-hashes.generated.ts @@ -0,0 +1,29 @@ +// 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 + * `.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> = { + "objects.sys_queue.fields.name.label": "5274417ff1ccaf01", + "objects.sys_queue_member.fields.recipient_type.options.position": "9a24c02e441eb0b4", + "objects.sys_queue_member.fields.recipient_type.options.team": "42b87165ac826b2b", + "objects.sys_record_share.fields.recipient_type.options.position": "9a24c02e441eb0b4", + "objects.sys_share_link.fields.token.label": "6e8de3393d2cee54", + "objects.sys_sharing_rule.fields.name.label": "5274417ff1ccaf01", + "objects.sys_sharing_rule.fields.recipient_type.options.position": "9a24c02e441eb0b4", + "objects.sys_sharing_rule.fields.recipient_type.options.team": "42b87165ac826b2b", +}; diff --git a/packages/plugins/plugin-sharing/src/translations/fr.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/fr.objects.generated.ts new file mode 100644 index 00000000000..20f02b8fcfc --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/fr.objects.generated.ts @@ -0,0 +1,347 @@ +// 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 = { + sys_record_share: { + label: "Partage d’enregistrement", + pluralLabel: "Partages d’enregistrements", + description: "Enregistrements partagés au-delà de leur propriétaire, une ligne par octroi.", + fields: { + id: { + label: "ID du partage" + }, + object_name: { + label: "Objet", + help: "Nom court de l’objet de l’enregistrement partagé" + }, + record_id: { + label: "Enregistrement", + help: "Clé primaire de l’enregistrement partagé dans object_name" + }, + recipient_type: { + label: "Type de destinataire", + help: "Type de principal qui détient l’octroi", + options: { + user: "Utilisateur", + group: "Groupe", + position: "Poste", + unit_and_subordinates: "Unité commerciale et subordonnées" + } + }, + recipient_id: { + label: "Destinataire", + help: "ID de l’utilisateur, du groupe ou du poste qui reçoit l’accès" + }, + access_level: { + label: "Niveau d’accès", + help: "Ce que le destinataire peut faire — lire, ou lire et modifier", + options: { + read: "Lecture", + edit: "Lecture et modification" + } + }, + source: { + label: "Source", + help: "Pourquoi cet octroi existe — utilisé par l’évaluateur de règles pour réconcilier", + options: { + manual: "Manuel", + rule: "Règle", + team: "Équipe", + inherited: "Hérité" + } + }, + source_id: { + label: "ID de la source", + help: "Nom de règle / ID d’équipe quand source != manual" + }, + granted_by: { + label: "Accordé par", + help: "Utilisateur qui a créé l’octroi (manuel uniquement)" + }, + reason: { + label: "Motif", + help: "Explication libre facultative montrée au destinataire" + }, + created_at: { + label: "Créé le" + }, + updated_at: { + label: "Mis à jour le" + } + }, + _views: { + all_shares: { + label: "Tout" + }, + granted_to_me: { + label: "Accordés à moi" + }, + granted_by_me: { + label: "Accordés par moi" + }, + by_object: { + label: "Par objet" + }, + manual_grants: { + label: "Octrois manuels" + }, + rule_grants: { + label: "Octrois par règle" + } + } + }, + sys_sharing_rule: { + label: "Règle de partage", + pluralLabel: "Règles de partage", + description: "Donne à un groupe de personnes l’accès aux enregistrements qui répondent à une condition.", + fields: { + id: { + label: "ID de la règle" + }, + organization_id: { + label: "Organisation", + help: "L’organisation à laquelle appartient cette règle." + }, + name: { + label: "Nom", + help: "Identifie la règle. Lettres minuscules, chiffres et tirets bas uniquement." + }, + label: { + label: "Libellé affiché" + }, + description: { + label: "Description" + }, + object_name: { + label: "Objet", + help: "L’objet dont cette règle partage les enregistrements." + }, + criteria_json: { + label: "Critères", + help: "Quels enregistrements partager. Obligatoire — une règle doit restreindre les enregistrements qu’elle partage, il n’existe donc pas de réglage « partager tous les enregistrements »." + }, + recipient_type: { + label: "Type de destinataire", + help: "Qui reçoit l’accès. Choisir une équipe, une unité commerciale, un poste ou une file d’attente donne l’accès à toutes les personnes qu’ils contiennent. « Unité commerciale et subordonnées » couvre aussi chaque unité sous celle que vous choisissez. « Champ » partage chaque enregistrement avec le ou les utilisateurs indiqués dans l’un de ses propres champs — indiquez ce champ comme destinataire.", + options: { + user: "Utilisateur", + team: "Équipe", + business_unit: "Unité commerciale", + position: "Poste", + unit_and_subordinates: "Unité commerciale et subordonnées", + queue: "File d’attente", + field: "Champ" + } + }, + recipient_id: { + label: "Destinataire", + help: "L’utilisateur, l’équipe, l’unité commerciale, le poste ou la file d’attente précis qui reçoit l’accès — ou, pour le type de destinataire « Champ », le nom du champ de l’enregistrement qui contient le ou les utilisateurs avec qui partager." + }, + access_level: { + label: "Niveau d’accès", + help: "Ce que les destinataires peuvent faire des enregistrements correspondants — les lire, ou les lire et les modifier.", + options: { + read: "Lecture", + edit: "Lecture et modification" + } + }, + active: { + label: "Actif", + help: "Désactivez pour retirer l’accès accordé par cette règle, sans la supprimer." + }, + managed_by: { + label: "Géré par", + help: "D’où vient cette règle : intégrée à la plateforme, installée avec une application ou créée ici dans Configuration.", + options: { + platform: "Plateforme", + package: "Paquet", + admin: "Administrateur" + } + }, + customized: { + label: "Personnalisée", + help: "Activé dès que vous modifiez une règle fournie avec une application, afin que vos changements soient conservés lors de sa mise à jour." + }, + created_at: { + label: "Créé le" + }, + updated_at: { + label: "Mis à jour le" + } + }, + _views: { + active: { + label: "Actif" + }, + inactive: { + label: "Inactif" + }, + by_object: { + label: "Par objet" + }, + all_rules: { + label: "Tout" + } + } + }, + sys_share_link: { + label: "Lien de partage", + pluralLabel: "Liens de partage", + description: "Jeton de capacité opaque donnant accès à un seul enregistrement. Partage par lien public à la Notion/Figma.", + fields: { + id: { + label: "ID du lien" + }, + token: { + label: "Jeton", + help: "Jeton aléatoire opaque compatible URL (≥ 22 caractères). Le seul secret de cette ligne." + }, + object_name: { + label: "Objet", + help: "Nom court de l’objet de l’enregistrement partagé (p. ex. ai_conversation, contracts_contract)" + }, + record_id: { + label: "Enregistrement", + help: "Clé primaire de l’enregistrement partagé dans object_name" + }, + permission: { + label: "Autorisation", + help: "Ce que le détenteur du lien peut faire de l’enregistrement", + options: { + view: "Voir", + comment: "Commenter", + edit: "Modifier" + } + }, + audience: { + label: "Audience", + help: "Couche de contrôle appliquée en plus de la vérification du jeton", + options: { + public: "Public (indexable)", + link_only: "Toute personne disposant du lien", + signed_in: "Utilisateurs connectés", + email: "Adresses e-mail précises" + } + }, + expires_at: { + label: "Expire le", + help: "Si défini, resolveToken renvoie null après cet horodatage" + }, + email_allowlist: { + label: "Liste d’adresses autorisées", + help: "Adresses en minuscules vérifiées quand audience=email" + }, + password_hash: { + label: "Empreinte du mot de passe", + help: "Empreinte Argon2/bcrypt. Si définie, l’interface demande un mot de passe avant l’affichage." + }, + redact_fields: { + label: "Masquages propres au lien", + help: "Champs supplémentaires retirés de la réponse, en plus de l’ensemble par défaut de l’objet" + }, + label: { + label: "Libellé", + help: "Texte libre affiché dans la boîte de dialogue de partage (p. ex. « Contrat ACME T3 »)" + }, + revoked_at: { + label: "Révoqué le", + help: "Si défini, le lien est désactivé définitivement" + }, + created_by: { + label: "Créé par", + help: "Émetteur du lien" + }, + created_at: { + label: "Créé le" + }, + last_used_at: { + label: "Dernière utilisation", + help: "Horodaté par resolveToken ; le tableau de bord s’en sert pour mettre en avant les liens actifs" + }, + use_count: { + label: "Nombre d’utilisations", + help: "Incrémenté par resolveToken à chaque résolution réussie" + } + }, + _views: { + active_links: { + label: "Actifs" + }, + by_me: { + label: "Créés par moi" + }, + revoked: { + label: "Révoqués" + }, + all_links: { + label: "Tout" + } + } + }, + sys_queue: { + label: "File d’attente", + pluralLabel: "Files d’attente", + description: "Un groupe nommé de personnes où les enregistrements non attribués attendent que l’une d’elles les prenne.", + fields: { + name: { + label: "Nom", + help: "Identifie la file d’attente partout où elle sert — sur les enregistrements, dans les règles d’attribution, les approbations et les règles de partage. Lettres minuscules, chiffres et tirets bas uniquement." + }, + label: { + label: "Libellé affiché" + }, + description: { + label: "Description" + } + }, + _views: { + all_queues: { + label: "Tout" + } + } + }, + sys_queue_member: { + label: "Membre de file d’attente", + pluralLabel: "Membres de file d’attente", + description: "Qui fait partie d’une file d’attente : un utilisateur, une équipe, un poste ou une unité commerciale.", + fields: { + queue: { + label: "File d’attente", + help: "Le nom de la file d’attente à laquelle appartient ce membre." + }, + recipient_type: { + label: "Type de membre", + help: "Le type de ce membre. Une équipe, un poste ou une unité commerciale rend membre chaque personne qu’ils contiennent. « Unité commerciale et subordonnées » couvre aussi chaque unité sous celle que vous choisissez.", + options: { + user: "Utilisateur", + team: "Équipe", + position: "Poste", + business_unit: "Unité commerciale", + unit_and_subordinates: "Unité commerciale et subordonnées" + } + }, + recipient_id: { + label: "Membre", + help: "L’utilisateur, l’équipe, le poste ou l’unité commerciale précis." + } + }, + _views: { + by_queue: { + label: "Par file d’attente" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/fr.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/fr.source-hashes.generated.ts new file mode 100644 index 00000000000..ec4f07b8668 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/fr.source-hashes.generated.ts @@ -0,0 +1,26 @@ +// 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 + * `.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> = { + "objects.sys_queue.fields.description.label": "779a4342c8eb6707", + "objects.sys_record_share.fields.source.label": "8fe786e8e29c8cec", + "objects.sys_share_link.fields.audience.label": "6530e0148b77428f", + "objects.sys_share_link.fields.audience.options.public": "2c35c3a6c016e6e1", + "objects.sys_sharing_rule.fields.description.label": "779a4342c8eb6707", +}; diff --git a/packages/plugins/plugin-sharing/src/translations/index.ts b/packages/plugins/plugin-sharing/src/translations/index.ts index 41767a03e1c..e697e977c77 100644 --- a/packages/plugins/plugin-sharing/src/translations/index.ts +++ b/packages/plugins/plugin-sharing/src/translations/index.ts @@ -17,9 +17,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'; import { sharingPageTranslations, sharingPageSourceHashes } from './pages.js'; /** @@ -66,4 +80,32 @@ export const SharingTranslations: TranslationBundle = { { objects: esESObjects, pages: sharingPageTranslations['es-ES'] }, enSource, sharingPageSourceHashes['es-ES'], esESGeneratedSourceHashes, ), + 'zh-Hant': withSourceFallback( + { objects: zhHantObjects, pages: sharingPageTranslations['zh-Hant'] }, + enSource, sharingPageSourceHashes['zh-Hant'], zhHantGeneratedSourceHashes, + ), + 'ar': withSourceFallback( + { objects: arObjects, pages: sharingPageTranslations['ar'] }, + enSource, sharingPageSourceHashes['ar'], arGeneratedSourceHashes, + ), + 'fr': withSourceFallback( + { objects: frObjects, pages: sharingPageTranslations['fr'] }, + enSource, sharingPageSourceHashes['fr'], frGeneratedSourceHashes, + ), + 'pt-BR': withSourceFallback( + { objects: ptBRObjects, pages: sharingPageTranslations['pt-BR'] }, + enSource, sharingPageSourceHashes['pt-BR'], ptBRGeneratedSourceHashes, + ), + 'ru': withSourceFallback( + { objects: ruObjects, pages: sharingPageTranslations['ru'] }, + enSource, sharingPageSourceHashes['ru'], ruGeneratedSourceHashes, + ), + 'de': withSourceFallback( + { objects: deObjects, pages: sharingPageTranslations['de'] }, + enSource, sharingPageSourceHashes['de'], deGeneratedSourceHashes, + ), + 'ko': withSourceFallback( + { objects: koObjects, pages: sharingPageTranslations['ko'] }, + enSource, sharingPageSourceHashes['ko'], koGeneratedSourceHashes, + ), }; diff --git a/packages/plugins/plugin-sharing/src/translations/ko.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/ko.objects.generated.ts new file mode 100644 index 00000000000..184b5d87c73 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/ko.objects.generated.ts @@ -0,0 +1,347 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ko'. + * 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 koObjects: NonNullable = { + sys_record_share: { + label: "레코드 공유", + pluralLabel: "레코드 공유", + description: "소유자 외의 사람에게 공유된 레코드로, 부여마다 한 행입니다.", + fields: { + id: { + label: "공유 ID" + }, + object_name: { + label: "객체", + help: "공유된 레코드의 객체 약칭" + }, + record_id: { + label: "레코드", + help: "object_name 안에서 공유된 레코드의 기본 키" + }, + recipient_type: { + label: "수신자 유형", + help: "이 부여를 가진 주체의 유형", + options: { + user: "사용자", + group: "그룹", + position: "직위", + unit_and_subordinates: "사업부 및 하위 사업부" + } + }, + recipient_id: { + label: "수신자", + help: "접근 권한을 받는 사용자/그룹/직위의 ID" + }, + access_level: { + label: "접근 수준", + help: "수신자가 할 수 있는 일 — 읽기, 또는 읽기 및 편집", + options: { + read: "읽기", + edit: "읽기 및 편집" + } + }, + source: { + label: "출처", + help: "이 부여가 존재하는 이유 — 규칙 평가기가 조정에 사용합니다", + options: { + manual: "수동", + rule: "규칙", + team: "팀", + inherited: "상속" + } + }, + source_id: { + label: "출처 ID", + help: "source != manual일 때의 규칙 이름/팀 ID" + }, + granted_by: { + label: "부여자", + help: "이 부여를 만든 사용자(수동 부여만)" + }, + reason: { + label: "사유", + help: "수신자에게 표시되는 선택 입력 자유 텍스트 설명" + }, + created_at: { + label: "생성 일시" + }, + updated_at: { + label: "수정 일시" + } + }, + _views: { + all_shares: { + label: "전체" + }, + granted_to_me: { + label: "나에게 부여됨" + }, + granted_by_me: { + label: "내가 부여함" + }, + by_object: { + label: "객체별" + }, + manual_grants: { + label: "수동 부여" + }, + rule_grants: { + label: "규칙 부여" + } + } + }, + sys_sharing_rule: { + label: "공유 규칙", + pluralLabel: "공유 규칙", + description: "조건에 맞는 레코드에 대한 접근 권한을 한 그룹의 사람들에게 부여합니다.", + fields: { + id: { + label: "규칙 ID" + }, + organization_id: { + label: "조직", + help: "이 규칙이 속한 조직입니다." + }, + name: { + label: "이름", + help: "규칙을 식별합니다. 영문 소문자, 숫자, 밑줄만 쓸 수 있습니다." + }, + label: { + label: "표시 레이블" + }, + description: { + label: "설명" + }, + object_name: { + label: "객체", + help: "이 규칙이 레코드를 공유하는 객체입니다." + }, + criteria_json: { + label: "조건", + help: "어떤 레코드를 공유할지 정합니다. 필수 — 규칙은 공유할 레코드 범위를 좁혀야 하므로 \"모든 레코드 공유\" 설정은 없습니다." + }, + recipient_type: { + label: "수신자 유형", + help: "누가 접근 권한을 받는지 정합니다. 팀, 사업부, 직위, 대기열을 고르면 그 안의 모든 사람이 접근 권한을 받습니다. \"사업부 및 하위 사업부\"는 고른 사업부 아래의 모든 사업부도 포함합니다. \"필드\"는 각 레코드를 그 레코드 자체의 한 필드에 담긴 사용자에게 공유하므로, 그 필드를 수신자로 지정하세요.", + options: { + user: "사용자", + team: "팀", + business_unit: "사업부", + position: "직위", + unit_and_subordinates: "사업부 및 하위 사업부", + queue: "대기열", + field: "필드" + } + }, + recipient_id: { + label: "수신자", + help: "접근 권한을 받는 특정 사용자, 팀, 사업부, 직위 또는 대기열입니다. 수신자 유형이 \"필드\"이면 공유할 사용자가 담긴 레코드 필드의 이름입니다." + }, + access_level: { + label: "접근 수준", + help: "수신자가 조건에 맞는 레코드로 할 수 있는 일 — 읽기, 또는 읽기 및 편집입니다.", + options: { + read: "읽기", + edit: "읽기 및 편집" + } + }, + active: { + label: "활성", + help: "규칙을 삭제하지 않고 이 규칙이 부여한 접근 권한을 회수하려면 끄세요." + }, + managed_by: { + label: "관리 주체", + help: "이 규칙의 출처: 플랫폼 기본 제공, 앱과 함께 설치, 또는 여기 설정에서 생성.", + options: { + platform: "플랫폼", + package: "패키지", + admin: "관리자" + } + }, + customized: { + label: "사용자 지정됨", + help: "앱과 함께 제공된 규칙을 편집하면 설정되어, 앱을 업데이트해도 변경 내용이 유지됩니다." + }, + created_at: { + label: "생성 일시" + }, + updated_at: { + label: "수정 일시" + } + }, + _views: { + active: { + label: "활성" + }, + inactive: { + label: "비활성" + }, + by_object: { + label: "객체별" + }, + all_rules: { + label: "전체" + } + } + }, + sys_share_link: { + label: "공유 링크", + pluralLabel: "공유 링크", + description: "단일 레코드에 대한 접근 권한을 주는 불투명 권한 토큰입니다. Notion/Figma 방식의 공개 링크 공유입니다.", + fields: { + id: { + label: "링크 ID" + }, + token: { + label: "토큰", + help: "URL에 안전한 불투명 무작위 토큰(≥ 22자)입니다. 이 행의 유일한 비밀 값입니다." + }, + object_name: { + label: "객체", + help: "공유된 레코드의 객체 약칭(예: ai_conversation, contracts_contract)" + }, + record_id: { + label: "레코드", + help: "object_name 안에서 공유된 레코드의 기본 키" + }, + permission: { + label: "권한", + help: "링크 소유자가 레코드로 할 수 있는 일", + options: { + view: "보기", + comment: "댓글", + edit: "편집" + } + }, + audience: { + label: "대상", + help: "토큰 확인 위에 추가로 적용되는 접근 제한 계층", + options: { + public: "공개(검색 색인 허용)", + link_only: "링크가 있는 모든 사람", + signed_in: "로그인한 사용자", + email: "지정한 이메일" + } + }, + expires_at: { + label: "만료 일시", + help: "설정하면 이 시각 이후 resolveToken이 null을 반환합니다" + }, + email_allowlist: { + label: "이메일 허용 목록", + help: "audience=email일 때 확인하는 소문자 주소" + }, + password_hash: { + label: "비밀번호 해시", + help: "Argon2/bcrypt 해시입니다. 설정하면 화면이 내용을 보여 주기 전에 비밀번호를 묻습니다." + }, + redact_fields: { + label: "링크별 가림 필드", + help: "객체 기본 집합 외에 응답에서 추가로 제거하는 필드" + }, + label: { + label: "레이블", + help: "공유 대화 상자에 표시되는 자유 텍스트(예: \"ACME 3분기 계약\")" + }, + revoked_at: { + label: "취소 일시", + help: "설정하면 링크가 영구적으로 비활성화됩니다" + }, + created_by: { + label: "생성자", + help: "링크를 발급한 사람" + }, + created_at: { + label: "생성 일시" + }, + last_used_at: { + label: "마지막 사용 일시", + help: "resolveToken이 기록하며, 대시보드가 활성 링크를 강조할 때 사용합니다" + }, + use_count: { + label: "사용 횟수", + help: "성공적으로 해석될 때마다 resolveToken이 증가시킵니다" + } + }, + _views: { + active_links: { + label: "활성" + }, + by_me: { + label: "내가 만든" + }, + revoked: { + label: "취소됨" + }, + all_links: { + label: "전체" + } + } + }, + sys_queue: { + label: "대기열", + pluralLabel: "대기열", + description: "배정되지 않은 레코드가 그중 한 사람이 가져갈 때까지 대기하는, 이름이 있는 사람들의 그룹입니다.", + fields: { + name: { + label: "이름", + help: "대기열이 쓰이는 모든 곳(레코드, 배정 규칙, 승인, 공유 규칙)에서 이 대기열을 식별합니다. 영문 소문자, 숫자, 밑줄만 쓸 수 있습니다." + }, + label: { + label: "표시 이름" + }, + description: { + label: "설명" + } + }, + _views: { + all_queues: { + label: "전체" + } + } + }, + sys_queue_member: { + label: "대기열 구성원", + pluralLabel: "대기열 구성원", + description: "대기열에 속한 대상: 사용자, 팀, 직위 또는 사업부입니다.", + fields: { + queue: { + label: "대기열", + help: "이 구성원이 속한 대기열의 이름입니다." + }, + recipient_type: { + label: "구성원 유형", + help: "어떤 종류의 구성원인지 정합니다. 팀, 직위, 사업부를 고르면 그 안의 모든 사람이 구성원이 됩니다. \"사업부 및 하위 사업부\"는 고른 사업부 아래의 모든 사업부도 포함합니다.", + options: { + user: "사용자", + team: "팀", + position: "직위", + business_unit: "사업부", + unit_and_subordinates: "사업부 및 하위 사업부" + } + }, + recipient_id: { + label: "구성원", + help: "특정 사용자, 팀, 직위 또는 사업부입니다." + } + }, + _views: { + by_queue: { + label: "대기열별" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/ko.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/ko.source-hashes.generated.ts new file mode 100644 index 00000000000..5ec3ebc3496 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/ko.source-hashes.generated.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ko'. 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 + * `.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 koGeneratedSourceHashes: Readonly> = { +}; diff --git a/packages/plugins/plugin-sharing/src/translations/pages.ts b/packages/plugins/plugin-sharing/src/translations/pages.ts index b6fd3471a3f..89c71f0fa86 100644 --- a/packages/plugins/plugin-sharing/src/translations/pages.ts +++ b/packages/plugins/plugin-sharing/src/translations/pages.ts @@ -33,14 +33,25 @@ import type { SourceHashes } from '@objectstack/platform-objects/apps'; type PageTranslations = NonNullable; -/** The shipped locales, the four the generated bundles carry. */ -export type SharingLocale = 'en' | 'zh-CN' | 'ja-JP' | 'es-ES'; +/** + * The shipped locales, the eleven the generated bundles carry. Each value is + * the locale's own `sys_queue` object label, so the page and the object it + * shows read the same. + */ +export type SharingLocale = 'en' | 'zh-CN' | 'ja-JP' | 'es-ES' | 'zh-Hant' | 'ar' | 'fr' | 'pt-BR' | 'ru' | 'de' | 'ko'; export const sharingPageTranslations: Readonly> = { en: { sys_queue_detail: { label: 'Queue' } }, 'zh-CN': { sys_queue_detail: { label: '队列' } }, 'ja-JP': { sys_queue_detail: { label: 'キュー' } }, 'es-ES': { sys_queue_detail: { label: 'Cola' } }, + 'zh-Hant': { sys_queue_detail: { label: '佇列' } }, + ar: { sys_queue_detail: { label: 'قائمة الانتظار' } }, + fr: { sys_queue_detail: { label: 'File d’attente' } }, + 'pt-BR': { sys_queue_detail: { label: 'Fila' } }, + ru: { sys_queue_detail: { label: 'Очередь' } }, + de: { sys_queue_detail: { label: 'Warteschlange' } }, + ko: { sys_queue_detail: { label: '대기열' } }, }; /** @@ -51,4 +62,11 @@ export const sharingPageSourceHashes: Readonly = { + sys_record_share: { + label: "Compartilhamento de registro", + pluralLabel: "Compartilhamentos de registros", + description: "Registros compartilhados além do proprietário, uma linha por concessão.", + fields: { + id: { + label: "ID do compartilhamento" + }, + object_name: { + label: "Objeto", + help: "Nome curto do objeto do registro compartilhado" + }, + record_id: { + label: "Registro", + help: "Chave primária do registro compartilhado em object_name" + }, + recipient_type: { + label: "Tipo de destinatário", + help: "Tipo de principal que detém a concessão", + options: { + user: "Usuário", + group: "Grupo", + position: "Cargo", + unit_and_subordinates: "Unidade de negócios e subordinadas" + } + }, + recipient_id: { + label: "Destinatário", + help: "ID do usuário, grupo ou cargo que recebe o acesso" + }, + access_level: { + label: "Nível de acesso", + help: "O que o destinatário pode fazer — ler, ou ler e editar", + options: { + read: "Leitura", + edit: "Leitura e edição" + } + }, + source: { + label: "Origem", + help: "Por que esta concessão existe — usado pelo avaliador de regras para conciliar", + options: { + manual: "Manual", + rule: "Regra", + team: "Equipe", + inherited: "Herdado" + } + }, + source_id: { + label: "ID da origem", + help: "Nome da regra / ID da equipe quando source != manual" + }, + granted_by: { + label: "Concedido por", + help: "Usuário que criou a concessão (somente manual)" + }, + reason: { + label: "Motivo", + help: "Explicação livre opcional mostrada ao destinatário" + }, + created_at: { + label: "Criado em" + }, + updated_at: { + label: "Atualizado em" + } + }, + _views: { + all_shares: { + label: "Todos" + }, + granted_to_me: { + label: "Concedidos a mim" + }, + granted_by_me: { + label: "Concedidos por mim" + }, + by_object: { + label: "Por objeto" + }, + manual_grants: { + label: "Concessões manuais" + }, + rule_grants: { + label: "Concessões por regra" + } + } + }, + sys_sharing_rule: { + label: "Regra de compartilhamento", + pluralLabel: "Regras de compartilhamento", + description: "Dá a um grupo de pessoas acesso aos registros que atendem a uma condição.", + fields: { + id: { + label: "ID da regra" + }, + organization_id: { + label: "Organização", + help: "A organização à qual esta regra pertence." + }, + name: { + label: "Nome", + help: "Identifica a regra. Apenas letras minúsculas, dígitos e sublinhados." + }, + label: { + label: "Rótulo de exibição" + }, + description: { + label: "Descrição" + }, + object_name: { + label: "Objeto", + help: "O objeto cujos registros esta regra compartilha." + }, + criteria_json: { + label: "Critérios", + help: "Quais registros compartilhar. Obrigatório — uma regra precisa restringir os registros que compartilha, então não existe a opção \"compartilhar todos os registros\"." + }, + recipient_type: { + label: "Tipo de destinatário", + help: "Quem recebe o acesso. Escolher uma equipe, unidade de negócios, cargo ou fila dá acesso a todos que estão nela. \"Unidade de negócios e subordinadas\" também cobre todas as unidades abaixo da escolhida. \"Campo\" compartilha cada registro com o usuário ou usuários guardados em um dos próprios campos dele — informe esse campo como destinatário.", + options: { + user: "Usuário", + team: "Equipe", + business_unit: "Unidade de negócios", + position: "Cargo", + unit_and_subordinates: "Unidade de negócios e subordinadas", + queue: "Fila", + field: "Campo" + } + }, + recipient_id: { + label: "Destinatário", + help: "O usuário, a equipe, a unidade de negócios, o cargo ou a fila específicos que recebem o acesso — ou, para o tipo de destinatário \"Campo\", o nome do campo do registro que guarda o usuário ou usuários com quem compartilhar." + }, + access_level: { + label: "Nível de acesso", + help: "O que os destinatários podem fazer com os registros correspondentes — lê-los, ou lê-los e editá-los.", + options: { + read: "Leitura", + edit: "Leitura e edição" + } + }, + active: { + label: "Ativo", + help: "Desative para retirar o acesso que esta regra concedeu, sem excluir a regra." + }, + managed_by: { + label: "Gerenciado por", + help: "De onde veio esta regra: integrada à plataforma, instalada com um aplicativo ou criada aqui na Configuração.", + options: { + platform: "Plataforma", + package: "Pacote", + admin: "Administrador" + } + }, + customized: { + label: "Personalizada", + help: "Marcado quando você edita uma regra que veio com um aplicativo, para que suas alterações sejam mantidas quando o aplicativo for atualizado." + }, + created_at: { + label: "Criado em" + }, + updated_at: { + label: "Atualizado em" + } + }, + _views: { + active: { + label: "Ativo" + }, + inactive: { + label: "Inativo" + }, + by_object: { + label: "Por objeto" + }, + all_rules: { + label: "Todos" + } + } + }, + sys_share_link: { + label: "Link de compartilhamento", + pluralLabel: "Links de compartilhamento", + description: "Token de capacidade opaco que dá acesso a um único registro. Compartilhamento por link público no estilo Notion/Figma.", + fields: { + id: { + label: "ID do link" + }, + token: { + label: "Token", + help: "Token aleatório opaco seguro para URL (≥ 22 caracteres). O único segredo nesta linha." + }, + object_name: { + label: "Objeto", + help: "Nome curto do objeto do registro compartilhado (ex.: ai_conversation, contracts_contract)" + }, + record_id: { + label: "Registro", + help: "Chave primária do registro compartilhado em object_name" + }, + permission: { + label: "Permissão", + help: "O que quem tem o link pode fazer com o registro", + options: { + view: "Visualizar", + comment: "Comentar", + edit: "Editar" + } + }, + audience: { + label: "Público", + help: "Camada de restrição aplicada além da verificação do token", + options: { + public: "Público (indexável)", + link_only: "Qualquer pessoa com o link", + signed_in: "Usuários conectados", + email: "E-mails específicos" + } + }, + expires_at: { + label: "Expira em", + help: "Quando definido, resolveToken retorna null após este carimbo de data/hora" + }, + email_allowlist: { + label: "Lista de e-mails permitidos", + help: "Endereços em minúsculas verificados quando audience=email" + }, + password_hash: { + label: "Hash da senha", + help: "Hash Argon2/bcrypt. Quando definido, a interface pede uma senha antes de exibir." + }, + redact_fields: { + label: "Ocultações por link", + help: "Campos extras removidos da resposta, além do conjunto padrão do objeto" + }, + label: { + label: "Rótulo", + help: "Texto livre exibido na caixa de diálogo de compartilhamento (ex.: \"Contrato ACME T3\")" + }, + revoked_at: { + label: "Revogado em", + help: "Quando definido, o link fica desativado permanentemente" + }, + created_by: { + label: "Criado por", + help: "Emissor do link" + }, + created_at: { + label: "Criado em" + }, + last_used_at: { + label: "Último uso", + help: "Registrado por resolveToken; o painel usa isso para destacar os links ativos" + }, + use_count: { + label: "Contagem de usos", + help: "Incrementado por resolveToken a cada resolução bem-sucedida" + } + }, + _views: { + active_links: { + label: "Ativos" + }, + by_me: { + label: "Criados por mim" + }, + revoked: { + label: "Revogados" + }, + all_links: { + label: "Todos" + } + } + }, + sys_queue: { + label: "Fila", + pluralLabel: "Filas", + description: "Um grupo nomeado de pessoas em que registros não atribuídos aguardam até que uma delas os assuma.", + fields: { + name: { + label: "Nome", + help: "Identifica a fila em todos os lugares onde é usada — nos registros, nas regras de atribuição, nas aprovações e nas regras de compartilhamento. Apenas letras minúsculas, dígitos e sublinhados." + }, + label: { + label: "Rótulo de exibição" + }, + description: { + label: "Descrição" + } + }, + _views: { + all_queues: { + label: "Todos" + } + } + }, + sys_queue_member: { + label: "Membro da fila", + pluralLabel: "Membros da fila", + description: "Quem pertence a uma fila: um usuário, uma equipe, um cargo ou uma unidade de negócios.", + fields: { + queue: { + label: "Fila", + help: "O nome da fila à qual este membro pertence." + }, + recipient_type: { + label: "Tipo de membro", + help: "Que tipo de membro é este. Uma equipe, cargo ou unidade de negócios torna membro todos que estão nela. \"Unidade de negócios e subordinadas\" também cobre todas as unidades abaixo da escolhida.", + options: { + user: "Usuário", + team: "Equipe", + position: "Cargo", + business_unit: "Unidade de negócios", + unit_and_subordinates: "Unidade de negócios e subordinadas" + } + }, + recipient_id: { + label: "Membro", + help: "O usuário, a equipe, o cargo ou a unidade de negócios específicos." + } + }, + _views: { + by_queue: { + label: "Por fila" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/pt-BR.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/pt-BR.source-hashes.generated.ts new file mode 100644 index 00000000000..a45cc3e0c70 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/pt-BR.source-hashes.generated.ts @@ -0,0 +1,22 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'pt-BR'. 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 + * `.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 ptBRGeneratedSourceHashes: Readonly> = { + "objects.sys_share_link.fields.token.label": "6e8de3393d2cee54", +}; diff --git a/packages/plugins/plugin-sharing/src/translations/recipient-vocabulary-consistency.test.ts b/packages/plugins/plugin-sharing/src/translations/recipient-vocabulary-consistency.test.ts index bd7df2f87dc..dfd038f305b 100644 --- a/packages/plugins/plugin-sharing/src/translations/recipient-vocabulary-consistency.test.ts +++ b/packages/plugins/plugin-sharing/src/translations/recipient-vocabulary-consistency.test.ts @@ -27,12 +27,26 @@ import { describe, it, expect } from 'vitest'; import { enObjects } from './en.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 { zhCNObjects } from './zh-CN.objects.generated.js'; import { jaJPObjects } from './ja-JP.objects.generated.js'; const LOCALES: Array<[string, Record]> = [ ['en', enObjects as Record], ['es-ES', esESObjects as Record], + ['zh-Hant', zhHantObjects as Record], + ['ar', arObjects as Record], + ['fr', frObjects as Record], + ['pt-BR', ptBRObjects as Record], + ['ru', ruObjects as Record], + ['de', deObjects as Record], + ['ko', koObjects as Record], ['zh-CN', zhCNObjects as Record], ['ja-JP', jaJPObjects as Record], ]; diff --git a/packages/plugins/plugin-sharing/src/translations/ru.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/ru.objects.generated.ts new file mode 100644 index 00000000000..a3323467c2a --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/ru.objects.generated.ts @@ -0,0 +1,347 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ru'. + * 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 ruObjects: NonNullable = { + sys_record_share: { + label: "Общий доступ к записи", + pluralLabel: "Общий доступ к записям", + description: "Записи, к которым открыт доступ не только владельцу, по одной строке на предоставление.", + fields: { + id: { + label: "ID общего доступа" + }, + object_name: { + label: "Объект", + help: "Краткое имя объекта общей записи" + }, + record_id: { + label: "Запись", + help: "Первичный ключ общей записи в object_name" + }, + recipient_type: { + label: "Тип получателя", + help: "Тип субъекта, которому принадлежит предоставление", + options: { + user: "Пользователь", + group: "Группа", + position: "Должность", + unit_and_subordinates: "Подразделение и подчинённые" + } + }, + recipient_id: { + label: "Получатель", + help: "ID пользователя, группы или должности, получающих доступ" + }, + access_level: { + label: "Уровень доступа", + help: "Что может получатель — читать или читать и редактировать", + options: { + read: "Чтение", + edit: "Чтение и редактирование" + } + }, + source: { + label: "Источник", + help: "Почему существует это предоставление — используется вычислителем правил для сверки", + options: { + manual: "Вручную", + rule: "Правило", + team: "Команда", + inherited: "Унаследовано" + } + }, + source_id: { + label: "ID источника", + help: "Имя правила или ID команды, когда source != manual" + }, + granted_by: { + label: "Кем выдано", + help: "Пользователь, создавший предоставление (только вручную)" + }, + reason: { + label: "Причина", + help: "Необязательное пояснение свободным текстом, которое видит получатель" + }, + created_at: { + label: "Создано" + }, + updated_at: { + label: "Обновлено" + } + }, + _views: { + all_shares: { + label: "Все" + }, + granted_to_me: { + label: "Выданные мне" + }, + granted_by_me: { + label: "Выданные мной" + }, + by_object: { + label: "По объекту" + }, + manual_grants: { + label: "Ручные предоставления" + }, + rule_grants: { + label: "Предоставления по правилам" + } + } + }, + sys_sharing_rule: { + label: "Правило общего доступа", + pluralLabel: "Правила общего доступа", + description: "Даёт группе людей доступ к записям, которые соответствуют условию.", + fields: { + id: { + label: "ID правила" + }, + organization_id: { + label: "Организация", + help: "Организация, к которой относится это правило." + }, + name: { + label: "Имя", + help: "Идентифицирует правило. Только строчные латинские буквы, цифры и подчёркивания." + }, + label: { + label: "Отображаемая метка" + }, + description: { + label: "Описание" + }, + object_name: { + label: "Объект", + help: "Объект, записями которого делится это правило." + }, + criteria_json: { + label: "Условия", + help: "Какими записями делиться. Обязательно — правило должно сужать круг записей, поэтому настройки «делиться всеми записями» нет." + }, + recipient_type: { + label: "Тип получателя", + help: "Кто получает доступ. Если выбрать команду, бизнес-подразделение, должность или очередь, доступ получит каждый, кто в неё входит. «Подразделение и подчинённые» охватывает также все подразделения ниже выбранного. «Поле» открывает каждую запись пользователю или пользователям, указанным в одном из её собственных полей, — укажите это поле как получателя.", + options: { + user: "Пользователь", + team: "Команда", + business_unit: "Бизнес-подразделение", + position: "Должность", + unit_and_subordinates: "Подразделение и подчинённые", + queue: "Очередь", + field: "Поле" + } + }, + recipient_id: { + label: "Получатель", + help: "Конкретный пользователь, команда, бизнес-подразделение, должность или очередь, получающие доступ, — или, для типа получателя «Поле», имя поля записи, в котором указан пользователь или пользователи для общего доступа." + }, + access_level: { + label: "Уровень доступа", + help: "Что получатели могут делать с подходящими записями — читать их или читать и редактировать.", + options: { + read: "Чтение", + edit: "Чтение и редактирование" + } + }, + active: { + label: "Активно", + help: "Выключите, чтобы отозвать доступ, выданный этим правилом, не удаляя его." + }, + managed_by: { + label: "Источник управления", + help: "Откуда это правило: встроено в платформу, установлено с приложением или создано здесь, в настройках.", + options: { + platform: "Платформа", + package: "Пакет", + admin: "Администратор" + } + }, + customized: { + label: "Изменено", + help: "Устанавливается, когда вы меняете правило, пришедшее с приложением, чтобы ваши изменения сохранились при его обновлении." + }, + created_at: { + label: "Создано" + }, + updated_at: { + label: "Обновлено" + } + }, + _views: { + active: { + label: "Активно" + }, + inactive: { + label: "Неактивно" + }, + by_object: { + label: "По объекту" + }, + all_rules: { + label: "Все" + } + } + }, + sys_share_link: { + label: "Ссылка общего доступа", + pluralLabel: "Ссылки общего доступа", + description: "Непрозрачный токен-полномочие, дающий доступ к одной записи. Публичная ссылка в стиле Notion/Figma.", + fields: { + id: { + label: "ID ссылки" + }, + token: { + label: "Токен", + help: "Непрозрачный случайный токен, безопасный для URL (≥ 22 символов). Единственный секрет в этой строке." + }, + object_name: { + label: "Объект", + help: "Краткое имя объекта общей записи (например, ai_conversation, contracts_contract)" + }, + record_id: { + label: "Запись", + help: "Первичный ключ общей записи в object_name" + }, + permission: { + label: "Разрешение", + help: "Что владелец ссылки может делать с записью", + options: { + view: "Просмотр", + comment: "Комментирование", + edit: "Редактирование" + } + }, + audience: { + label: "Аудитория", + help: "Слой ограничения поверх проверки токена", + options: { + public: "Публично (индексируется)", + link_only: "Все, у кого есть ссылка", + signed_in: "Вошедшие пользователи", + email: "Определённые адреса" + } + }, + expires_at: { + label: "Истекает", + help: "Если задано, после этой отметки времени resolveToken возвращает null" + }, + email_allowlist: { + label: "Список разрешённых адресов", + help: "Адреса в нижнем регистре, проверяемые при audience=email" + }, + password_hash: { + label: "Хеш пароля", + help: "Хеш Argon2/bcrypt. Если задан, интерфейс запрашивает пароль перед показом." + }, + redact_fields: { + label: "Скрытые поля ссылки", + help: "Дополнительные поля, удаляемые из ответа сверх набора объекта по умолчанию" + }, + label: { + label: "Метка", + help: "Свободный текст в окне общего доступа (например, «Договор ACME, 3-й кв.»)" + }, + revoked_at: { + label: "Отозвано", + help: "Если задано, ссылка отключена навсегда" + }, + created_by: { + label: "Создатель", + help: "Кто выпустил ссылку" + }, + created_at: { + label: "Создано" + }, + last_used_at: { + label: "Последнее использование", + help: "Проставляется resolveToken; панель использует это, чтобы выделять активные ссылки" + }, + use_count: { + label: "Число использований", + help: "Увеличивается resolveToken при каждом успешном разрешении" + } + }, + _views: { + active_links: { + label: "Активные" + }, + by_me: { + label: "Созданные мной" + }, + revoked: { + label: "Отозванные" + }, + all_links: { + label: "Все" + } + } + }, + sys_queue: { + label: "Очередь", + pluralLabel: "Очереди", + description: "Именованная группа людей, где неназначенные записи ждут, пока кто-то из них не возьмёт запись.", + fields: { + name: { + label: "Имя", + help: "Идентифицирует очередь везде, где она используется: в записях, правилах назначения, согласованиях и правилах общего доступа. Только строчные латинские буквы, цифры и подчёркивания." + }, + label: { + label: "Отображаемое имя" + }, + description: { + label: "Описание" + } + }, + _views: { + all_queues: { + label: "Все" + } + } + }, + sys_queue_member: { + label: "Участник очереди", + pluralLabel: "Участники очереди", + description: "Кто входит в очередь: пользователь, команда, должность или бизнес-подразделение.", + fields: { + queue: { + label: "Очередь", + help: "Имя очереди, в которую входит этот участник." + }, + recipient_type: { + label: "Тип участника", + help: "Какой это участник. Команда, должность или бизнес-подразделение делают участником каждого, кто в них входит. «Подразделение и подчинённые» охватывает также все подразделения ниже выбранного.", + options: { + user: "Пользователь", + team: "Команда", + position: "Должность", + business_unit: "Бизнес-подразделение", + unit_and_subordinates: "Подразделение и подчинённые" + } + }, + recipient_id: { + label: "Участник", + help: "Конкретный пользователь, команда, должность или бизнес-подразделение." + } + }, + _views: { + by_queue: { + label: "По очереди" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/ru.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/ru.source-hashes.generated.ts new file mode 100644 index 00000000000..0f794307ebc --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/ru.source-hashes.generated.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'ru'. 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 + * `.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 ruGeneratedSourceHashes: Readonly> = { +}; diff --git a/packages/plugins/plugin-sharing/src/translations/zh-Hant.objects.generated.ts b/packages/plugins/plugin-sharing/src/translations/zh-Hant.objects.generated.ts new file mode 100644 index 00000000000..f50bbfeb053 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/zh-Hant.objects.generated.ts @@ -0,0 +1,347 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-Hant'. + * 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 zhHantObjects: NonNullable = { + sys_record_share: { + label: "記錄共用", + pluralLabel: "記錄共用", + description: "共用給擁有者以外人員的記錄,每筆授權一列。", + fields: { + id: { + label: "共用 ID" + }, + object_name: { + label: "物件", + help: "被共用記錄的物件短名稱" + }, + record_id: { + label: "記錄", + help: "object_name 中被共用記錄的主鍵" + }, + recipient_type: { + label: "接收方類型", + help: "持有此授權的主體類型", + options: { + user: "使用者", + group: "群組", + position: "職位", + unit_and_subordinates: "業務單位及下級" + } + }, + recipient_id: { + label: "接收方", + help: "取得存取權的使用者/群組/職位 ID" + }, + access_level: { + label: "存取層級", + help: "接收方可以做什麼——讀取,或讀取並編輯", + options: { + read: "讀取", + edit: "讀取並編輯" + } + }, + source: { + label: "來源", + help: "此授權存在的原因——規則評估器據此進行對帳", + options: { + manual: "手動", + rule: "規則", + team: "團隊", + inherited: "繼承" + } + }, + source_id: { + label: "來源 ID", + help: "當 source != manual 時,為規則名稱/團隊 ID" + }, + granted_by: { + label: "授權人", + help: "建立此授權的使用者(僅限手動授權)" + }, + reason: { + label: "原因", + help: "選填的自由文字說明,會顯示給接收方" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all_shares: { + label: "全部" + }, + granted_to_me: { + label: "授予我的" + }, + granted_by_me: { + label: "我授予的" + }, + by_object: { + label: "依物件" + }, + manual_grants: { + label: "手動授權" + }, + rule_grants: { + label: "規則授權" + } + } + }, + sys_sharing_rule: { + label: "共用規則", + pluralLabel: "共用規則", + description: "將符合條件的記錄授權給一組人存取。", + fields: { + id: { + label: "規則 ID" + }, + organization_id: { + label: "組織", + help: "此規則所屬的組織。" + }, + name: { + label: "名稱", + help: "規則的識別名稱。只能使用小寫字母、數字和底線。" + }, + label: { + label: "顯示標籤" + }, + description: { + label: "描述" + }, + object_name: { + label: "物件", + help: "此規則要共用哪個物件的記錄。" + }, + criteria_json: { + label: "條件", + help: "要共用哪些記錄。必填——規則必須縮小共用範圍,因此沒有「共用所有記錄」這個設定。" + }, + recipient_type: { + label: "接收方類型", + help: "誰會取得存取權。選擇團隊、業務單位、職位或佇列時,其中每個人都會取得存取權。「業務單位及下級」還涵蓋所選單位以下的每個單位。「欄位」會把每筆記錄共用給該記錄自身某個欄位中保存的使用者——請將該欄位填為接收方。", + options: { + user: "使用者", + team: "團隊", + business_unit: "業務單位", + position: "職位", + unit_and_subordinates: "業務單位及下級", + queue: "佇列", + field: "欄位" + } + }, + recipient_id: { + label: "接收方", + help: "取得存取權的特定使用者、團隊、業務單位、職位或佇列;接收方類型為「欄位」時,則為保存要共用之使用者的記錄欄位名稱。" + }, + access_level: { + label: "存取層級", + help: "接收方可以對符合的記錄做什麼——讀取,或讀取並編輯。", + options: { + read: "讀取", + edit: "讀取並編輯" + } + }, + active: { + label: "啟用", + help: "關閉即可收回此規則已授予的存取權,而不刪除規則。" + }, + managed_by: { + label: "管理來源", + help: "此規則的來源:平台內建、隨應用程式安裝,或在設定中於此處建立。", + options: { + platform: "平台", + package: "套件", + admin: "管理員" + } + }, + customized: { + label: "已自訂", + help: "當你編輯隨應用程式安裝的規則後就會設定,應用程式更新時會保留你的變更。" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + active: { + label: "啟用" + }, + inactive: { + label: "停用" + }, + by_object: { + label: "依物件" + }, + all_rules: { + label: "全部" + } + } + }, + sys_share_link: { + label: "共用連結", + pluralLabel: "共用連結", + description: "授予單筆記錄存取權的不透明能力權杖。類似 Notion/Figma 的公開連結共用。", + fields: { + id: { + label: "連結 ID" + }, + token: { + label: "權杖", + help: "URL 安全的不透明隨機權杖(≥ 22 個字元)。此列中唯一的機密。" + }, + object_name: { + label: "物件", + help: "被共用記錄的物件短名稱(例如 ai_conversation、contracts_contract)" + }, + record_id: { + label: "記錄", + help: "object_name 中被共用記錄的主鍵" + }, + permission: { + label: "權限", + help: "連結持有人可對此記錄做什麼", + options: { + view: "檢視", + comment: "留言", + edit: "編輯" + } + }, + audience: { + label: "對象", + help: "在權杖檢查之上再套用的存取限制層", + options: { + public: "公開(可被索引)", + link_only: "任何擁有連結的人", + signed_in: "已登入使用者", + email: "指定電子郵件" + } + }, + expires_at: { + label: "到期時間", + help: "設定後,超過此時間點 resolveToken 會傳回 null" + }, + email_allowlist: { + label: "電子郵件允許清單", + help: "當 audience=email 時檢查的小寫電子郵件地址" + }, + password_hash: { + label: "密碼雜湊", + help: "Argon2/bcrypt 雜湊值。設定後,介面會在顯示內容前要求輸入密碼。" + }, + redact_fields: { + label: "各連結遮蔽欄位", + help: "在物件預設集合之外,額外從回應中移除的欄位" + }, + label: { + label: "標籤", + help: "在共用對話框中顯示的自由文字(例如「ACME Q3 合約」)" + }, + revoked_at: { + label: "撤銷時間", + help: "設定後,此連結即永久停用" + }, + created_by: { + label: "建立者", + help: "此連結的簽發者" + }, + created_at: { + label: "建立時間" + }, + last_used_at: { + label: "最近使用時間", + help: "由 resolveToken 記錄;儀表板據此醒目標示使用中的連結" + }, + use_count: { + label: "使用次數", + help: "每次成功解析時由 resolveToken 遞增" + } + }, + _views: { + active_links: { + label: "使用中" + }, + by_me: { + label: "我建立的" + }, + revoked: { + label: "已撤銷" + }, + all_links: { + label: "全部" + } + } + }, + sys_queue: { + label: "佇列", + pluralLabel: "佇列", + description: "一組有名稱的成員;尚未指派負責人的記錄可在此等候,直到其中一人認領。", + fields: { + name: { + label: "名稱", + help: "在所有使用此佇列的地方識別它:記錄上、指派規則、核准與共用規則中。只能使用小寫字母、數字和底線。" + }, + label: { + label: "顯示名稱" + }, + description: { + label: "描述" + } + }, + _views: { + all_queues: { + label: "全部" + } + } + }, + sys_queue_member: { + label: "佇列成員", + pluralLabel: "佇列成員", + description: "誰屬於某個佇列:一位使用者、一個團隊、一個職位或一個業務單位。", + fields: { + queue: { + label: "佇列", + help: "此成員所屬佇列的名稱。" + }, + recipient_type: { + label: "成員類型", + help: "這是哪一類成員。選擇團隊、職位或業務單位時,其中每個人都是成員。「業務單位及下級」還涵蓋所選單位以下的每個單位。", + options: { + user: "使用者", + team: "團隊", + position: "職位", + business_unit: "業務單位", + unit_and_subordinates: "業務單位及下級" + } + }, + recipient_id: { + label: "成員", + help: "特定的使用者、團隊、職位或業務單位。" + } + }, + _views: { + by_queue: { + label: "依佇列" + } + } + } +}; diff --git a/packages/plugins/plugin-sharing/src/translations/zh-Hant.source-hashes.generated.ts b/packages/plugins/plugin-sharing/src/translations/zh-Hant.source-hashes.generated.ts new file mode 100644 index 00000000000..bf521657f18 --- /dev/null +++ b/packages/plugins/plugin-sharing/src/translations/zh-Hant.source-hashes.generated.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-Hant'. 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 + * `.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 zhHantGeneratedSourceHashes: Readonly> = { +};