From 9c22c66732748cc6e08e8b7a2c8e5b2db8760538 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 03:53:47 +0000 Subject: [PATCH] i18n(service-settings): ship the built-in settings pages in zh-Hant, ar, fr and pt-BR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four new locales for the built-in Setup settings pages (page titles and descriptions, groups, field labels and help, option labels, banners, actions and source badges), registered in settingsBuiltinTranslations under the platform-table tags (zh-CN before zh-Hant). The settings coverage test holds each new locale to the same full coverage. Carried from the released handover commit 70f9870a (written by session_01Utp6Cai5CRdAs4u9gTipWU) without its localization-currency-lock and localization-locale-options hunks: #30630 (landed as #30734) owns those copy tables and already carries all eleven languages there. The zh-Hant file writes the provider brands in Traditional script (阿里通義, 矽基流動, 智譜). Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_016WV4cya5ej5GQV86RPbRPK --- .../30512-service-settings-locales-a.md | 10 + .../service-settings/src/translations/ar.ts | 698 ++++++++++++++++ .../service-settings/src/translations/fr.ts | 752 ++++++++++++++++++ .../src/translations/index.ts | 10 +- .../src/translations/pt-BR.ts | 736 +++++++++++++++++ .../settings-translation-coverage.test.ts | 8 +- .../src/translations/zh-Hant.ts | 483 +++++++++++ 7 files changed, 2694 insertions(+), 3 deletions(-) create mode 100644 .changeset/30512-service-settings-locales-a.md create mode 100644 packages/services/service-settings/src/translations/ar.ts create mode 100644 packages/services/service-settings/src/translations/fr.ts create mode 100644 packages/services/service-settings/src/translations/pt-BR.ts create mode 100644 packages/services/service-settings/src/translations/zh-Hant.ts diff --git a/.changeset/30512-service-settings-locales-a.md b/.changeset/30512-service-settings-locales-a.md new file mode 100644 index 00000000000..24df5ff10a9 --- /dev/null +++ b/.changeset/30512-service-settings-locales-a.md @@ -0,0 +1,10 @@ +--- +'@objectstack/service-settings': patch +--- + +i18n(service-settings): the built-in Setup settings pages ship in Traditional Chinese, Arabic, French and Brazilian Portuguese + +Clause-②: no +Metadata-protocol: none + +The built-in settings pages (Mail, SMS, Branding, Company, Localization, Authentication, File Storage, AI & Embedder, Knowledge, and the Data Lifecycle page `@objectstack/objectql` registers) now carry `zh-Hant`, `ar`, `fr` and `pt-BR` beside the existing `en`, `zh-CN`, `ja-JP` and `es-ES`: page titles and descriptions, group headings, field labels and help, option labels, banners, action buttons and the source badges. An administrator reading Setup in one of those languages sees these pages in their own language instead of English. Country pickers name each country in the reader's language through `Intl.DisplayNames`, as the existing locales do. The settings coverage test now holds each new locale to the same full coverage. First half of package 6 of #30512; `ru`, `de` and `ko` follow in the second half. diff --git a/packages/services/service-settings/src/translations/ar.ts b/packages/services/service-settings/src/translations/ar.ts new file mode 100644 index 00000000000..5682fb761bd --- /dev/null +++ b/packages/services/service-settings/src/translations/ar.ts @@ -0,0 +1,698 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { PlatformTranslationData } from '@objectstack/spec/system'; +import { countryOptionLabels } from '../manifests/company.manifest.js'; + +/** + * العربية (ar) — built-in settings manifest translations. + */ +export const ar: PlatformTranslationData = { + settingsCommon: { + sourceLabels: { env: 'متغير بيئة', global: 'عام', tenant: 'المؤسسة', user: 'المستخدم', default: 'افتراضي' }, + }, + settings: { + mail: { + title: 'إرسال البريد', + description: 'إعداد SMTP ومزوّد البريد الإلكتروني للمعاملات.', + groups: { + provider: { title: 'المزوّد', description: 'اختر طريقة إرسال هذا النظام للبريد الصادر.' }, + smtp: { title: 'SMTP' }, + api_key: { title: 'مفتاح API' }, + from_address: { title: 'عنوان المرسِل' }, + delivery: { title: 'التسليم', description: 'كيف يُسلَّم البريد الصادر إلى المزوّد.' }, + }, + keys: { + provider: { + label: 'المزوّد', + help: 'لا تُعرض إلا المزوّدات التي يستطيع هذا الخادم الإرسال عبرها فعلًا. يُضبط SendGrid وAmazon SES على أنهما SMTP.', + options: { smtp: 'SMTP', resend: 'Resend', postmark: 'Postmark', log: 'لا شيء (تسجيل فقط — دون إرسال فعلي)' }, + }, + smtp_host: { label: 'المضيف', help: 'مثال: smtp.example.com' }, + smtp_port: { label: 'المنفذ' }, + smtp_secure: { label: 'استخدام TLS' }, + smtp_user: { label: 'اسم المستخدم' }, + smtp_password: { label: 'كلمة المرور' }, + api_key: { label: 'مفتاح API' }, + from_email: { label: 'بريد المرسِل', help: 'مثال: no-reply@example.com' }, + from_name: { + label: 'اسم المرسِل', + help: 'اتركه فارغًا لاستخدام اسم المنتج (العلامة التجارية)، أو الاسم الذي أُعِدّ به هذا النشر.', + }, + queue_delivery: { + label: 'التسليم عبر قائمة انتظار دائمة', + help: 'تُسلَّم كل رسالة إلى قائمة انتظار المهام بدل إرسالها مباشرةً، فيعيد العامل (worker) محاولة التسليم الفاشل مع تأخير متزايد، ويبقى التسليم بعد إعادة التشغيل. يتطلب قدرة قائمة الانتظار مع محوّل دائم. يُرسَل «إرسال بريد تجريبي» مباشرةً دائمًا.', + }, + }, + actions: { + test: { label: 'إرسال بريد تجريبي' }, + }, + }, + sms: { + title: 'إرسال الرسائل النصية', + description: 'إعداد مزوّد الرسائل النصية لتسجيل الدخول برمز OTP والدعوات والإشعارات.', + groups: { + provider: { title: 'المزوّد', description: 'اختر طريقة إرسال هذا النظام للرسائل النصية الصادرة.' }, + aliyun: { title: 'Aliyun SMS' }, + twilio: { title: 'Twilio' }, + limits: { + title: 'حدود الإنفاق', + description: 'يحدّ من حجم الرسائل النصية الصادرة من هذا النشر. الرسائل النصية قناة مدفوعة، وكل إرسال له تكلفة حقيقية.', + }, + }, + keys: { + provider: { + label: 'المزوّد', + options: { log: 'لا شيء (تسجيل فقط — دون إرسال فعلي)', aliyun: 'Aliyun SMS (阿里云短信)', twilio: 'Twilio' }, + }, + aliyun_access_key_id: { label: 'AccessKey ID' }, + aliyun_access_key_secret: { label: 'AccessKey Secret' }, + aliyun_sign_name: { label: 'اسم التوقيع (短信签名)' }, + aliyun_template_code: { + label: 'رمز القالب الافتراضي (短信模板)', + help: 'يُستخدم عندما لا يحدد الإرسال قالبًا صريحًا. يتيح قالب عام بمتغير ${content} واحد إرسال رسائل إشعارات عامة.', + }, + twilio_account_sid: { label: 'Account SID' }, + twilio_auth_token: { label: 'رمز المصادقة (Auth token)' }, + twilio_from_number: { + label: 'رقم المرسِل', + help: 'المرسِل بصيغة E.164، مثل +15005550006. إمّا هذا وإمّا Messaging Service SID.', + }, + twilio_messaging_service_sid: { label: 'Messaging Service SID' }, + daily_quota: { + label: 'حد الإرسال اليومي', + help: 'الحد الأقصى للرسائل النصية التي يرسلها هذا النشر في اليوم بتوقيت UTC، عبر تسجيل الدخول برمز OTP والدعوات والإشعارات. القيمة 0 تعني بلا حد. يُرفض الإرسال الذي يتجاوز الحد حتى الساعة 00:00 UTC.', + }, + }, + actions: { + test: { label: 'إرسال رسالة نصية تجريبية' }, + }, + }, + branding: { + title: 'العلامة التجارية', + description: 'الاسم والشعار واللون الذي تعرضه وحدة التحكم وصفحة تسجيل الدخول. القيمة غير المضبوطة ترجع إلى العلامة التجارية للنشر نفسه.', + groups: { + identity: { title: 'الهوية' }, + appearance: { title: 'المظهر' }, + }, + keys: { + workspace_name: { + label: 'اسم المنتج', + help: 'اسم المنتج في وحدة التحكم وصفحة تسجيل الدخول وعنوان المتصفح، وفي رسائل تسجيل الدخول وتطبيق المصادقة وتسجيل الدخول عبر MCP. اتركه فارغًا لاستخدام الاسم الذي أُعِدّ به هذا النشر. هذا ليس اسم المؤسسة نفسها، وهو «الاسم» في سجل المؤسسة.', + }, + accent_color: { + label: 'لون التمييز', + help: 'لون سمة المتصفح والتطبيق المثبّت. يحتفظ كل تطبيق بألوانه الخاصة.', + }, + logo_url: { + label: 'رابط الشعار', + help: 'يظهر في صفحة تسجيل الدخول وشاشة التحميل ورأس الصفحة الرئيسية. مثال: https://…/logo.svg', + }, + }, + }, + company: { + title: 'الشركة', + description: 'هوية الكيان القانوني — الاسم المسجَّل والعنوان والأرقام الضريبية وجهة الاتصال الرئيسية.', + groups: { + identity: { title: 'الهوية' }, + address: { title: 'العنوان المسجَّل' }, + contact: { title: 'جهة الاتصال' }, + }, + keys: { + legal_name: { label: 'الاسم القانوني', help: 'الاسم القانوني المسجَّل للمؤسسة كما يظهر في العقود والفواتير.' }, + registration_number: { label: 'رقم التسجيل', help: 'رقم تسجيل الشركة أو تأسيسها (مثل EIN).' }, + tax_id: { label: 'الرقم الضريبي / ضريبة القيمة المضافة', help: 'يظهر في الفواتير (مثل VAT وGST وABN).' }, + address_line1: { label: 'سطر العنوان 1' }, + address_line2: { label: 'سطر العنوان 2' }, + city: { label: 'المدينة' }, + state: { label: 'الولاية / المقاطعة' }, + postal_code: { label: 'الرمز البريدي' }, + country: { + label: 'البلد', + help: 'بلد العنوان المسجَّل. اكتب اسم البلد واختره من القائمة.', + options: countryOptionLabels('ar'), + }, + phone: { label: 'الهاتف', help: 'يُفضَّل تنسيق E.164، مثل +1 415 555 0100.' }, + website: { label: 'الموقع الإلكتروني', help: 'مثال: https://example.com' }, + primary_contact_name: { label: 'اسم جهة الاتصال الرئيسية' }, + primary_contact_email: { label: 'بريد جهة الاتصال الرئيسية', help: 'مثال: ops@example.com' }, + }, + }, + localization: { + title: 'الترجمة والإعدادات المحلية', + description: 'المنطقة الزمنية واللغة والبلد والعملة والسنة المالية الافتراضية.', + groups: { + region: { title: 'المنطقة' }, + finance: { title: 'المالية' }, + }, + keys: { + timezone: { + label: 'المنطقة الزمنية الافتراضية', + help: 'تحدد معنى «اليوم» في الصيغ، وكيف تجمّع التقارير التواريخ، وكيف تُعرض التواريخ والأوقات. تقبل أي اسم منطقة زمنية من IANA (مثل Asia/Dubai). هذه هي المنطقة الزمنية السارية للمؤسسة: حقل «المنطقة الزمنية» في سجل المؤسسة لا يغيّرها.', + options: { + UTC: 'UTC', + 'America/Los_Angeles': '(UTC−08/−07) لوس أنجلوس', + 'America/Denver': '(UTC−07/−06) دنفر', + 'America/Chicago': '(UTC−06/−05) شيكاغو', + 'America/New_York': '(UTC−05/−04) نيويورك', + 'America/Sao_Paulo': '(UTC−03) ساو باولو', + 'Europe/London': '(UTC±00/+01) لندن', + 'Europe/Paris': '(UTC+01/+02) باريس', + 'Europe/Berlin': '(UTC+01/+02) برلين', + 'Europe/Moscow': '(UTC+03) موسكو', + 'Asia/Dubai': '(UTC+04) دبي', + 'Asia/Kolkata': '(UTC+05:30) كولكاتا', + 'Asia/Singapore': '(UTC+08) سنغافورة', + 'Asia/Shanghai': '(UTC+08) شنغهاي', + 'Asia/Tokyo': '(UTC+09) طوكيو', + 'Australia/Sydney': '(UTC+10/+11) سيدني', + 'Pacific/Auckland': '(UTC+12/+13) أوكلاند', + }, + }, + locale: { label: 'اللغة الافتراضية', help: 'لغة BCP-47 لكتالوجات الرسائل وتنسيق الأرقام والتواريخ.' }, + default_country: { + label: 'البلد الافتراضي', + help: 'يُستخدم قيمةً افتراضية للعناوين والهواتف. اكتب اسم البلد واختره من القائمة.', + options: countryOptionLabels('ar'), + }, + currency: { + label: 'العملة الافتراضية', + help: 'رمز ISO 4217 يُطبَّق عندما لا يحدد حقل العملة رمزه الخاص. اتركه غير مضبوط لعرض المبالغ التي بلا رمز أرقامًا عادية.', + options: { + USD: 'USD — دولار أمريكي', + EUR: 'EUR — يورو', + GBP: 'GBP — جنيه إسترليني', + JPY: 'JPY — ين ياباني', + CNY: 'CNY — يوان صيني', + INR: 'INR — روبية هندية', + AUD: 'AUD — دولار أسترالي', + CAD: 'CAD — دولار كندي', + BRL: 'BRL — ريال برازيلي', + }, + }, + fiscal_year_start: { + label: 'بداية السنة المالية', + help: 'الشهر الأول من السنة المالية — يحدد «هذا الربع / هذه السنة المالية» في التقارير.', + options: { + january: 'يناير', + february: 'فبراير', + march: 'مارس', + april: 'أبريل', + may: 'مايو', + june: 'يونيو', + july: 'يوليو', + august: 'أغسطس', + september: 'سبتمبر', + october: 'أكتوبر', + november: 'نوفمبر', + december: 'ديسمبر', + }, + }, + }, + }, + auth: { + title: 'المصادقة', + description: 'عناصر التحكم في تسجيل الدخول والتسجيل وميزات المصادقة المدمجة.', + groups: { + email_password: { + title: 'البريد الإلكتروني وكلمة المرور', + description: 'التحكم في تسجيل الدخول المحلي بالبريد الإلكتروني وكلمة المرور وفي التسجيل الذاتي.', + }, + membership: { + title: 'العضوية', + description: 'ما الذي ينضم إليه المستخدم الجديد. يُضبط مع التسجيل الذاتي أعلاه.', + }, + audience: { + title: 'الجمهور', + description: 'من يمكنه أن يصبح مستخدمًا لتطبيقات هذا النظام. نطاقات البريد المسموح بها تفرض التحقق من البريد دائمًا. الوضع «مفتوح» يفرضه ما لم يُعطِّله النشر؛ ولا يمكن لقيمة محفوظة في وحدة التحكم هذه تعطيله. تُقبل الدعوات والمستخدمون الذين ينشئهم المسؤول وتزويد SCIM وتسجيل الدخول الموحَّد للمؤسسات (SSO) في كل الأوضاع.', + }, + password_policy: { + title: 'سياسة كلمات المرور', + description: 'حدود الطول التي يفرضها مزوّد المصادقة عند التسجيل وإعادة تعيين كلمة المرور.', + }, + anti_abuse: { + title: 'مكافحة إساءة الاستخدام', + description: 'الحماية من هجمات القوة الغاشمة: قفل الحساب لكل هوية وتحديد معدل الطلبات لكل عنوان IP على نقاط نهاية المصادقة.', + }, + multi_factor: { title: 'متعدد العوامل', description: 'إلزام الأعضاء بحماية حساباتهم بتطبيق مصادقة (TOTP).' }, + sessions: { title: 'الجلسات', description: 'مدة بقاء جلسة تسجيل الدخول صالحة.' }, + network: { title: 'الشبكة', description: 'تقييد الأماكن التي يمكن للمستخدمين المصادقة منها.' }, + api_keys: { + title: 'مفاتيح API', + description: 'مدة صلاحية مفاتيح API الشخصية. يُرفض المفتاح الذي انتهت صلاحيته أو ظل دون استخدام بعد فترة عدم النشاط أو أُلغي، برمز خطأ خاص به، والعلاج دائمًا مفتاح جديد.', + }, + social: { + title: 'تسجيل الدخول عبر الشبكات الاجتماعية', + description: 'إعداد مزوّد تسجيل الدخول المدمج عبر Google. بيانات الاعتماد المضبوطة في إعدادات الخادم تبقى لها الأولوية.', + }, + }, + keys: { + email_password_enabled: { + label: 'تفعيل تسجيل الدخول بالبريد الإلكتروني وكلمة المرور', + confirmOffText: 'سيحتاج من يسجّلون الدخول بكلمة مرور إلى طريقة أخرى للدخول.', + }, + signup_enabled: { + label: 'السماح بالتسجيل بالبريد الإلكتروني', + help: 'يفتح نموذج التسجيل بالبريد الإلكتروني وكلمة المرور. ومن يقبلهم يحدده «جمهور التسجيل الذاتي» أدناه: في وضع «بالدعوة فقط» لا يُقبل إلا عنوان لديه دعوة معلّقة؛ وفي «نطاقات البريد المسموح بها» تُقبل النطاقات المسموح بها والعناوين المدعوّة؛ وفي «مفتوح» يُقبل أي شخص. الإيقاف يغلق النموذج في كل الأوضاع، بما في ذلك الدعوات.', + }, + require_email_verification: { label: 'طلب التحقق من البريد الإلكتروني' }, + membership_policy: { + label: 'عضوية المستخدم الجديد', + help: '«تلقائي» يربط كل مستخدم جديد بالمؤسسة الافتراضية. «بالدعوة فقط» يمنح العضوية بإجراء صريح فقط — إنشاء مؤسسة، أو قبول دعوة، أو إضافته من قِبل مسؤول، أو التزويد الفوري عبر SSO.', + options: { + auto: 'الانضمام إلى المؤسسة الافتراضية تلقائيًا', + 'invite-only': 'بالدعوة فقط — لا انضمام تلقائي أبدًا', + }, + }, + audience_posture: { + label: 'جمهور التسجيل الذاتي', + help: '«بالدعوة فقط» يغلق التسجيل الذاتي: لا يصبح الأشخاص مستخدمين إلا عندما يدعوهم مسؤول أو ينشئهم أو يستوردهم، أو عبر تزويد الدليل (SCIM) أو تسجيل الدخول الموحَّد للمؤسسات. «نطاقات البريد المسموح بها» يفتحه للنطاقات المدرجة أدناه. «مفتوح» يقبل أي شخص. يحتاج كلٌّ من «نطاقات البريد المسموح بها» و«مفتوح» إلى ضبط مجموعة أذونات التسجيل الذاتي أدناه. «نطاقات البريد المسموح بها» يتطلب التحقق من البريد دائمًا. «مفتوح» يتطلبه ما لم تُعطِّله إعدادات الخادم؛ ويُرفض تعطيله من وحدة التحكم هذه في وضع «مفتوح».', + options: { + invite_only: 'بالدعوة فقط — دون تسجيل ذاتي (افتراضي)', + email_domain: 'نطاقات البريد المسموح بها فقط', + open: 'مفتوح — يمكن لأي شخص التسجيل ذاتيًا', + }, + }, + audience_allowed_email_domains: { + label: 'نطاقات البريد المسموح بها', + help: 'نطاقات مجردة، واحد في كل سطر أو مفصولة بفواصل (مثل acme.com). المطابقة تامة ولا تراعي حالة الأحرف؛ تحتاج النطاقات الفرعية إلى إدخالات خاصة بها؛ ولا أحرف بدل.', + }, + audience_self_registration_permission_set: { + label: 'مجموعة أذونات التسجيل الذاتي', + help: 'اسم مجموعة الأذونات التي يحصل عليها كل مستخدم سجّل ذاتيًا. مجموعة الأعضاء القياسية مناسبة؛ وتُرفض مجموعة المسؤول الكاملة. مطلوب كلما سمح الجمهور بالتسجيل الذاتي.', + }, + password_min_length: { label: 'الحد الأدنى لطول كلمة المرور' }, + password_max_length: { + label: 'الحد الأقصى لطول كلمة المرور', + help: 'يحمي من حجب الخدمة عبر تجزئة كلمات مرور طويلة جدًا.', + }, + password_reject_breached: { + label: 'رفض كلمات المرور المسرَّبة', + help: 'حظر كلمات المرور الموجودة في مجموعات التسريبات العامة عبر Have I Been Pwned (فحص نطاق بإخفاء الهوية k؛ لا تُرسَل كلمة المرور كاملةً أبدًا).', + }, + password_require_complexity: { + label: 'طلب كلمات مرور معقدة', + help: 'إلزام كلمات المرور بالجمع بين فئات أحرف (كبيرة وصغيرة وأرقام ورموز) عند التسجيل وتغيير كلمة المرور أو إعادة تعيينها.', + }, + password_min_classes: { + label: 'الحد الأدنى لفئات الأحرف', + help: 'كم فئة من الفئات الأربع (كبيرة / صغيرة / رقم / رمز) يجب أن تتضمنها كلمة المرور.', + }, + password_history_count: { + label: 'سجل كلمات المرور (دون إعادة استخدام)', + help: 'منع إعادة استخدام هذا العدد من كلمات المرور السابقة عند التغيير أو إعادة التعيين. القيمة 0 تعطّل الفحص.', + }, + password_expiry_days: { + label: 'انتهاء صلاحية كلمة المرور (أيام)', + help: 'فرض تغيير كلمة المرور بعد هذا العدد من الأيام. القيمة 0 تعطّل الانتهاء. أثناء انتهاء الصلاحية يُمنع المستخدم من الوصول إلى البيانات حتى يغيّر كلمة المرور.', + }, + lockout_threshold: { + label: 'حد قفل الحساب', + help: 'قفل الحساب بعد هذا العدد من محاولات تسجيل الدخول الفاشلة المتتالية — كلمات المرور الخاطئة ورموز التحقق الثنائي الخاطئة على حدٍّ سواء. أثناء القفل يُرفض تسجيل الدخول حتى ببيانات اعتماد صحيحة. القيمة 0 تعطّل قفل مرحلة كلمة المرور؛ ويحتفظ التحقق الثنائي حينها بحده المدمج البالغ 10 محاولات كل 15 دقيقة، لأنه آخر فحص قبل إصدار الجلسة.', + }, + lockout_duration_minutes: { + label: 'مدة القفل (دقائق)', + help: 'مدة بقاء الحساب مقفلًا بعد تجاوز الحد، في أي من مرحلتي تسجيل الدخول.', + }, + rate_limit_max: { + label: 'تحديد معدل المصادقة: الحد الأقصى للطلبات', + help: 'الحد الأقصى للطلبات لكل IP في كل نافذة زمنية إلى نقاط نهاية تسجيل الدخول / التسجيل / إعادة تعيين كلمة المرور.', + }, + rate_limit_window_seconds: { + label: 'تحديد معدل المصادقة: النافذة (ثوانٍ)', + help: 'النافذة المنزلقة التي يُحسب خلالها حد الطلبات أعلاه.', + }, + mfa_required: { + label: 'طلب المصادقة متعددة العوامل', + help: 'يُمنع المستخدمون الذين لم يسجّلوا تطبيق مصادقة من الوصول إلى البيانات بعد انتهاء فترة السماح. تفعيل هذا الخيار يشغّل أيضًا ميزة التحقق الثنائي ليتمكن المستخدمون من التسجيل.', + }, + mfa_grace_period_days: { + label: 'فترة السماح لـ MFA (أيام)', + help: 'المدة التي يمكن للمستخدمين خلالها تأجيل التسجيل قبل الحظر الصارم. القيمة 0 تحظر فورًا.', + }, + session_expiry_days: { + label: 'مدة الجلسة (أيام)', + help: 'تنتهي الجلسة بعد هذا العدد من الأيام من تسجيل الدخول.', + }, + session_refresh_days: { + label: 'حد التجديد (أيام)', + help: 'تُمدَّد الجلسة النشطة عندما يتجاوز عمرها هذه المدة.', + }, + session_idle_timeout_minutes: { + label: 'مهلة الخمول (دقائق)', + help: 'تسجيل خروج المستخدم بعد هذا العدد من دقائق عدم النشاط. القيمة 0 تعطّل ذلك.', + }, + session_absolute_max_hours: { + label: 'المدة المطلقة للجلسة (ساعات)', + help: 'فرض إعادة المصادقة بعد هذا العدد من الساعات من تسجيل الدخول، بغض النظر عن النشاط. القيمة 0 تعطّل ذلك.', + }, + max_concurrent_sessions_per_user: { + label: 'الحد الأقصى للجلسات المتزامنة لكل مستخدم', + help: 'تحديد عدد الجلسات المتزامنة لكل مستخدم؛ ويُسجَّل خروج الأقدم عند تجاوز الحد. 0 = بلا حد.', + }, + allowed_ip_ranges: { + label: 'نطاقات IP المسموح بها', + help: 'نطاقات CIDR أو عناوين IP محددة (واحد في كل سطر أو مفصولة بفواصل)، مثل 203.0.113.0/24. عند ضبطها يُرفض تسجيل الدخول من خارج هذه النطاقات، وكذلك أي طلب لا يستطيع الخادم تحديد عنوان عميله. فارغ = بلا تقييد. العنوان الذي يُحكم عليه هو عنوان مصدر الاتصال؛ وخلف وكيل عكسي يجب ضبط الخادم ليثق بذلك الوكيل حتى يُحكم على عنوان العميل الأصلي بدلًا منه.', + }, + api_key_default_expiry_days: { + label: 'المدة الافتراضية للمفتاح (أيام)', + help: 'المفتاح الذي يُنشأ دون تاريخ انتهاء تنتهي صلاحيته بعد هذا العدد من الأيام من إنشائه. ولا يتجاوز أبدًا المدة القصوى أدناه.', + }, + api_key_max_expiry_days: { + label: 'المدة القصوى للمفتاح (أيام)', + help: 'أبعد تاريخ انتهاء يمكن أن يطلبه مفتاح جديد، محسوبًا من إنشائه. يُرفض الأطول؛ ويُقبل هذا العدد من الأيام تمامًا.', + }, + api_key_inactivity_days: { + label: 'انتهاء بسبب عدم النشاط (أيام)', + help: 'يُرفض المفتاح الذي لا يوجد له استخدام مسجَّل طوال هذا العدد من الأيام. لا يُلغى؛ أنشئ مفتاحًا جديدًا. المفتاح الذي لم يُستخدم قط يُحسب من إنشائه، أو من بدء السياسة أدناه إن كان أقدم. القيمة 0 تعطّل الانتهاء بسبب عدم النشاط.', + }, + api_key_policy_started_at: { + label: 'بدء سياسة المفاتيح في', + help: 'يُسجَّل تلقائيًا (UTC، ISO 8601) في أول مرة يعمل فيها الخادم بهذه السياسة. المفاتيح التي أُنشئت قبله دون تاريخ انتهاء تنتهي بعده بسنة؛ والمفاتيح التي لا استخدام مسجَّل لها تحسب عدم نشاطها منه. تغييره ينقل كلا التاريخين للمفاتيح التي لم تحصل عليهما بعد.', + }, + google_enabled: { + label: 'تفعيل تسجيل الدخول عبر Google', + help: 'يتيح تسجيل الدخول عبر Google متى توفر معرّف العميل وسر العميل معًا: محفوظَين أدناه، أو مضبوطَين في إعدادات الخادم التي لها الأولوية على القيم المحفوظة. وحتى يتوفرا لا تعرض صفحة تسجيل الدخول زر Google، حتى مع تشغيل هذا الخيار. الإيقاف يسحب تسجيل الدخول عبر Google حتى لو ضبطهما الخادم. أنشئ عميل OAuth في Google Cloud Console.', + }, + google_client_id: { + label: 'معرّف عميل Google', + help: 'معرّف عميل OAuth من Google Cloud Console. ويمكن ضبطه أيضًا في إعدادات الخادم.', + }, + google_client_secret: { label: 'سر عميل Google', help: 'يُخزَّن مشفّرًا. ويمكن ضبطه أيضًا في إعدادات الخادم.' }, + }, + }, + storage: { + title: 'تخزين الملفات', + description: 'الواجهة الخلفية المستخدمة للمرفقات والتصديرات وملفات المستخدمين المرفوعة.', + groups: { + adapter: { title: 'الواجهة الخلفية', description: 'اختر مكان تخزين الملفات المرفوعة.' }, + local: { title: 'محلي' }, + s3: { title: 'S3' }, + limits: { title: 'الحدود' }, + }, + keys: { + adapter: { + label: 'المحوّل', + options: { local: 'نظام الملفات المحلي', s3: 'S3 / متوافق مع S3' }, + }, + local_root: { + label: 'الدليل الجذر', + help: 'مسار نظام الملفات الذي تُخزَّن الملفات تحته. تُحلّ المسارات النسبية انطلاقًا من دليل العمل الحالي (CWD) للخادم.', + }, + s3_bucket: { + label: 'الحاوية (Bucket)', + help: 'حاوية المضيف المشتركة. تُفصل ملفات كل بيئة عبر البادئة projects//.', + }, + s3_region: { label: 'المنطقة', help: 'مثال: us-east-1' }, + s3_endpoint: { + label: 'نقطة النهاية', + help: 'نقطة نهاية مخصصة للمزوّدات المتوافقة مع S3 (R2 وMinIO وWasabi). اتركها فارغة لـ AWS S3.', + }, + s3_access_key_id: { label: 'معرّف مفتاح الوصول' }, + s3_secret_access_key: { label: 'مفتاح الوصول السري' }, + s3_force_path_style: { + label: 'فرض عناوين URL بنمط المسار', + help: 'فعّله لـ MinIO ومعظم المزوّدات المتوافقة مع S3؛ وعطّله لـ AWS S3.', + }, + presigned_ttl: { label: 'مدة صلاحية عنوان URL الموقَّع مسبقًا (ثوانٍ)' }, + session_ttl: { label: 'مدة صلاحية جلسة الرفع (ثوانٍ)', help: 'مدة بقاء جلسة الرفع المجزَّأ قابلة للاستئناف.' }, + max_upload_mb: { label: 'الحد الأقصى لحجم الرفع (ميغابايت)' }, + }, + actions: { + test: { label: 'اختبار الاتصال' }, + }, + banners: { + adapter_switch_warning: { + title: 'لا تُنقل الملفات الموجودة', + text: 'تبديل المحوّل لا ينقل الملفات الموجودة — الملفات المرفوعة عبر المحوّل السابق يتعذّر الوصول إليها عبر المحوّل الجديد.', + }, + }, + }, + ai: { + title: 'الذكاء الاصطناعي والتضمين (Embedder)', + description: 'مزوّد LLM والنموذج وبيانات الاعتماد وإعداد التضمين التي تستخدمها خدمات الذكاء الاصطناعي والمعرفة في المنصة.', + groups: { + provider: { + title: 'المزوّد', + description: 'اختر الواجهة الخلفية لـ LLM. وضع Memory يعيد صدى المدخلات — مفيد للاختبارات ولا يصلح للإنتاج أبدًا.', + }, + gateway: { + title: 'Vercel AI Gateway', + description: 'موجّه متعدد المزوّدات. يُكتب النموذج بصيغة provider/model، مثل openai/gpt-4o.', + }, + openai: { title: 'OpenAI' }, + anthropic: { title: 'Anthropic' }, + google: { title: 'Google' }, + deepseek: { + title: 'DeepSeek', + description: 'واجهة API متوافقة مع OpenAI على https://api.deepseek.com. يُملأ Base URL تلقائيًا.', + }, + dashscope: { + title: '阿里通义 DashScope', + description: 'نقطة نهاية متوافقة مع OpenAI على dashscope.aliyuncs.com/compatible-mode/v1. يُملأ Base URL تلقائيًا.', + }, + cloudflare: { + title: 'Cloudflare AI Gateway', + description: 'يستخدم نقطة النهاية /compat، لذا يُكتب النموذج بصيغة provider/model (مثل openai/gpt-4o-mini وanthropic/claude-3-5-sonnet وdeepseek/deepseek-chat). ملاحظة: لا يدعم Cloudflare /compat النماذج alibaba/qwen* — استخدم مزوّد DashScope لـ Qwen.', + }, + siliconflow: { + title: '硅基流动 SiliconFlow', + description: 'نقطة نهاية متوافقة مع OpenAI على api.siliconflow.cn/v1. يُملأ Base URL تلقائيًا.', + }, + openrouter: { + title: 'OpenRouter', + description: 'موجّه متعدد المزوّدات على openrouter.ai/api/v1. يُملأ Base URL تلقائيًا.', + }, + titles: { title: 'عناوين المحادثات', description: 'إنشاء عنوان ملخّص قصير للمحادثات الجديدة تلقائيًا.' }, + defaults: { + title: 'الإعدادات الافتراضية للتوليد', + description: 'تُطبَّق عندما لا يحدد طلب الوكيل أو الدردشة قيمته الخاصة.', + }, + observability: { title: 'قابلية المراقبة' }, + embedder: { + title: 'Embedder', + description: 'مزوّد تحويل النص → متجه الذي تستخدمه مصادر المعرفة وRAG. مستقل عن مزوّد الدردشة أعلاه.', + }, + }, + keys: { + provider: { + label: 'المزوّد', + options: { + memory: 'Memory (صدى — للاختبار فقط)', + gateway: 'Vercel AI Gateway', + openai: 'OpenAI', + anthropic: 'Anthropic', + google: 'Google Generative AI', + deepseek: 'DeepSeek (متوافق مع OpenAI)', + dashscope: '阿里通义 DashScope (متوافق مع OpenAI)', + cloudflare: 'Cloudflare AI Gateway (متوافق مع OpenAI)', + siliconflow: '硅基流动 SiliconFlow (متوافق مع OpenAI)', + openrouter: 'OpenRouter (متوافق مع OpenAI)', + }, + }, + gateway_model: { + label: 'نموذج Gateway', + help: 'يُستخدم بدل النموذج المضبوط في إعدادات الخادم. مثال: openai/gpt-4o', + }, + gateway_api_key: { label: 'مفتاح API لـ Gateway', help: 'اختياري — مطلوب فقط إذا فرضت Gateway المصادقة.' }, + openai_api_key: { + label: 'مفتاح API لـ OpenAI', + help: 'يُستخدم بدل المفتاح المضبوط في إعدادات الخادم. يُخزَّن مشفّرًا.', + }, + openai_model: { label: 'النموذج', help: 'معرّف النموذج الافتراضي. تتقدّم التجاوزات الخاصة بكل وكيل.' }, + openai_base_url: { + label: 'Base URL', + help: 'تجاوز لـ Azure OpenAI أو البوابات المستضافة ذاتيًا. اتركه فارغًا لـ api.openai.com.', + }, + anthropic_api_key: { + label: 'مفتاح API لـ Anthropic', + help: 'يُستخدم بدل المفتاح المضبوط في إعدادات الخادم. يُخزَّن مشفّرًا.', + }, + anthropic_model: { label: 'النموذج' }, + google_api_key: { + label: 'مفتاح API لـ Google', + help: 'يُستخدم بدل المفتاح المضبوط في إعدادات الخادم. يُخزَّن مشفّرًا.', + }, + google_model: { label: 'النموذج' }, + deepseek_api_key: { label: 'مفتاح API لـ DeepSeek', help: 'sk-... — يصدر من platform.deepseek.com.' }, + deepseek_model: { label: 'النموذج', help: 'أمثلة: deepseek-chat (V3)، وdeepseek-reasoner (التفكير R1).' }, + dashscope_api_key: { label: 'مفتاح API لـ DashScope', help: 'sk-... — يصدر من dashscope.console.aliyun.com.' }, + dashscope_model: { label: 'النموذج' }, + cloudflare_account_id: { + label: 'معرّف حساب Cloudflare', + help: 'المعرّف السداسي العشري المكوَّن من 32 حرفًا في عنوان URL للوحة تحكم Cloudflare.', + }, + cloudflare_gateway_id: { + label: 'معرّف Gateway', + help: 'اسم البوابة المضبوط في Cloudflare → AI Gateway. القيمة الافتراضية "default".', + }, + cloudflare_api_key: { + label: 'رمز Cloudflare AI Gateway', + help: 'يصدر في AI Gateway ← تبويب "API tokens" (cfut_… أو sk_…).', + }, + cloudflare_model: { label: 'النموذج' }, + siliconflow_api_key: { label: 'مفتاح API لـ SiliconFlow' }, + siliconflow_model: { + label: 'النموذج', + help: 'أمثلة: Qwen/Qwen2.5-72B-Instruct، وdeepseek-ai/DeepSeek-V3، وmeta-llama/Meta-Llama-3.1-8B-Instruct.', + }, + openrouter_api_key: { label: 'مفتاح API لـ OpenRouter', help: 'sk-or-...' }, + openrouter_model: { + label: 'النموذج', + help: 'الصيغة: provider/model (مثل anthropic/claude-3.5-sonnet وdeepseek/deepseek-chat).', + }, + title_generation_enabled: { label: 'تلخيص عناوين المحادثات تلقائيًا' }, + title_max_length: { + label: 'الحد الأقصى لطول العنوان (أحرف)', + help: 'حد صارم لطول العنوان المُنشأ. أي زيادة تُقتطع على الخادم.', + }, + temperature: { label: 'درجة الحرارة', help: '0 = حتمي، 2 = إبداعي جدًا.' }, + max_tokens: { label: 'الحد الأقصى لرموز الإخراج', help: 'حد صارم لعدد الرموز المولَّدة في كل استجابة.' }, + request_timeout_ms: { label: 'مهلة الطلب (ملّي ثانية)' }, + trace_enabled: { + label: 'تسجيل التتبعات', + help: 'الاحتفاظ بسجل لكل طلب ذكاء اصطناعي واستجابته، حتى يمكن فحص المحادثة وإعادة تشغيلها عند حدوث خطأ.', + }, + log_prompts: { + label: 'تسجيل المطالبات كاملة', + help: 'تضمين المطالبات المعروضة (لا البيانات الوصفية فقط) في صفوف التتبع.', + }, + embedder_provider: { + label: 'المزوّد', + options: { + none: 'معطّل (دون تضمينات)', + openai: 'OpenAI', + azure: 'Azure OpenAI', + dashscope: '阿里通义 DashScope', + zhipu: '智谱 BigModel', + siliconflow: '硅基流动 SiliconFlow', + doubao: '火山引擎 Doubao', + minimax: 'MiniMax', + ollama: 'Ollama (محلي)', + custom: 'مخصص (متوافق مع OpenAI)', + }, + }, + embedder_api_key: { + label: 'مفتاح API لـ Embedder', + help: 'رمز Bearer يُرسَل في ترويسة Authorization. مع Ollama تصلح أي قيمة غير فارغة.', + }, + embedder_model: { + label: 'النموذج', + help: 'أمثلة — OpenAI: text-embedding-3-small · 阿里通义: text-embedding-v3 · 智谱: embedding-3 · 硅基流动: BAAI/bge-m3 · Ollama: bge-m3', + }, + embedder_base_url: { + label: 'Base URL', + help: 'جذر نقطة النهاية (دون /embeddings). يُملأ تلقائيًا من الإعداد المسبق؛ ويمكن تجاوزه للوكلاء أو البوابات المستضافة ذاتيًا.', + }, + embedder_dimensions: { + label: 'الأبعاد', + help: 'تجاوز عدد أبعاد الإخراج (لنماذج Matryoshka فقط). اتركه فارغًا لاستخدام القيمة الافتراضية للنموذج.', + }, + embedder_batch_size: { + label: 'حجم الدفعة', + help: 'عدد المقاطع المرسلة إلى مزوّد التضمين في طلب واحد. قلّله إذا بلغت حدود المعدل أو الحجم لدى المزوّد.', + }, + }, + actions: { + test: { label: 'اختبار الاتصال' }, + test_embedder: { label: 'اختبار التضمين (Embedder)' }, + reset: { label: 'إعادة التعيين إلى افتراضيات البيئة' }, + }, + banners: { + log_prompts_warning: { + title: 'قد تحتوي المطالبات على بيانات شخصية', + text: 'قد يسرّب بيانات شخصية — عطّله في البيئات الخاضعة للتنظيم.', + }, + }, + }, + knowledge: { + title: 'المعرفة', + description: 'الواجهة الخلفية لمخزن المتجهات لـ RAG ومصادر المعرفة.', + groups: { + adapter: { title: 'الواجهة الخلفية', description: 'اختر مكان تخزين مقاطع المستندات ومتجهاتها.' }, + turso: { title: 'Turso / libSQL', description: 'يعمل مع Turso المُدار أو ملف محلي أو في الذاكرة.' }, + ragflow: { + title: 'RAGFlow', + description: 'نشر RAGFlow خارجي. راجع https://ragflow.io لتعليمات الاستضافة الذاتية.', + }, + indexing: { + title: 'افتراضيات الفهرسة', + description: 'القيمة المضبوطة على مصدر المعرفة نفسه تتقدّم على هذه الافتراضيات.', + }, + permissions: { title: 'الأذونات' }, + }, + keys: { + adapter: { + label: 'المحوّل', + options: { + memory: 'في الذاكرة (للتطوير / الاختبار فقط)', + turso: 'Turso / libSQL (سحابي أو محلي)', + ragflow: 'RAGFlow (خارجي)', + }, + }, + turso_url: { + label: 'عنوان URL للاتصال', + help: 'أمثلة: libsql://your-tenant.turso.io · file:./.objectstack/knowledge.db · :memory:', + }, + turso_auth_token: { label: 'رمز المصادقة', help: 'مطلوب فقط لعناوين Turso المُدارة.' }, + ragflow_base_url: { label: 'Base URL', help: 'مثال: http://localhost:9380' }, + ragflow_api_key: { label: 'مفتاح API' }, + ragflow_default_dataset: { + label: 'معرّف مجموعة البيانات الافتراضية', + help: 'يُستخدم عندما لا يحدد مصدر المعرفة مجموعة بيانات RAGFlow الخاصة به.', + }, + chunk_target: { + label: 'الحجم المستهدف للمقطع (أحرف)', + help: 'حد مرن لحجم المقطع قبل أن يبدأ التقسيم المراعي للرموز.', + }, + chunk_overlap: { + label: 'تداخل المقاطع (أحرف)', + help: 'الأحرف المحتفَظ بها من المقطع السابق كي يبقى السياق عبر الحدّ الفاصل.', + }, + over_fetch: { + label: 'مضاعف الجلب الزائد', + help: 'يجلب البحث هذا المضاعف من عدد النتائج المطلوبة، حتى يبقى منها ما يكفي لإرجاعه بعد تصفيتها حسب تفاصيل المستند.', + }, + enforce_rls: { label: 'فرض RLS عند البحث', help: 'إعادة فحص كل نتيجة وفق أذونات المستدعي على مستوى السجل.' }, + }, + actions: { + test: { label: 'اختبار الاتصال' }, + }, + banners: { + adapter_switch_warning: { + title: 'لا تُنقل الفهارس الموجودة', + text: 'تبديل المحوّل لا ينقل الفهارس الموجودة — المستندات المفهرسة عبر المحوّل السابق يتعذّر الوصول إليها حتى تُعاد فهرستها.', + }, + enforce_rls_warning: { + title: 'أبقِ RLS مفعّلًا في الإنتاج', + text: 'التعطيل يتخطى ضمانة المنصة الفريدة ضد تسرّب بيانات مخزن المتجهات — أبقِه مفعّلًا في الإنتاج.', + }, + }, + }, + lifecycle: { + title: 'دورة حياة البيانات', + description: 'مدة الاحتفاظ ببيانات النظام: تجاوز فترات الاحتفاظ، وتحديد عدد الصفوف لكل جدول، وتلقّي تنبيه عندما ينمو جدول على نحو غير متوقع. تطبّق المنصة هذه الإعدادات في جولة التنظيف الدورية.', + keys: { + enabled: { + label: 'فرض سياسات دورة الحياة', + help: 'المفتاح الرئيسي لجولة التنظيف الدورية. الإيقاف = تتوقف سياسات الاحتفاظ/TTL/التدوير المعلنة عن التطبيق (وتعود البيانات إلى النمو بلا حد).', + }, + retention_overrides: { + label: 'تجاوزات الاحتفاظ', + help: 'تجاوزات النافذة لكل كائن: { "": { "maxAge": "1y", "expireAfter": "30d" } }. تستخدم المدد h/d/w/y. تُضبط لكل مؤسسة، فتحتفظ المؤسسة الخاضعة للتنظيم بسنوات بينما تحتفظ مؤسسة التطوير بأيام. يُتجاهَل التجاوز الأقصر من الحد الأدنى الذي تحتاجه ميزة في المنصة (مثل نافذة كشف التكرار في قائمة انتظار المهام) ويُسجَّل خطأً، وتظل النافذة الافتراضية سارية.', + }, + quotas: { + label: 'حصص الصفوف', + help: 'الحد الأقصى لعدد الصفوف لكل كائن: { "": 1000000 }. يؤدي التجاوز إلى تنبيه حوكمة — ولا تحذف الحصص أبدًا ما يتجاوز السياسة المعلنة.', + }, + quota_defaults: { + label: 'الحصص الافتراضية حسب الفئة', + help: 'حصص احتياطية حسب فئة دورة الحياة: { "telemetry": 1000000, "transient": 500000, "event": 100000, "audit": 5000000 }.', + }, + growth_alert_rows: { + label: 'حد تنبيه النمو (صفوف لكل جولة)', + help: 'التنبيه عندما ينمو جدول معلَن له دورة حياة بأكثر من هذا العدد من الصفوف بين جولتين. القيمة 0 تعطّل تنبيهات النمو.', + }, + }, + }, + }, +}; diff --git a/packages/services/service-settings/src/translations/fr.ts b/packages/services/service-settings/src/translations/fr.ts new file mode 100644 index 00000000000..f4aa79f7bda --- /dev/null +++ b/packages/services/service-settings/src/translations/fr.ts @@ -0,0 +1,752 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { PlatformTranslationData } from '@objectstack/spec/system'; +import { countryOptionLabels } from '../manifests/company.manifest.js'; + +/** + * Français (fr) — built-in settings manifest translations. + */ +export const fr: PlatformTranslationData = { + settingsCommon: { + sourceLabels: { + env: 'Environnement', + global: 'Global', + tenant: 'Organisation', + user: 'Utilisateur', + default: 'Par défaut', + }, + }, + settings: { + mail: { + title: 'Envoi des e-mails', + description: 'Configuration SMTP et du fournisseur d’e-mails transactionnels.', + groups: { + provider: { title: 'Fournisseur', description: 'Choisissez comment ce système envoie les e-mails sortants.' }, + smtp: { title: 'SMTP' }, + api_key: { title: 'Clé API' }, + from_address: { title: 'Adresse d’expédition' }, + delivery: { title: 'Envoi', description: 'Comment les e-mails sortants sont remis au fournisseur.' }, + }, + keys: { + provider: { + label: 'Fournisseur', + help: 'Seuls les fournisseurs par lesquels ce serveur peut réellement envoyer sont listés. SendGrid et Amazon SES se configurent en SMTP.', + options: { + smtp: 'SMTP', + resend: 'Resend', + postmark: 'Postmark', + log: 'Aucun (journalisation seule — aucun envoi réel)', + }, + }, + smtp_host: { label: 'Hôte', help: 'Exemple : smtp.example.com' }, + smtp_port: { label: 'Port' }, + smtp_secure: { label: 'Utiliser TLS' }, + smtp_user: { label: 'Nom d’utilisateur' }, + smtp_password: { label: 'Mot de passe' }, + api_key: { label: 'Clé API' }, + from_email: { label: 'E-mail d’expédition', help: 'Exemple : no-reply@example.com' }, + from_name: { + label: 'Nom d’expéditeur', + help: 'Laissez vide pour utiliser le nom du produit (Image de marque) ou le nom avec lequel ce déploiement a été configuré.', + }, + queue_delivery: { + label: 'Envoi via une file durable', + help: 'Chaque message est confié à la file de tâches au lieu d’être envoyé directement : un envoi en échec est relancé par un worker avec un délai croissant et survit à un redémarrage. Nécessite la capacité de file avec un adaptateur durable. « Envoyer un e-mail de test » envoie toujours directement.', + }, + }, + actions: { + test: { label: 'Envoyer un e-mail de test' }, + }, + }, + sms: { + title: 'Envoi des SMS', + description: 'Configuration du fournisseur SMS pour la connexion par OTP, les invitations et les notifications.', + groups: { + provider: { title: 'Fournisseur', description: 'Choisissez comment ce système envoie les SMS sortants.' }, + aliyun: { title: 'Aliyun SMS' }, + twilio: { title: 'Twilio' }, + limits: { + title: 'Limites de dépense', + description: 'Plafonne le volume de SMS sortants du déploiement. Le SMS est un canal payant et chaque envoi coûte de l’argent réel.', + }, + }, + keys: { + provider: { + label: 'Fournisseur', + options: { + log: 'Aucun (journalisation seule — aucun envoi réel)', + aliyun: 'Aliyun SMS (阿里云短信)', + twilio: 'Twilio', + }, + }, + aliyun_access_key_id: { label: 'AccessKey ID' }, + aliyun_access_key_secret: { label: 'AccessKey Secret' }, + aliyun_sign_name: { label: 'Nom de signature (短信签名)' }, + aliyun_template_code: { + label: 'Code de modèle par défaut (短信模板)', + help: 'Utilisé lorsqu’un envoi ne précise aucun modèle. Un modèle générique avec une seule variable ${content} permet les SMS de notification génériques.', + }, + twilio_account_sid: { label: 'Account SID' }, + twilio_auth_token: { label: 'Jeton d’authentification' }, + twilio_from_number: { + label: 'Numéro d’expédition', + help: 'Expéditeur au format E.164, par ex. +15005550006. Soit ce numéro, soit un Messaging Service SID.', + }, + twilio_messaging_service_sid: { label: 'Messaging Service SID' }, + daily_quota: { + label: 'Limite d’envoi quotidienne', + help: 'Nombre maximal de SMS que ce déploiement peut envoyer par jour UTC, connexion par OTP, invitations et notifications comprises. 0 signifie aucune limite. Les envois au-delà de la limite sont refusés jusqu’à 00:00 UTC.', + }, + }, + actions: { + test: { label: 'Envoyer un SMS de test' }, + }, + }, + branding: { + title: 'Image de marque', + description: 'Le nom, le logo et la couleur affichés par la console et la page de connexion. Une valeur non définie reprend l’image de marque du déploiement.', + groups: { + identity: { title: 'Identité' }, + appearance: { title: 'Apparence' }, + }, + keys: { + workspace_name: { + label: 'Nom du produit', + help: 'Le nom du produit dans la console, sur la page de connexion et dans le titre du navigateur, ainsi que dans les e-mails de connexion, l’application d’authentification et la connexion MCP. Laissez vide pour utiliser le nom avec lequel ce déploiement a été configuré. Ce n’est pas le nom de l’organisation elle-même, qui est le Nom de la fiche Organisation.', + }, + accent_color: { + label: 'Couleur d’accent', + help: 'La couleur de thème du navigateur et de l’application installée. Chaque application garde ses propres couleurs.', + }, + logo_url: { + label: 'URL du logo', + help: 'Affiché sur la page de connexion, l’écran de chargement et l’en-tête d’accueil. Exemple : https://…/logo.svg', + }, + }, + }, + company: { + title: 'Entreprise', + description: 'Identité de l’entité juridique — raison sociale, adresse, identifiants fiscaux et contact principal.', + groups: { + identity: { title: 'Identité' }, + address: { title: 'Adresse du siège' }, + contact: { title: 'Contact' }, + }, + keys: { + legal_name: { + label: 'Raison sociale', + help: 'Nom légal enregistré de l’organisation, tel qu’il figure sur les contrats et les factures.', + }, + registration_number: { + label: 'Numéro d’immatriculation', + help: 'Numéro d’immatriculation ou de constitution de l’entreprise (par ex. EIN).', + }, + tax_id: { label: 'N° fiscal / TVA', help: 'Affiché sur les factures (par ex. TVA, GST, ABN).' }, + address_line1: { label: 'Adresse (ligne 1)' }, + address_line2: { label: 'Adresse (ligne 2)' }, + city: { label: 'Ville' }, + state: { label: 'État / Province' }, + postal_code: { label: 'Code postal' }, + country: { + label: 'Pays', + help: 'Pays de l’adresse du siège. Saisissez un nom de pays et choisissez-le dans la liste.', + options: countryOptionLabels('fr'), + }, + phone: { label: 'Téléphone', help: 'Format E.164 recommandé, par ex. +33 1 55 55 01 00.' }, + website: { label: 'Site web', help: 'Exemple : https://example.com' }, + primary_contact_name: { label: 'Nom du contact principal' }, + primary_contact_email: { label: 'E-mail du contact principal', help: 'Exemple : ops@example.com' }, + }, + }, + localization: { + title: 'Localisation', + description: 'Fuseau horaire, langue, pays, devise et exercice fiscal par défaut.', + groups: { + region: { title: 'Région' }, + finance: { title: 'Finance' }, + }, + keys: { + timezone: { + label: 'Fuseau horaire par défaut', + help: 'Détermine ce que « aujourd’hui » signifie dans les formules, comment les rapports regroupent les dates et comment les dates et heures s’affichent. Accepte tout nom de fuseau IANA (par ex. Europe/Paris). C’est le fuseau en vigueur pour l’organisation : le champ Fuseau horaire de la fiche Organisation ne le change pas.', + options: { + UTC: 'UTC', + 'America/Los_Angeles': '(UTC−08/−07) Los Angeles', + 'America/Denver': '(UTC−07/−06) Denver', + 'America/Chicago': '(UTC−06/−05) Chicago', + 'America/New_York': '(UTC−05/−04) New York', + 'America/Sao_Paulo': '(UTC−03) São Paulo', + 'Europe/London': '(UTC±00/+01) Londres', + 'Europe/Paris': '(UTC+01/+02) Paris', + 'Europe/Berlin': '(UTC+01/+02) Berlin', + 'Europe/Moscow': '(UTC+03) Moscou', + 'Asia/Dubai': '(UTC+04) Dubaï', + 'Asia/Kolkata': '(UTC+05:30) Calcutta', + 'Asia/Singapore': '(UTC+08) Singapour', + 'Asia/Shanghai': '(UTC+08) Shanghai', + 'Asia/Tokyo': '(UTC+09) Tokyo', + 'Australia/Sydney': '(UTC+10/+11) Sydney', + 'Pacific/Auckland': '(UTC+12/+13) Auckland', + }, + }, + locale: { + label: 'Langue par défaut', + help: 'Langue BCP-47 des catalogues de messages et du formatage des nombres et des dates.', + }, + default_country: { + label: 'Pays par défaut', + help: 'Sert de valeur par défaut pour les adresses et les téléphones. Saisissez un nom de pays et choisissez-le dans la liste.', + options: countryOptionLabels('fr'), + }, + currency: { + label: 'Devise par défaut', + help: 'Code ISO 4217 appliqué lorsqu’un champ devise n’indique pas le sien. Laissez vide pour afficher les montants sans code comme de simples nombres.', + options: { + USD: 'USD — Dollar américain', + EUR: 'EUR — Euro', + GBP: 'GBP — Livre sterling', + JPY: 'JPY — Yen japonais', + CNY: 'CNY — Yuan chinois', + INR: 'INR — Roupie indienne', + AUD: 'AUD — Dollar australien', + CAD: 'CAD — Dollar canadien', + BRL: 'BRL — Réal brésilien', + }, + }, + fiscal_year_start: { + label: 'Début de l’exercice fiscal', + help: 'Premier mois de l’exercice fiscal — détermine « ce trimestre / cet exercice » dans les rapports.', + options: { + january: 'Janvier', + february: 'Février', + march: 'Mars', + april: 'Avril', + may: 'Mai', + june: 'Juin', + july: 'Juillet', + august: 'Août', + september: 'Septembre', + october: 'Octobre', + november: 'Novembre', + december: 'Décembre', + }, + }, + }, + }, + auth: { + title: 'Authentification', + description: 'Connexion, inscription et réglages des fonctions d’authentification intégrées.', + groups: { + email_password: { + title: 'E-mail et mot de passe', + description: 'Gère la connexion locale par e-mail et mot de passe et l’inscription en libre-service.', + }, + membership: { + title: 'Adhésion', + description: 'Ce que rejoint un utilisateur nouvellement créé. Va de pair avec l’inscription en libre-service ci-dessus.', + }, + audience: { + title: 'Public', + description: 'Qui peut devenir utilisateur des applications de ce système. Les domaines e-mail autorisés imposent toujours la vérification de l’e-mail. Ouvert l’impose aussi, sauf si le déploiement la désactive ; une valeur enregistrée dans cette console ne le peut pas. Les invitations, les utilisateurs créés par un administrateur, le provisionnement SCIM et le SSO d’entreprise sont admis quel que soit le mode.', + }, + password_policy: { + title: 'Politique de mot de passe', + description: 'Bornes de longueur imposées par le fournisseur d’authentification à l’inscription et à la réinitialisation du mot de passe.', + }, + anti_abuse: { + title: 'Anti-abus', + description: 'Protection contre la force brute : verrouillage du compte par identité et limitation du débit par IP sur les points de terminaison d’authentification.', + }, + multi_factor: { + title: 'Multifacteur', + description: 'Exige que les membres protègent leur compte avec une application d’authentification (TOTP).', + }, + sessions: { title: 'Sessions', description: 'Durée de validité d’une session ouverte.' }, + network: { + title: 'Réseau', + description: 'Restreint les lieux depuis lesquels les utilisateurs peuvent s’authentifier.', + }, + api_keys: { + title: 'Clés API', + description: 'Durée de vie des clés API personnelles. Une clé expirée, inutilisée au-delà de la période d’inactivité ou révoquée est refusée avec son propre code d’erreur, et le remède est toujours une nouvelle clé.', + }, + social: { + title: 'Connexion sociale', + description: 'Configure le fournisseur de connexion Google intégré. Les identifiants définis dans la configuration du serveur restent prioritaires.', + }, + }, + keys: { + email_password_enabled: { + label: 'Activer la connexion par e-mail et mot de passe', + confirmOffText: 'Les personnes qui se connectent avec un mot de passe auront besoin d’un autre moyen d’accès.', + }, + signup_enabled: { + label: 'Autoriser l’inscription par e-mail', + help: 'Ouvre le formulaire d’inscription par e-mail et mot de passe. Qui il admet dépend du Public de l’inscription libre ci-dessous : avec Sur invitation uniquement, seule une adresse ayant une invitation en attente ; avec Domaines e-mail autorisés, les domaines autorisés et les adresses invitées ; avec Ouvert, tout le monde. Désactivé, le formulaire est fermé quel que soit le public, invitations comprises.', + }, + require_email_verification: { label: 'Exiger la vérification de l’e-mail' }, + membership_policy: { + label: 'Adhésion des nouveaux utilisateurs', + help: 'Automatique rattache chaque nouvel utilisateur à l’organisation par défaut. Sur invitation uniquement n’accorde l’adhésion que par un acte explicite — créer une organisation, accepter une invitation, être ajouté par un administrateur ou être provisionné à la volée par SSO.', + options: { + auto: 'Rejoindre automatiquement l’organisation par défaut', + 'invite-only': 'Sur invitation uniquement — jamais d’adhésion automatique', + }, + }, + audience_posture: { + label: 'Public de l’inscription libre', + help: 'Sur invitation uniquement ferme l’inscription libre : on ne devient utilisateur que lorsqu’un administrateur vous invite, vous crée ou vous importe, ou via le provisionnement d’annuaire (SCIM) ou l’authentification unique d’entreprise. Domaines e-mail autorisés l’ouvre aux domaines listés ci-dessous. Ouvert admet tout le monde. Domaines e-mail autorisés comme Ouvert nécessitent l’ensemble d’autorisations d’inscription libre ci-dessous. Domaines e-mail autorisés exige toujours la vérification de l’e-mail. Ouvert l’exige sauf si la configuration du serveur la désactive ; la désactiver dans cette console est refusé en mode Ouvert.', + options: { + invite_only: 'Sur invitation uniquement — pas d’inscription libre (par défaut)', + email_domain: 'Domaines e-mail autorisés uniquement', + open: 'Ouvert — tout le monde peut s’inscrire', + }, + }, + audience_allowed_email_domains: { + label: 'Domaines e-mail autorisés', + help: 'Domaines nus, un par ligne ou séparés par des virgules (par ex. acme.com). La correspondance est exacte et insensible à la casse ; les sous-domaines nécessitent leur propre entrée ; pas de caractères génériques.', + }, + audience_self_registration_permission_set: { + label: 'Ensemble d’autorisations de l’inscription libre', + help: 'Nom de l’ensemble d’autorisations reçu par chaque utilisateur inscrit librement. L’ensemble membre standard convient ; l’ensemble administrateur complet est refusé. Obligatoire dès que le public permet l’inscription libre.', + }, + password_min_length: { label: 'Longueur minimale du mot de passe' }, + password_max_length: { + label: 'Longueur maximale du mot de passe', + help: 'Protège contre le déni de service par hachage de mots de passe très longs.', + }, + password_reject_breached: { + label: 'Refuser les mots de passe compromis', + help: 'Bloque les mots de passe présents dans les fuites publiques via Have I Been Pwned (vérification par plage en k-anonymat ; le mot de passe n’est jamais envoyé en entier).', + }, + password_require_complexity: { + label: 'Exiger des mots de passe complexes', + help: 'Exige que les mots de passe mélangent des classes de caractères (majuscules, minuscules, chiffres, symboles) à l’inscription et lors du changement ou de la réinitialisation.', + }, + password_min_classes: { + label: 'Classes de caractères minimales', + help: 'Combien des quatre classes (majuscule / minuscule / chiffre / symbole) un mot de passe doit contenir.', + }, + password_history_count: { + label: 'Historique des mots de passe (pas de réutilisation)', + help: 'Empêche de réutiliser ce nombre de mots de passe précédents lors d’un changement ou d’une réinitialisation. 0 désactive la vérification.', + }, + password_expiry_days: { + label: 'Expiration du mot de passe (jours)', + help: 'Impose un changement de mot de passe après ce nombre de jours. 0 désactive l’expiration. Tant que le mot de passe est expiré, l’utilisateur n’a plus accès aux données jusqu’à ce qu’il le change.', + }, + lockout_threshold: { + label: 'Seuil de verrouillage du compte', + help: 'Verrouille un compte après ce nombre d’échecs de connexion consécutifs — mauvais mots de passe comme mauvais codes à deux facteurs. Pendant le verrouillage, la connexion est refusée même avec les bons identifiants. 0 désactive le verrouillage à l’étape du mot de passe ; la vérification à deux facteurs garde alors sa limite intégrée de 10 tentatives par 15 minutes, puisqu’elle est le dernier contrôle avant l’émission d’une session.', + }, + lockout_duration_minutes: { + label: 'Durée du verrouillage (minutes)', + help: 'Durée pendant laquelle un compte reste verrouillé une fois le seuil franchi, à l’une ou l’autre étape de connexion.', + }, + rate_limit_max: { + label: 'Limitation d’authentification : requêtes max.', + help: 'Nombre maximal de requêtes par IP et par fenêtre vers les points de terminaison de connexion / inscription / réinitialisation du mot de passe.', + }, + rate_limit_window_seconds: { + label: 'Limitation d’authentification : fenêtre (secondes)', + help: 'Fenêtre glissante sur laquelle le plafond de requêtes ci-dessus est compté.', + }, + mfa_required: { + label: 'Exiger l’authentification multifacteur', + help: 'Les utilisateurs sans application d’authentification enregistrée perdent l’accès aux données à la fin de leur délai de grâce. L’activer active aussi la fonction à deux facteurs pour que les utilisateurs puissent s’enregistrer.', + }, + mfa_grace_period_days: { + label: 'Délai de grâce MFA (jours)', + help: 'Durée pendant laquelle les utilisateurs peuvent différer l’enregistrement avant le blocage. 0 bloque immédiatement.', + }, + session_expiry_days: { + label: 'Durée de session (jours)', + help: 'Une session expire ce nombre de jours après la connexion.', + }, + session_refresh_days: { + label: 'Seuil de renouvellement (jours)', + help: 'Une session active est prolongée lorsqu’elle est plus ancienne que cette durée.', + }, + session_idle_timeout_minutes: { + label: 'Délai d’inactivité (minutes)', + help: 'Déconnecte un utilisateur après ce nombre de minutes d’inactivité. 0 désactive.', + }, + session_absolute_max_hours: { + label: 'Durée de session absolue (heures)', + help: 'Impose une nouvelle authentification ce nombre d’heures après la connexion, quelle que soit l’activité. 0 désactive.', + }, + max_concurrent_sessions_per_user: { + label: 'Sessions simultanées max. par utilisateur', + help: 'Plafonne les sessions ouvertes simultanément par utilisateur ; les plus anciennes sont déconnectées au-delà. 0 = illimité.', + }, + allowed_ip_ranges: { + label: 'Plages IP autorisées', + help: 'Plages CIDR ou IP exactes (une par ligne, ou séparées par des virgules), par ex. 203.0.113.0/24. Une fois définies, la connexion depuis l’extérieur de ces plages est refusée, tout comme toute requête dont le serveur ne peut établir l’adresse cliente. Vide = aucune restriction. L’adresse jugée est celle d’où vient la connexion ; derrière un proxy inverse, le serveur doit être configuré pour faire confiance à ce proxy afin que l’adresse cliente d’origine soit jugée à la place.', + }, + api_key_default_expiry_days: { + label: 'Durée de vie par défaut des clés (jours)', + help: 'Une clé créée sans date d’expiration expire ce nombre de jours après sa création. Jamais plus longtemps que la durée maximale ci-dessous.', + }, + api_key_max_expiry_days: { + label: 'Durée de vie maximale des clés (jours)', + help: 'Date d’expiration la plus lointaine qu’une nouvelle clé peut demander, comptée depuis sa création. Au-delà, elle est refusée ; exactement ce nombre de jours est accepté.', + }, + api_key_inactivity_days: { + label: 'Expiration pour inactivité (jours)', + help: 'Une clé sans utilisation enregistrée depuis ce nombre de jours est refusée. Elle n’est pas révoquée ; créez une nouvelle clé. Une clé jamais utilisée compte depuis sa création, ou depuis le début de la politique ci-dessous si elle est plus ancienne. 0 désactive l’expiration pour inactivité.', + }, + api_key_policy_started_at: { + label: 'Début de la politique des clés', + help: 'Enregistré automatiquement (UTC, ISO 8601) la première fois que le serveur tourne avec cette politique. Les clés créées avant sans date d’expiration expirent un an après ; les clés sans utilisation enregistrée comptent leur inactivité à partir de là. Le modifier déplace ces deux dates pour les clés qui ne les ont pas encore reçues.', + }, + google_enabled: { + label: 'Activer la connexion Google', + help: 'Propose la connexion Google dès qu’un ID client et un secret client sont tous deux disponibles : enregistrés ci-dessous, ou définis dans la configuration du serveur, qui l’emporte sur les valeurs enregistrées. Tant que les deux ne sont pas disponibles, la page de connexion n’affiche aucun bouton Google, même si ce réglage est activé. Désactivé, la connexion Google est retirée même si le serveur définit les deux. Créez le client OAuth dans Google Cloud Console.', + }, + google_client_id: { + label: 'ID client Google', + help: 'ID client OAuth issu de Google Cloud Console. Il peut aussi être défini dans la configuration du serveur.', + }, + google_client_secret: { + label: 'Secret client Google', + help: 'Stocké chiffré. Il peut aussi être défini dans la configuration du serveur.', + }, + }, + }, + storage: { + title: 'Stockage des fichiers', + description: 'Backend utilisé pour les pièces jointes, les exports et les fichiers envoyés par les utilisateurs.', + groups: { + adapter: { title: 'Backend', description: 'Choisissez où sont stockés les fichiers envoyés.' }, + local: { title: 'Local' }, + s3: { title: 'S3' }, + limits: { title: 'Limites' }, + }, + keys: { + adapter: { + label: 'Adaptateur', + options: { local: 'Système de fichiers local', s3: 'S3 / compatible S3' }, + }, + local_root: { + label: 'Répertoire racine', + help: 'Chemin du système de fichiers sous lequel les fichiers sont stockés. Les chemins relatifs sont résolus depuis le CWD du serveur.', + }, + s3_bucket: { + label: 'Bucket', + help: 'Bucket partagé de l’hôte. Les fichiers de chaque environnement sont séparés par le préfixe projects//.', + }, + s3_region: { label: 'Région', help: 'Exemple : us-east-1' }, + s3_endpoint: { + label: 'Point de terminaison', + help: 'Point de terminaison personnalisé pour les fournisseurs compatibles S3 (R2, MinIO, Wasabi). Laissez vide pour AWS S3.', + }, + s3_access_key_id: { label: 'ID de clé d’accès' }, + s3_secret_access_key: { label: 'Clé d’accès secrète' }, + s3_force_path_style: { + label: 'Forcer les URL de style chemin', + help: 'À activer pour MinIO et la plupart des fournisseurs compatibles S3 ; à désactiver pour AWS S3.', + }, + presigned_ttl: { label: 'Durée de validité des URL présignées (secondes)' }, + session_ttl: { + label: 'Durée de validité de la session d’envoi (secondes)', + help: 'Durée pendant laquelle une session d’envoi par morceaux reste reprenable.', + }, + max_upload_mb: { label: 'Taille d’envoi max. (Mo)' }, + }, + actions: { + test: { label: 'Tester la connexion' }, + }, + banners: { + adapter_switch_warning: { + title: 'Les fichiers existants ne sont pas migrés', + text: 'Changer d’adaptateur ne migre pas les fichiers existants — les fichiers envoyés avec l’adaptateur précédent deviennent inaccessibles via le nouveau.', + }, + }, + }, + ai: { + title: 'IA et Embedder', + description: 'Fournisseur LLM, modèle, identifiants et configuration de l’Embedder utilisés par les services d’IA et de connaissances de la plateforme.', + groups: { + provider: { + title: 'Fournisseur', + description: 'Choisissez le backend LLM. Le mode Memory renvoie l’entrée telle quelle — utile pour les tests, jamais en production.', + }, + gateway: { + title: 'Vercel AI Gateway', + description: 'Routeur multifournisseur. Le modèle s’écrit provider/model, par ex. openai/gpt-4o.', + }, + openai: { title: 'OpenAI' }, + anthropic: { title: 'Anthropic' }, + google: { title: 'Google' }, + deepseek: { + title: 'DeepSeek', + description: 'API compatible OpenAI sur https://api.deepseek.com. La Base URL est renseignée automatiquement.', + }, + dashscope: { + title: '阿里通义 DashScope', + description: 'Point de terminaison compatible OpenAI sur dashscope.aliyuncs.com/compatible-mode/v1. La Base URL est renseignée automatiquement.', + }, + cloudflare: { + title: 'Cloudflare AI Gateway', + description: 'Utilise le point de terminaison /compat : le modèle s’écrit donc provider/model (par ex. openai/gpt-4o-mini, anthropic/claude-3-5-sonnet, deepseek/deepseek-chat). Remarque : alibaba/qwen* n’est PAS pris en charge par Cloudflare /compat — utilisez le fournisseur DashScope pour Qwen.', + }, + siliconflow: { + title: '硅基流动 SiliconFlow', + description: 'Point de terminaison compatible OpenAI sur api.siliconflow.cn/v1. La Base URL est renseignée automatiquement.', + }, + openrouter: { + title: 'OpenRouter', + description: 'Routeur multifournisseur sur openrouter.ai/api/v1. La Base URL est renseignée automatiquement.', + }, + titles: { + title: 'Titres des conversations', + description: 'Génère automatiquement un court titre de synthèse pour les nouvelles conversations.', + }, + defaults: { + title: 'Valeurs de génération par défaut', + description: 'Appliquées lorsqu’une requête d’agent ou de chat ne précise pas sa propre valeur.', + }, + observability: { title: 'Observabilité' }, + embedder: { + title: 'Embedder', + description: 'Fournisseur texte → vecteur utilisé par les sources de connaissances et le RAG. Indépendant du fournisseur de chat ci-dessus.', + }, + }, + keys: { + provider: { + label: 'Fournisseur', + options: { + memory: 'Memory (écho — tests uniquement)', + gateway: 'Vercel AI Gateway', + openai: 'OpenAI', + anthropic: 'Anthropic', + google: 'Google Generative AI', + deepseek: 'DeepSeek (compatible OpenAI)', + dashscope: '阿里通义 DashScope (compatible OpenAI)', + cloudflare: 'Cloudflare AI Gateway (compatible OpenAI)', + siliconflow: '硅基流动 SiliconFlow (compatible OpenAI)', + openrouter: 'OpenRouter (compatible OpenAI)', + }, + }, + gateway_model: { + label: 'Modèle Gateway', + help: 'Utilisé à la place du modèle défini dans la configuration du serveur. Exemple : openai/gpt-4o', + }, + gateway_api_key: { + label: 'Clé API Gateway', + help: 'Facultatif — nécessaire uniquement si la passerelle impose une authentification.', + }, + openai_api_key: { + label: 'Clé API OpenAI', + help: 'Utilisée à la place de la clé définie dans la configuration du serveur. Stockée chiffrée.', + }, + openai_model: { label: 'Modèle', help: 'ID du modèle par défaut. Les surcharges par agent sont prioritaires.' }, + openai_base_url: { + label: 'Base URL', + help: 'Surcharge pour Azure OpenAI ou des passerelles auto-hébergées. Laissez vide pour api.openai.com.', + }, + anthropic_api_key: { + label: 'Clé API Anthropic', + help: 'Utilisée à la place de la clé définie dans la configuration du serveur. Stockée chiffrée.', + }, + anthropic_model: { label: 'Modèle' }, + google_api_key: { + label: 'Clé API Google', + help: 'Utilisée à la place de la clé définie dans la configuration du serveur. Stockée chiffrée.', + }, + google_model: { label: 'Modèle' }, + deepseek_api_key: { label: 'Clé API DeepSeek', help: 'sk-... — délivrée sur platform.deepseek.com.' }, + deepseek_model: { + label: 'Modèle', + help: 'Exemples : deepseek-chat (V3), deepseek-reasoner (raisonnement R1).', + }, + dashscope_api_key: { label: 'Clé API DashScope', help: 'sk-... — délivrée sur dashscope.console.aliyun.com.' }, + dashscope_model: { label: 'Modèle' }, + cloudflare_account_id: { + label: 'ID de compte Cloudflare', + help: 'L’ID hexadécimal de 32 caractères présent dans l’URL de votre tableau de bord Cloudflare.', + }, + cloudflare_gateway_id: { + label: 'ID de la passerelle', + help: 'Nom de passerelle configuré dans Cloudflare → AI Gateway. Par défaut « default ».', + }, + cloudflare_api_key: { + label: 'Jeton Cloudflare AI Gateway', + help: 'Délivré dans AI Gateway → onglet « API tokens » (cfut_… ou sk_…).', + }, + cloudflare_model: { label: 'Modèle' }, + siliconflow_api_key: { label: 'Clé API SiliconFlow' }, + siliconflow_model: { + label: 'Modèle', + help: 'Exemples : Qwen/Qwen2.5-72B-Instruct, deepseek-ai/DeepSeek-V3, meta-llama/Meta-Llama-3.1-8B-Instruct.', + }, + openrouter_api_key: { label: 'Clé API OpenRouter', help: 'sk-or-...' }, + openrouter_model: { + label: 'Modèle', + help: 'Format : provider/model (par ex. anthropic/claude-3.5-sonnet, deepseek/deepseek-chat).', + }, + title_generation_enabled: { label: 'Résumer automatiquement les titres des conversations' }, + title_max_length: { + label: 'Longueur max. du titre (caractères)', + help: 'Plafond strict du titre généré. Tout dépassement est tronqué côté serveur.', + }, + temperature: { label: 'Température', help: '0 = déterministe, 2 = très créatif.' }, + max_tokens: { label: 'Jetons de sortie max.', help: 'Plafond strict de jetons générés par réponse.' }, + request_timeout_ms: { label: 'Délai d’expiration de la requête (ms)' }, + trace_enabled: { + label: 'Enregistrer les traces', + help: 'Conserve une trace de chaque requête IA et de sa réponse, pour pouvoir inspecter et rejouer une conversation en cas de problème.', + }, + log_prompts: { + label: 'Journaliser les prompts complets', + help: 'Inclut les prompts rendus (et pas seulement les métadonnées) dans les lignes de trace.', + }, + embedder_provider: { + label: 'Fournisseur', + options: { + none: 'Désactivé (aucun embedding)', + openai: 'OpenAI', + azure: 'Azure OpenAI', + dashscope: '阿里通义 DashScope', + zhipu: '智谱 BigModel', + siliconflow: '硅基流动 SiliconFlow', + doubao: '火山引擎 Doubao', + minimax: 'MiniMax', + ollama: 'Ollama (local)', + custom: 'Personnalisé (compatible OpenAI)', + }, + }, + embedder_api_key: { + label: 'Clé API Embedder', + help: 'Jeton Bearer envoyé dans l’en-tête Authorization. Pour Ollama, toute valeur non vide convient.', + }, + embedder_model: { + label: 'Modèle', + help: 'Exemples — OpenAI: text-embedding-3-small · 阿里通义: text-embedding-v3 · 智谱: embedding-3 · 硅基流动: BAAI/bge-m3 · Ollama: bge-m3', + }, + embedder_base_url: { + label: 'Base URL', + help: 'Racine du point de terminaison (sans /embeddings). Renseignée depuis le préréglage ; à surcharger pour un proxy ou une passerelle auto-hébergée.', + }, + embedder_dimensions: { + label: 'Dimensions', + help: 'Surcharge la dimensionnalité de sortie (modèles Matryoshka uniquement). Laissez vide pour la valeur par défaut du modèle.', + }, + embedder_batch_size: { + label: 'Taille de lot', + help: 'Nombre de morceaux envoyés au fournisseur d’embeddings par requête. Réduisez-le si vous atteignez les limites de débit ou de taille du fournisseur.', + }, + }, + actions: { + test: { label: 'Tester la connexion' }, + test_embedder: { label: 'Tester l’Embedder' }, + reset: { label: 'Rétablir les valeurs de l’environnement' }, + }, + banners: { + log_prompts_warning: { + title: 'Les prompts peuvent contenir des données personnelles', + text: 'Risque de fuite de données personnelles — à désactiver dans les environnements réglementés.', + }, + }, + }, + knowledge: { + title: 'Connaissances', + description: 'Backend de stockage vectoriel pour le RAG et les sources de connaissances.', + groups: { + adapter: { + title: 'Backend', + description: 'Choisissez où sont stockés les morceaux de documents et leurs vecteurs.', + }, + turso: { title: 'Turso / libSQL', description: 'Fonctionne avec Turso géré, un fichier local ou en mémoire.' }, + ragflow: { + title: 'RAGFlow', + description: 'Déploiement RAGFlow externe. Voir https://ragflow.io pour l’auto-hébergement.', + }, + indexing: { + title: 'Valeurs d’indexation par défaut', + description: 'Une valeur définie sur la source de connaissances elle-même l’emporte sur ces valeurs par défaut.', + }, + permissions: { title: 'Autorisations' }, + }, + keys: { + adapter: { + label: 'Adaptateur', + options: { + memory: 'En mémoire (dév. / test uniquement)', + turso: 'Turso / libSQL (cloud ou local)', + ragflow: 'RAGFlow (externe)', + }, + }, + turso_url: { + label: 'URL de connexion', + help: 'Exemples : libsql://your-tenant.turso.io · file:./.objectstack/knowledge.db · :memory:', + }, + turso_auth_token: { + label: 'Jeton d’authentification', + help: 'Nécessaire uniquement pour les URL Turso gérées.', + }, + ragflow_base_url: { label: 'Base URL', help: 'Exemple : http://localhost:9380' }, + ragflow_api_key: { label: 'Clé API' }, + ragflow_default_dataset: { + label: 'ID du jeu de données par défaut', + help: 'Utilisé lorsqu’une source de connaissances ne désigne pas son propre jeu de données RAGFlow.', + }, + chunk_target: { + label: 'Taille cible des morceaux (caractères)', + help: 'Plafond souple de taille des morceaux avant que le découpage par jetons n’intervienne.', + }, + chunk_overlap: { + label: 'Chevauchement des morceaux (caractères)', + help: 'Caractères repris du morceau précédent pour que le contexte franchisse la frontière.', + }, + over_fetch: { + label: 'Multiplicateur de sur-récupération', + help: 'La recherche récupère ce multiple du nombre de résultats demandés, pour qu’il en reste assez à renvoyer après le filtrage selon les détails des documents.', + }, + enforce_rls: { + label: 'Appliquer la RLS à la recherche', + help: 'Revérifie chaque résultat selon les autorisations au niveau des enregistrements de l’appelant.', + }, + }, + actions: { + test: { label: 'Tester la connexion' }, + }, + banners: { + adapter_switch_warning: { + title: 'Les index existants ne sont pas migrés', + text: 'Changer d’adaptateur ne migre PAS les index existants — les documents indexés avec l’adaptateur précédent deviennent inaccessibles jusqu’à leur réindexation.', + }, + enforce_rls_warning: { + title: 'Laissez la RLS activée en production', + text: 'La désactiver contourne la protection propre à la plateforme contre les fuites de données du stockage vectoriel — laissez-la activée en production.', + }, + }, + }, + lifecycle: { + title: 'Cycle de vie des données', + description: 'Durée de conservation des données système : surcharger les fenêtres de rétention, plafonner le nombre de lignes par table et être alerté quand une table grossit de façon inattendue. La plateforme les applique lors de son nettoyage périodique.', + keys: { + enabled: { + label: 'Appliquer les politiques de cycle de vie', + help: 'Interrupteur général du nettoyage périodique. Désactivé = les politiques de rétention/TTL/rotation déclarées ne sont plus appliquées (les données recommencent à croître sans limite).', + }, + retention_overrides: { + label: 'Surcharges de rétention', + help: 'Surcharges de fenêtre par objet : { "": { "maxAge": "1y", "expireAfter": "30d" } }. Les durées utilisent h/d/w/y. Définies par organisation : une organisation réglementée peut conserver des années, une organisation de développement quelques jours. Une surcharge plus courte que le minimum requis par une fonction de la plateforme (par exemple la fenêtre de détection des doublons de la file de tâches) est ignorée et journalisée comme erreur, et la fenêtre par défaut continue de s’appliquer.', + }, + quotas: { + label: 'Quotas de lignes', + help: 'Nombre maximal de lignes par objet : { "": 1000000 }. Un dépassement déclenche une alerte de gouvernance — les quotas ne suppriment jamais au-delà de la politique déclarée.', + }, + quota_defaults: { + label: 'Quotas par défaut par classe', + help: 'Quotas de repli par classe de cycle de vie : { "telemetry": 1000000, "transient": 500000, "event": 100000, "audit": 5000000 }.', + }, + growth_alert_rows: { + label: 'Seuil d’alerte de croissance (lignes par passage)', + help: 'Alerte lorsqu’une table déclarant un cycle de vie gagne plus que ce nombre de lignes entre deux passages. 0 désactive les alertes de croissance.', + }, + }, + }, + }, +}; diff --git a/packages/services/service-settings/src/translations/index.ts b/packages/services/service-settings/src/translations/index.ts index 9d4c61b2919..c219b037772 100644 --- a/packages/services/service-settings/src/translations/index.ts +++ b/packages/services/service-settings/src/translations/index.ts @@ -22,12 +22,20 @@ import { en } from './en.js'; import { zhCN } from './zh-CN.js'; import { jaJP } from './ja-JP.js'; import { esES } from './es-ES.js'; +import { zhHant } from './zh-Hant.js'; +import { ar } from './ar.js'; +import { fr } from './fr.js'; +import { ptBR } from './pt-BR.js'; -export { en, zhCN, jaJP, esES }; +export { en, zhCN, jaJP, esES, zhHant, ar, fr, ptBR }; export const settingsBuiltinTranslations: PlatformTranslationBundle = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, + 'zh-Hant': zhHant, + 'ar': ar, + 'fr': fr, + 'pt-BR': ptBR, }; diff --git a/packages/services/service-settings/src/translations/pt-BR.ts b/packages/services/service-settings/src/translations/pt-BR.ts new file mode 100644 index 00000000000..34d4f36b1a3 --- /dev/null +++ b/packages/services/service-settings/src/translations/pt-BR.ts @@ -0,0 +1,736 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { PlatformTranslationData } from '@objectstack/spec/system'; +import { countryOptionLabels } from '../manifests/company.manifest.js'; + +/** + * Português do Brasil (pt-BR) — built-in settings manifest translations. + */ +export const ptBR: PlatformTranslationData = { + settingsCommon: { + sourceLabels: { env: 'Ambiente', global: 'Global', tenant: 'Organização', user: 'Usuário', default: 'Padrão' }, + }, + settings: { + mail: { + title: 'Envio de e-mail', + description: 'Configuração de SMTP e do provedor de e-mail transacional.', + groups: { + provider: { title: 'Provedor', description: 'Escolha como este sistema envia os e-mails de saída.' }, + smtp: { title: 'SMTP' }, + api_key: { title: 'Chave de API' }, + from_address: { title: 'Endereço do remetente' }, + delivery: { title: 'Entrega', description: 'Como os e-mails de saída são entregues ao provedor.' }, + }, + keys: { + provider: { + label: 'Provedor', + help: 'Só aparecem os provedores pelos quais este servidor consegue de fato entregar. SendGrid e Amazon SES são configurados como SMTP.', + options: { + smtp: 'SMTP', + resend: 'Resend', + postmark: 'Postmark', + log: 'Nenhum (apenas log — sem entrega real)', + }, + }, + smtp_host: { label: 'Host', help: 'Exemplo: smtp.example.com' }, + smtp_port: { label: 'Porta' }, + smtp_secure: { label: 'Usar TLS' }, + smtp_user: { label: 'Nome de usuário' }, + smtp_password: { label: 'Senha' }, + api_key: { label: 'Chave de API' }, + from_email: { label: 'E-mail do remetente', help: 'Exemplo: no-reply@example.com' }, + from_name: { + label: 'Nome do remetente', + help: 'Deixe em branco para usar o nome do produto (Identidade visual) ou o nome com que esta implantação foi configurada.', + }, + queue_delivery: { + label: 'Entrega por fila durável', + help: 'Entrega cada mensagem à fila de tarefas em vez de enviá-la diretamente, para que uma entrega com falha seja repetida com espera progressiva por um worker e sobreviva a uma reinicialização. Requer a capacidade de fila com um adaptador durável. "Enviar e-mail de teste" sempre envia diretamente.', + }, + }, + actions: { + test: { label: 'Enviar e-mail de teste' }, + }, + }, + sms: { + title: 'Envio de SMS', + description: 'Configuração do provedor de SMS para login por OTP, convites e notificações.', + groups: { + provider: { title: 'Provedor', description: 'Escolha como este sistema envia os SMS de saída.' }, + aliyun: { title: 'Aliyun SMS' }, + twilio: { title: 'Twilio' }, + limits: { + title: 'Limites de gasto', + description: 'Limita o volume de SMS de saída da implantação. SMS é um canal pago e cada envio custa dinheiro de verdade.', + }, + }, + keys: { + provider: { + label: 'Provedor', + options: { log: 'Nenhum (apenas log — sem entrega real)', aliyun: 'Aliyun SMS (阿里云短信)', twilio: 'Twilio' }, + }, + aliyun_access_key_id: { label: 'AccessKey ID' }, + aliyun_access_key_secret: { label: 'AccessKey Secret' }, + aliyun_sign_name: { label: 'Nome de assinatura (短信签名)' }, + aliyun_template_code: { + label: 'Código de modelo padrão (短信模板)', + help: 'Usado quando um envio não informa um modelo explícito. Um modelo genérico com uma única variável ${content} permite SMS de notificação genéricos.', + }, + twilio_account_sid: { label: 'Account SID' }, + twilio_auth_token: { label: 'Token de autenticação' }, + twilio_from_number: { + label: 'Número do remetente', + help: 'Remetente no formato E.164, por exemplo +15005550006. Ou este ou um Messaging Service SID.', + }, + twilio_messaging_service_sid: { label: 'Messaging Service SID' }, + daily_quota: { + label: 'Limite diário de envio', + help: 'Máximo de SMS que esta implantação pode enviar por dia UTC, somando login por OTP, convites e notificações. 0 significa sem limite. Envios acima do limite são recusados até 00:00 UTC.', + }, + }, + actions: { + test: { label: 'Enviar SMS de teste' }, + }, + }, + branding: { + title: 'Identidade visual', + description: 'O nome, o logotipo e a cor exibidos no console e na página de login. Um valor não definido usa a identidade visual da própria implantação.', + groups: { + identity: { title: 'Identidade' }, + appearance: { title: 'Aparência' }, + }, + keys: { + workspace_name: { + label: 'Nome do produto', + help: 'O nome do produto no console, na página de login e no título do navegador, além dos e-mails de login, do aplicativo autenticador e do login MCP. Deixe em branco para usar o nome com que esta implantação foi configurada. Este não é o nome da própria organização, que é o Nome no registro da Organização.', + }, + accent_color: { + label: 'Cor de destaque', + help: 'A cor de tema do navegador e do aplicativo instalado. Cada aplicativo mantém as próprias cores.', + }, + logo_url: { + label: 'URL do logotipo', + help: 'Exibido na página de login, na tela de carregamento e no cabeçalho da página inicial. Exemplo: https://…/logo.svg', + }, + }, + }, + company: { + title: 'Empresa', + description: 'Identidade da pessoa jurídica — razão social, endereço, identificações fiscais e contato principal.', + groups: { + identity: { title: 'Identificação' }, + address: { title: 'Endereço registrado' }, + contact: { title: 'Contato' }, + }, + keys: { + legal_name: { + label: 'Razão social', + help: 'Nome jurídico registrado da organização, como aparece em contratos e faturas.', + }, + registration_number: { + label: 'Número de registro', + help: 'Número de registro ou constituição da empresa (por exemplo, EIN).', + }, + tax_id: { label: 'ID fiscal / IVA', help: 'Exibido nas faturas (por exemplo, VAT, GST, ABN).' }, + address_line1: { label: 'Endereço (linha 1)' }, + address_line2: { label: 'Endereço (linha 2)' }, + city: { label: 'Cidade' }, + state: { label: 'Estado / Província' }, + postal_code: { label: 'CEP' }, + country: { + label: 'País', + help: 'País do endereço registrado. Digite o nome de um país e escolha-o na lista.', + options: countryOptionLabels('pt-BR'), + }, + phone: { label: 'Telefone', help: 'Recomenda-se o formato E.164, por exemplo +55 11 5555 0100.' }, + website: { label: 'Site', help: 'Exemplo: https://example.com' }, + primary_contact_name: { label: 'Nome do contato principal' }, + primary_contact_email: { label: 'E-mail do contato principal', help: 'Exemplo: ops@example.com' }, + }, + }, + localization: { + title: 'Localização', + description: 'Fuso horário, idioma, país, moeda e ano fiscal padrão.', + groups: { + region: { title: 'Região' }, + finance: { title: 'Finanças' }, + }, + keys: { + timezone: { + label: 'Fuso horário padrão', + help: 'Define o que "hoje" significa nas fórmulas, como os relatórios agrupam datas e como datas e horas são exibidas. Aceita qualquer nome de fuso horário IANA (por exemplo, America/Sao_Paulo). Este é o fuso em vigor para a organização: o campo Fuso horário no registro da Organização não o altera.', + options: { + UTC: 'UTC', + 'America/Los_Angeles': '(UTC−08/−07) Los Angeles', + 'America/Denver': '(UTC−07/−06) Denver', + 'America/Chicago': '(UTC−06/−05) Chicago', + 'America/New_York': '(UTC−05/−04) Nova York', + 'America/Sao_Paulo': '(UTC−03) São Paulo', + 'Europe/London': '(UTC±00/+01) Londres', + 'Europe/Paris': '(UTC+01/+02) Paris', + 'Europe/Berlin': '(UTC+01/+02) Berlim', + 'Europe/Moscow': '(UTC+03) Moscou', + 'Asia/Dubai': '(UTC+04) Dubai', + 'Asia/Kolkata': '(UTC+05:30) Calcutá', + 'Asia/Singapore': '(UTC+08) Singapura', + 'Asia/Shanghai': '(UTC+08) Xangai', + 'Asia/Tokyo': '(UTC+09) Tóquio', + 'Australia/Sydney': '(UTC+10/+11) Sydney', + 'Pacific/Auckland': '(UTC+12/+13) Auckland', + }, + }, + locale: { + label: 'Idioma padrão', + help: 'Idioma BCP-47 dos catálogos de mensagens e da formatação de números e datas.', + }, + default_country: { + label: 'País padrão', + help: 'Usado como padrão para endereços e telefones. Digite o nome de um país e escolha-o na lista.', + options: countryOptionLabels('pt-BR'), + }, + currency: { + label: 'Moeda padrão', + help: 'Código ISO 4217 aplicado quando um campo de moeda não informa o seu. Deixe sem valor para exibir valores sem código como números simples.', + options: { + USD: 'USD — Dólar americano', + EUR: 'EUR — Euro', + GBP: 'GBP — Libra esterlina', + JPY: 'JPY — Iene japonês', + CNY: 'CNY — Yuan chinês', + INR: 'INR — Rupia indiana', + AUD: 'AUD — Dólar australiano', + CAD: 'CAD — Dólar canadense', + BRL: 'BRL — Real brasileiro', + }, + }, + fiscal_year_start: { + label: 'Início do ano fiscal', + help: 'Primeiro mês do ano fiscal — define "este trimestre / ano fiscal" nos relatórios.', + options: { + january: 'Janeiro', + february: 'Fevereiro', + march: 'Março', + april: 'Abril', + may: 'Maio', + june: 'Junho', + july: 'Julho', + august: 'Agosto', + september: 'Setembro', + october: 'Outubro', + november: 'Novembro', + december: 'Dezembro', + }, + }, + }, + }, + auth: { + title: 'Autenticação', + description: 'Controles de login, cadastro e recursos de autenticação integrados.', + groups: { + email_password: { + title: 'E-mail e senha', + description: 'Controla o login local por e-mail e senha e o cadastro por conta própria.', + }, + membership: { + title: 'Associação', + description: 'A que um usuário recém-criado se junta. Funciona em conjunto com o cadastro por conta própria acima.', + }, + audience: { + title: 'Público', + description: 'Quem pode se tornar usuário dos aplicativos deste sistema. Domínios de e-mail permitidos sempre exigem a verificação de e-mail. Aberto também a exige, a menos que a implantação a desative; um valor salvo neste console não pode desativá-la. Convites, usuários criados por administradores, provisionamento SCIM e SSO corporativo são aceitos em qualquer modo.', + }, + password_policy: { + title: 'Política de senhas', + description: 'Limites de tamanho impostos pelo provedor de autenticação no cadastro e na redefinição de senha.', + }, + anti_abuse: { + title: 'Antiabuso', + description: 'Proteção contra força bruta: bloqueio de conta por identidade e limite de taxa por IP nos endpoints de autenticação.', + }, + multi_factor: { + title: 'Multifator', + description: 'Exige que os membros protejam a conta com um aplicativo autenticador (TOTP).', + }, + sessions: { title: 'Sessões', description: 'Por quanto tempo uma sessão de login permanece válida.' }, + network: { title: 'Rede', description: 'Restringe de onde os usuários podem se autenticar.' }, + api_keys: { + title: 'Chaves de API', + description: 'Por quanto tempo as chaves de API pessoais valem. Uma chave expirada, sem uso além da janela de inatividade ou revogada é recusada com seu próprio código de erro, e a solução é sempre uma nova chave.', + }, + social: { + title: 'Login social', + description: 'Configura o provedor de login integrado do Google. As credenciais definidas na configuração do servidor continuam prevalecendo.', + }, + }, + keys: { + email_password_enabled: { + label: 'Ativar login por e-mail e senha', + confirmOffText: 'Quem entra com senha vai precisar de outra forma de acesso.', + }, + signup_enabled: { + label: 'Permitir cadastro por e-mail', + help: 'Abre o formulário de cadastro com e-mail e senha. Quem ele aceita é decidido pelo Público do cadastro próprio abaixo: em Somente por convite, apenas um endereço com convite pendente; em Domínios de e-mail permitidos, os domínios permitidos e os endereços convidados; em Aberto, qualquer pessoa. Desativado, o formulário fica fechado em qualquer público, inclusive para convites.', + }, + require_email_verification: { label: 'Exigir verificação de e-mail' }, + membership_policy: { + label: 'Associação de novos usuários', + help: 'Automático vincula cada novo usuário à organização padrão. Somente por convite concede a associação apenas por um ato explícito — criar uma organização, aceitar um convite, ser adicionado por um administrador ou ser provisionado na hora pelo SSO.', + options: { + auto: 'Entrar automaticamente na organização padrão', + 'invite-only': 'Somente por convite — nunca entrar automaticamente', + }, + }, + audience_posture: { + label: 'Público do cadastro próprio', + help: 'Somente por convite fecha o cadastro próprio: as pessoas só se tornam usuárias quando um administrador as convida, cria ou importa, ou por provisionamento de diretório (SCIM) ou login único corporativo. Domínios de e-mail permitidos o abre para os domínios listados abaixo. Aberto aceita qualquer pessoa. Tanto Domínios de e-mail permitidos quanto Aberto precisam do conjunto de permissões de cadastro próprio abaixo. Domínios de e-mail permitidos sempre exige verificação de e-mail. Aberto a exige, a menos que a configuração do servidor a desative; desativá-la neste console é recusado em Aberto.', + options: { + invite_only: 'Somente por convite — sem cadastro próprio (padrão)', + email_domain: 'Somente domínios de e-mail permitidos', + open: 'Aberto — qualquer pessoa pode se cadastrar', + }, + }, + audience_allowed_email_domains: { + label: 'Domínios de e-mail permitidos', + help: 'Domínios simples, um por linha ou separados por vírgula (por exemplo, acme.com). A correspondência é exata e não diferencia maiúsculas de minúsculas; subdomínios precisam de entradas próprias; sem curingas.', + }, + audience_self_registration_permission_set: { + label: 'Conjunto de permissões do cadastro próprio', + help: 'Nome do conjunto de permissões que cada usuário cadastrado por conta própria recebe. O conjunto padrão de membro serve; o conjunto completo de administrador é recusado. Obrigatório sempre que o público permite cadastro próprio.', + }, + password_min_length: { label: 'Tamanho mínimo da senha' }, + password_max_length: { + label: 'Tamanho máximo da senha', + help: 'Protege contra negação de serviço por hash de senhas muito longas.', + }, + password_reject_breached: { + label: 'Recusar senhas vazadas', + help: 'Bloqueia senhas encontradas em vazamentos públicos via Have I Been Pwned (verificação por faixa com k-anonimato; a senha nunca é enviada completa).', + }, + password_require_complexity: { + label: 'Exigir senhas complexas', + help: 'Exige que as senhas misturem classes de caracteres (maiúsculas, minúsculas, dígitos, símbolos) no cadastro e na alteração ou redefinição de senha.', + }, + password_min_classes: { + label: 'Mínimo de classes de caracteres', + help: 'Quantas das quatro classes (maiúscula / minúscula / dígito / símbolo) uma senha deve incluir.', + }, + password_history_count: { + label: 'Histórico de senhas (sem reutilização)', + help: 'Impede reutilizar essa quantidade de senhas anteriores na alteração ou redefinição. 0 desativa a verificação.', + }, + password_expiry_days: { + label: 'Expiração da senha (dias)', + help: 'Força a troca de senha após essa quantidade de dias. 0 desativa a expiração. Enquanto a senha estiver expirada, o usuário fica sem acesso aos dados até trocá-la.', + }, + lockout_threshold: { + label: 'Limite de bloqueio da conta', + help: 'Bloqueia uma conta após essa quantidade de tentativas de login seguidas com falha — senhas erradas e códigos de dois fatores errados. Durante o bloqueio, o login é recusado mesmo com as credenciais corretas. 0 desativa o bloqueio na etapa da senha; a verificação em dois fatores mantém então seu limite integrado de 10 tentativas a cada 15 minutos, pois é a última verificação antes de emitir a sessão.', + }, + lockout_duration_minutes: { + label: 'Duração do bloqueio (minutos)', + help: 'Por quanto tempo uma conta fica bloqueada após ultrapassar o limite, em qualquer etapa do login.', + }, + rate_limit_max: { + label: 'Limite de autenticação: máximo de requisições', + help: 'Máximo de requisições por IP, por janela, aos endpoints de login / cadastro / redefinição de senha.', + }, + rate_limit_window_seconds: { + label: 'Limite de autenticação: janela (segundos)', + help: 'Janela deslizante em que o limite de requisições acima é contado.', + }, + mfa_required: { + label: 'Exigir autenticação multifator', + help: 'Usuários sem um autenticador cadastrado perdem o acesso aos dados quando o período de carência termina. Ativar isto também liga o recurso de dois fatores para que os usuários possam se cadastrar.', + }, + mfa_grace_period_days: { + label: 'Carência de MFA (dias)', + help: 'Por quanto tempo os usuários podem adiar o cadastro antes do bloqueio. 0 bloqueia imediatamente.', + }, + session_expiry_days: { + label: 'Duração da sessão (dias)', + help: 'Uma sessão expira essa quantidade de dias após o login.', + }, + session_refresh_days: { + label: 'Limite de renovação (dias)', + help: 'Uma sessão ativa é estendida quando fica mais antiga que isso.', + }, + session_idle_timeout_minutes: { + label: 'Tempo limite de inatividade (minutos)', + help: 'Desconecta o usuário após essa quantidade de minutos de inatividade. 0 desativa.', + }, + session_absolute_max_hours: { + label: 'Duração absoluta da sessão (horas)', + help: 'Força nova autenticação essa quantidade de horas após o login, independentemente da atividade. 0 desativa.', + }, + max_concurrent_sessions_per_user: { + label: 'Máximo de sessões simultâneas por usuário', + help: 'Limita as sessões simultâneas por usuário; as mais antigas são desconectadas além do limite. 0 = ilimitado.', + }, + allowed_ip_ranges: { + label: 'Faixas de IP permitidas', + help: 'Faixas CIDR ou IPs exatos (um por linha ou separados por vírgula), por exemplo 203.0.113.0/24. Quando definido, o login de fora dessas faixas é recusado, assim como qualquer requisição cujo endereço de cliente o servidor não consiga determinar. Vazio = sem restrição. O endereço avaliado é o de origem da conexão; atrás de um proxy reverso, o servidor precisa ser configurado para confiar nesse proxy para que o endereço original do cliente seja avaliado.', + }, + api_key_default_expiry_days: { + label: 'Validade padrão da chave (dias)', + help: 'Uma chave criada sem data de expiração expira essa quantidade de dias após a criação. Nunca mais que a validade máxima abaixo.', + }, + api_key_max_expiry_days: { + label: 'Validade máxima da chave (dias)', + help: 'A data de expiração mais distante que uma nova chave pode pedir, contada a partir da criação. Uma maior é recusada; exatamente essa quantidade de dias é aceita.', + }, + api_key_inactivity_days: { + label: 'Expiração por inatividade (dias)', + help: 'Uma chave sem uso registrado por essa quantidade de dias é recusada. Ela não é revogada; crie uma nova chave. Uma chave nunca usada conta a partir da criação, ou do início da política abaixo quando for mais antiga. 0 desativa a expiração por inatividade.', + }, + api_key_policy_started_at: { + label: 'Início da política de chaves', + help: 'Registrado automaticamente (UTC, ISO 8601) na primeira vez que o servidor roda com esta política. Chaves criadas antes, sem data de expiração, expiram um ano depois disso; chaves sem uso registrado contam a inatividade a partir daí. Alterá-lo move as duas datas das chaves que ainda não as receberam.', + }, + google_enabled: { + label: 'Ativar login com Google', + help: 'Oferece login com Google quando um ID do cliente e uma chave secreta do cliente estão disponíveis: salvos abaixo ou definidos na configuração do servidor, que prevalece sobre os valores salvos. Até que ambos estejam disponíveis, a página de login não mostra o botão do Google, mesmo com isto ativado. Desativado, retira o login com Google mesmo quando o servidor define ambos. Crie o cliente OAuth no Google Cloud Console.', + }, + google_client_id: { + label: 'ID do cliente Google', + help: 'ID do cliente OAuth do Google Cloud Console. Também pode ser definido na configuração do servidor.', + }, + google_client_secret: { + label: 'Chave secreta do cliente Google', + help: 'Armazenado criptografado. Também pode ser definido na configuração do servidor.', + }, + }, + }, + storage: { + title: 'Armazenamento de arquivos', + description: 'Back-end usado para anexos, exportações e uploads dos usuários.', + groups: { + adapter: { title: 'Back-end', description: 'Escolha onde os arquivos enviados são armazenados.' }, + local: { title: 'Local' }, + s3: { title: 'S3' }, + limits: { title: 'Limites' }, + }, + keys: { + adapter: { + label: 'Adaptador', + options: { local: 'Sistema de arquivos local', s3: 'S3 / compatível com S3' }, + }, + local_root: { + label: 'Diretório raiz', + help: 'Caminho do sistema de arquivos onde os arquivos são armazenados. Caminhos relativos são resolvidos a partir do CWD do servidor.', + }, + s3_bucket: { + label: 'Bucket', + help: 'Bucket compartilhado do host. Os arquivos de cada ambiente ficam separados pelo prefixo projects//.', + }, + s3_region: { label: 'Região', help: 'Exemplo: us-east-1' }, + s3_endpoint: { + label: 'Endpoint', + help: 'Endpoint personalizado para provedores compatíveis com S3 (R2, MinIO, Wasabi). Deixe em branco para AWS S3.', + }, + s3_access_key_id: { label: 'ID da chave de acesso' }, + s3_secret_access_key: { label: 'Chave de acesso secreta' }, + s3_force_path_style: { + label: 'Forçar URLs no estilo de caminho', + help: 'Ative para MinIO e a maioria dos provedores compatíveis com S3; desative para AWS S3.', + }, + presigned_ttl: { label: 'Validade da URL pré-assinada (segundos)' }, + session_ttl: { + label: 'Validade da sessão de upload (segundos)', + help: 'Por quanto tempo uma sessão de upload em partes continua retomável.', + }, + max_upload_mb: { label: 'Tamanho máximo de upload (MB)' }, + }, + actions: { + test: { label: 'Testar conexão' }, + }, + banners: { + adapter_switch_warning: { + title: 'Os arquivos existentes não são migrados', + text: 'Trocar de adaptador não migra os arquivos existentes — arquivos enviados pelo adaptador anterior ficam inacessíveis pelo novo.', + }, + }, + }, + ai: { + title: 'IA e Embedder', + description: 'Provedor de LLM, modelo, credenciais e configuração do Embedder usados pelos serviços de IA e de conhecimento da plataforma.', + groups: { + provider: { + title: 'Provedor', + description: 'Escolha o back-end de LLM. O modo Memory devolve a entrada — útil para testes, nunca para produção.', + }, + gateway: { + title: 'Vercel AI Gateway', + description: 'Roteador de vários provedores. O modelo é escrito como provider/model, por exemplo openai/gpt-4o.', + }, + openai: { title: 'OpenAI' }, + anthropic: { title: 'Anthropic' }, + google: { title: 'Google' }, + deepseek: { + title: 'DeepSeek', + description: 'API compatível com OpenAI em https://api.deepseek.com. A Base URL é preenchida automaticamente.', + }, + dashscope: { + title: '阿里通义 DashScope', + description: 'Endpoint compatível com OpenAI em dashscope.aliyuncs.com/compatible-mode/v1. A Base URL é preenchida automaticamente.', + }, + cloudflare: { + title: 'Cloudflare AI Gateway', + description: 'Usa o endpoint /compat, então o modelo é escrito como provider/model (por exemplo openai/gpt-4o-mini, anthropic/claude-3-5-sonnet, deepseek/deepseek-chat). Observação: alibaba/qwen* NÃO é compatível com o Cloudflare /compat — use o provedor DashScope para o Qwen.', + }, + siliconflow: { + title: '硅基流动 SiliconFlow', + description: 'Endpoint compatível com OpenAI em api.siliconflow.cn/v1. A Base URL é preenchida automaticamente.', + }, + openrouter: { + title: 'OpenRouter', + description: 'Roteador de vários provedores em openrouter.ai/api/v1. A Base URL é preenchida automaticamente.', + }, + titles: { + title: 'Títulos das conversas', + description: 'Gera automaticamente um título curto de resumo para novas conversas.', + }, + defaults: { + title: 'Padrões de geração', + description: 'Aplicados quando uma requisição de agente ou de chat não informa o próprio valor.', + }, + observability: { title: 'Observabilidade' }, + embedder: { + title: 'Embedder', + description: 'Provedor de texto → vetor usado pelas fontes de conhecimento e pelo RAG. Independente do provedor de chat acima.', + }, + }, + keys: { + provider: { + label: 'Provedor', + options: { + memory: 'Memory (eco — apenas testes)', + gateway: 'Vercel AI Gateway', + openai: 'OpenAI', + anthropic: 'Anthropic', + google: 'Google Generative AI', + deepseek: 'DeepSeek (compatível com OpenAI)', + dashscope: '阿里通义 DashScope (compatível com OpenAI)', + cloudflare: 'Cloudflare AI Gateway (compatível com OpenAI)', + siliconflow: '硅基流动 SiliconFlow (compatível com OpenAI)', + openrouter: 'OpenRouter (compatível com OpenAI)', + }, + }, + gateway_model: { + label: 'Modelo do Gateway', + help: 'Usado no lugar do modelo definido na configuração do servidor. Exemplo: openai/gpt-4o', + }, + gateway_api_key: { + label: 'Chave de API do Gateway', + help: 'Opcional — necessária apenas se o gateway exigir autenticação.', + }, + openai_api_key: { + label: 'Chave de API do OpenAI', + help: 'Usada no lugar da chave definida na configuração do servidor. Armazenada criptografada.', + }, + openai_model: { label: 'Modelo', help: 'ID do modelo padrão. As substituições por agente têm precedência.' }, + openai_base_url: { + label: 'Base URL', + help: 'Substituição para Azure OpenAI ou gateways auto-hospedados. Deixe em branco para api.openai.com.', + }, + anthropic_api_key: { + label: 'Chave de API do Anthropic', + help: 'Usada no lugar da chave definida na configuração do servidor. Armazenada criptografada.', + }, + anthropic_model: { label: 'Modelo' }, + google_api_key: { + label: 'Chave de API do Google', + help: 'Usada no lugar da chave definida na configuração do servidor. Armazenada criptografada.', + }, + google_model: { label: 'Modelo' }, + deepseek_api_key: { label: 'Chave de API do DeepSeek', help: 'sk-... — emitida em platform.deepseek.com.' }, + deepseek_model: { label: 'Modelo', help: 'Exemplos: deepseek-chat (V3), deepseek-reasoner (raciocínio R1).' }, + dashscope_api_key: { + label: 'Chave de API do DashScope', + help: 'sk-... — emitida em dashscope.console.aliyun.com.', + }, + dashscope_model: { label: 'Modelo' }, + cloudflare_account_id: { + label: 'ID da conta Cloudflare', + help: 'O ID hexadecimal de 32 caracteres da URL do seu painel da Cloudflare.', + }, + cloudflare_gateway_id: { + label: 'ID do gateway', + help: 'Nome do gateway configurado em Cloudflare → AI Gateway. O padrão é "default".', + }, + cloudflare_api_key: { + label: 'Token do Cloudflare AI Gateway', + help: 'Emitido em AI Gateway → aba "API tokens" (cfut_… ou sk_…).', + }, + cloudflare_model: { label: 'Modelo' }, + siliconflow_api_key: { label: 'Chave de API do SiliconFlow' }, + siliconflow_model: { + label: 'Modelo', + help: 'Exemplos: Qwen/Qwen2.5-72B-Instruct, deepseek-ai/DeepSeek-V3, meta-llama/Meta-Llama-3.1-8B-Instruct.', + }, + openrouter_api_key: { label: 'Chave de API do OpenRouter', help: 'sk-or-...' }, + openrouter_model: { + label: 'Modelo', + help: 'Formato: provider/model (por exemplo anthropic/claude-3.5-sonnet, deepseek/deepseek-chat).', + }, + title_generation_enabled: { label: 'Resumir automaticamente os títulos das conversas' }, + title_max_length: { + label: 'Tamanho máximo do título (caracteres)', + help: 'Limite rígido do título gerado. O que passar disso é truncado no servidor.', + }, + temperature: { label: 'Temperatura', help: '0 = determinístico, 2 = altamente criativo.' }, + max_tokens: { label: 'Máximo de tokens de saída', help: 'Limite rígido de tokens gerados por resposta.' }, + request_timeout_ms: { label: 'Tempo limite da requisição (ms)' }, + trace_enabled: { + label: 'Registrar rastros', + help: 'Mantém um registro de cada requisição de IA e da resposta, para que uma conversa possa ser inspecionada e reproduzida quando algo der errado.', + }, + log_prompts: { + label: 'Registrar prompts completos', + help: 'Inclui os prompts renderizados (não só os metadados) nas linhas de rastro.', + }, + embedder_provider: { + label: 'Provedor', + options: { + none: 'Desativado (sem embeddings)', + openai: 'OpenAI', + azure: 'Azure OpenAI', + dashscope: '阿里通义 DashScope', + zhipu: '智谱 BigModel', + siliconflow: '硅基流动 SiliconFlow', + doubao: '火山引擎 Doubao', + minimax: 'MiniMax', + ollama: 'Ollama (local)', + custom: 'Personalizado (compatível com OpenAI)', + }, + }, + embedder_api_key: { + label: 'Chave de API do Embedder', + help: 'Token Bearer enviado no cabeçalho Authorization. Para o Ollama, qualquer valor não vazio funciona.', + }, + embedder_model: { + label: 'Modelo', + help: 'Exemplos — OpenAI: text-embedding-3-small · 阿里通义: text-embedding-v3 · 智谱: embedding-3 · 硅基流动: BAAI/bge-m3 · Ollama: bge-m3', + }, + embedder_base_url: { + label: 'Base URL', + help: 'Raiz do endpoint (sem /embeddings). Preenchida a partir da predefinição; substitua para proxies ou gateways auto-hospedados.', + }, + embedder_dimensions: { + label: 'Dimensões', + help: 'Substitui a dimensionalidade de saída (apenas modelos Matryoshka). Deixe em branco para usar o padrão do modelo.', + }, + embedder_batch_size: { + label: 'Tamanho do lote', + help: 'Partes enviadas ao provedor de embeddings em uma requisição. Reduza se atingir os limites de taxa ou tamanho do provedor.', + }, + }, + actions: { + test: { label: 'Testar conexão' }, + test_embedder: { label: 'Testar Embedder' }, + reset: { label: 'Redefinir para os padrões do ambiente' }, + }, + banners: { + log_prompts_warning: { + title: 'Os prompts podem conter dados pessoais', + text: 'Pode vazar dados pessoais — desative em ambientes regulados.', + }, + }, + }, + knowledge: { + title: 'Conhecimento', + description: 'Back-end de armazenamento vetorial para RAG e fontes de conhecimento.', + groups: { + adapter: { + title: 'Back-end', + description: 'Escolha onde as partes dos documentos e seus vetores são armazenados.', + }, + turso: { title: 'Turso / libSQL', description: 'Funciona com Turso gerenciado, arquivo local ou em memória.' }, + ragflow: { + title: 'RAGFlow', + description: 'Implantação externa do RAGFlow. Veja https://ragflow.io para instruções de auto-hospedagem.', + }, + indexing: { + title: 'Padrões de indexação', + description: 'Um valor definido na própria fonte de conhecimento prevalece sobre estes padrões.', + }, + permissions: { title: 'Permissões' }, + }, + keys: { + adapter: { + label: 'Adaptador', + options: { + memory: 'Em memória (apenas dev / teste)', + turso: 'Turso / libSQL (nuvem ou local)', + ragflow: 'RAGFlow (externo)', + }, + }, + turso_url: { + label: 'URL de conexão', + help: 'Exemplos: libsql://your-tenant.turso.io · file:./.objectstack/knowledge.db · :memory:', + }, + turso_auth_token: { label: 'Token de autenticação', help: 'Necessário apenas para URLs do Turso gerenciado.' }, + ragflow_base_url: { label: 'Base URL', help: 'Exemplo: http://localhost:9380' }, + ragflow_api_key: { label: 'Chave de API' }, + ragflow_default_dataset: { + label: 'ID do conjunto de dados padrão', + help: 'Usado quando uma fonte de conhecimento não indica o próprio conjunto de dados do RAGFlow.', + }, + chunk_target: { + label: 'Tamanho-alvo da parte (caracteres)', + help: 'Limite flexível do tamanho da parte antes de entrar a divisão por tokens.', + }, + chunk_overlap: { + label: 'Sobreposição das partes (caracteres)', + help: 'Caracteres mantidos da parte anterior para que o contexto atravesse o limite.', + }, + over_fetch: { + label: 'Multiplicador de busca extra', + help: 'A busca obtém este múltiplo do número de resultados pedidos, para que sobrem resultados suficientes depois da filtragem pelos detalhes dos documentos.', + }, + enforce_rls: { + label: 'Aplicar RLS na busca', + help: 'Verifica de novo cada resultado com as permissões em nível de registro de quem chamou.', + }, + }, + actions: { + test: { label: 'Testar conexão' }, + }, + banners: { + adapter_switch_warning: { + title: 'Os índices existentes não são migrados', + text: 'Trocar de adaptador NÃO migra os índices existentes — documentos indexados pelo adaptador anterior ficam inacessíveis até serem reindexados.', + }, + enforce_rls_warning: { + title: 'Mantenha a RLS ativada em produção', + text: 'Desativar pula a proteção exclusiva da plataforma contra vazamento de dados do armazenamento vetorial — mantenha ativada em produção.', + }, + }, + }, + lifecycle: { + title: 'Ciclo de vida dos dados', + description: 'Por quanto tempo os dados do sistema são mantidos: substitua as janelas de retenção, limite a quantidade de linhas por tabela e receba alertas quando uma tabela cresce inesperadamente. A plataforma aplica isso na limpeza periódica.', + keys: { + enabled: { + label: 'Aplicar políticas de ciclo de vida', + help: 'Chave geral da limpeza periódica. Desativado = as políticas declaradas de retenção/TTL/rotação deixam de ser aplicadas (os dados voltam a crescer sem limite).', + }, + retention_overrides: { + label: 'Substituições de retenção', + help: 'Substituições de janela por objeto: { "": { "maxAge": "1y", "expireAfter": "30d" } }. As durações usam h/d/w/y. Definidas por organização, para que uma organização regulada guarde anos e uma de desenvolvimento guarde dias. Uma substituição mais curta que o mínimo exigido por um recurso da plataforma (por exemplo, a janela de detecção de duplicatas da fila de tarefas) é ignorada e registrada como erro, e a janela padrão continua valendo.', + }, + quotas: { + label: 'Cotas de linhas', + help: 'Quantidade máxima de linhas por objeto: { "": 1000000 }. Ultrapassar gera um alerta de governança — as cotas nunca excluem além da política declarada.', + }, + quota_defaults: { + label: 'Cotas padrão por classe', + help: 'Cotas de reserva por classe de ciclo de vida: { "telemetry": 1000000, "transient": 500000, "event": 100000, "audit": 5000000 }.', + }, + growth_alert_rows: { + label: 'Limite de alerta de crescimento (linhas por varredura)', + help: 'Alerta quando uma tabela com ciclo de vida declarado cresce mais do que essa quantidade de linhas entre duas varreduras. 0 desativa os alertas de crescimento.', + }, + }, + }, + }, +}; diff --git a/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts b/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts index 0fa750e06d4..9b054b2a54c 100644 --- a/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts +++ b/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * Settings-translation coverage guard (zh-CN / ja-JP / es-ES). + * Settings-translation coverage guard (every shipped non-English locale). * * Every built-in settings manifest ships English labels inline; the console * localizes them by resolving `settings..…` against the shipped @@ -24,7 +24,7 @@ import { describe, it, expect } from 'vitest'; import type { SettingsManifest } from '@objectstack/spec/system'; import { lifecycleSettingsManifest } from '@objectstack/objectql'; import * as manifestsModule from '../manifests/index.js'; -import { en, zhCN, jaJP, esES } from './index.js'; +import { en, zhCN, jaJP, esES, zhHant, ar, fr, ptBR } from './index.js'; // The manifests barrel also exports action handlers and the aggregate array; // keep only the manifest objects. @@ -53,6 +53,10 @@ const LOCALES: Array<[string, { settings?: Record }]> = [ ['zh-CN', zhCN], ['ja-JP', jaJP], ['es-ES', esES], + ['zh-Hant', zhHant], + ['ar', ar], + ['fr', fr], + ['pt-BR', ptBR], ]; function missingFor(data: { settings?: Record }, m: SettingsManifest): string[] { diff --git a/packages/services/service-settings/src/translations/zh-Hant.ts b/packages/services/service-settings/src/translations/zh-Hant.ts new file mode 100644 index 00000000000..3420c43bb51 --- /dev/null +++ b/packages/services/service-settings/src/translations/zh-Hant.ts @@ -0,0 +1,483 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { PlatformTranslationData } from '@objectstack/spec/system'; +import { countryOptionLabels } from '../manifests/company.manifest.js'; + +/** + * 繁體中文 (zh-Hant) — built-in settings manifest translations. + */ +export const zhHant: PlatformTranslationData = { + settingsCommon: { + sourceLabels: { env: '環境變數', global: '全域', tenant: '組織', user: '使用者', default: '預設' }, + }, + settings: { + mail: { + title: '郵件投遞', + description: 'SMTP 與交易郵件服務商設定。', + groups: { + provider: { title: '服務商', description: '選擇本系統如何寄出外寄郵件。' }, + smtp: { title: 'SMTP' }, + api_key: { title: 'API 金鑰' }, + from_address: { title: '寄件地址' }, + delivery: { title: '投遞方式', description: '外寄郵件如何交給服務商。' }, + }, + keys: { + provider: { + label: '服務商', + help: '此處只列出本伺服器實際能投遞的服務商。SendGrid 與 Amazon SES 以 SMTP 設定。', + options: { smtp: 'SMTP', resend: 'Resend', postmark: 'Postmark', log: '不傳送(僅記錄日誌,不實際投遞)' }, + }, + smtp_host: { label: '主機', help: '範例:smtp.example.com' }, + smtp_port: { label: '連接埠' }, + smtp_secure: { label: '使用 TLS' }, + smtp_user: { label: '使用者名稱' }, + smtp_password: { label: '密碼' }, + api_key: { label: 'API 金鑰' }, + from_email: { label: '寄件電子郵件', help: '範例:no-reply@example.com' }, + from_name: { label: '寄件者名稱', help: '留空則使用產品名稱(品牌設定),或本部署設定時的名稱。' }, + queue_delivery: { + label: '持久化佇列投遞', + help: '將每封郵件交給工作佇列,而非直接寄出:投遞失敗時由 worker 依退避策略重試,且重新啟動後不會遺失。需要具備持久化轉接器的佇列能力。「傳送測試郵件」一律直接寄出。', + }, + }, + actions: { + test: { label: '傳送測試郵件' }, + }, + }, + sms: { + title: '簡訊投遞', + description: '用於 OTP 登入、邀請與通知的簡訊服務商設定。', + groups: { + provider: { title: '服務商', description: '選擇本系統如何寄出外寄簡訊。' }, + aliyun: { title: '阿里雲簡訊' }, + twilio: { title: 'Twilio' }, + limits: { title: '傳送額度', description: '限制本部署的外寄簡訊量。簡訊是付費通道,每一則都會產生實際費用。' }, + }, + keys: { + provider: { + label: '服務商', + options: { log: '不傳送(僅記錄日誌,不實際投遞)', aliyun: '阿里雲簡訊', twilio: 'Twilio' }, + }, + aliyun_access_key_id: { label: 'AccessKey ID' }, + aliyun_access_key_secret: { label: 'AccessKey Secret' }, + aliyun_sign_name: { label: '簡訊簽名' }, + aliyun_template_code: { label: '預設簡訊範本', help: '當傳送未指定範本時使用。含單一 ${content} 變數的通用範本可用於傳送一般通知簡訊。' }, + twilio_account_sid: { label: 'Account SID' }, + twilio_auth_token: { label: 'Auth Token' }, + twilio_from_number: { label: '發訊號碼', help: 'E.164 格式的發訊方,例如 +15005550006。此項與 Messaging Service SID 二擇一。' }, + twilio_messaging_service_sid: { label: 'Messaging Service SID' }, + daily_quota: { + label: '每日傳送上限', + help: '本部署每個 UTC 日最多可傳送的簡訊則數,涵蓋 OTP 登入、邀請與通知。0 表示不限制。超過上限的傳送會被拒絕,直到 00:00 UTC。', + }, + }, + actions: { + test: { label: '傳送測試簡訊' }, + }, + }, + branding: { + title: '品牌', + description: '主控台與登入頁顯示的名稱、Logo 與顏色。未設定的值沿用部署本身的品牌。', + groups: { + identity: { title: '身分' }, + appearance: { title: '外觀' }, + }, + keys: { + workspace_name: { + label: '產品名稱', + help: '主控台、登入頁與瀏覽器標題中的產品名稱,也用於登入郵件、驗證器應用程式與 MCP 登入。留空則使用本部署設定時的名稱。這不是組織本身的名稱,組織本身的名稱是組織記錄上的「名稱」。', + }, + accent_color: { label: '主題色', help: '瀏覽器與已安裝應用程式的主題色。各應用程式保留自己的顏色。' }, + logo_url: { label: 'Logo 網址', help: '顯示在登入頁、載入畫面與首頁頂端列。範例:https://…/logo.svg' }, + }, + }, + company: { + title: '公司', + description: '法律實體資訊——登記名稱、地址、稅籍編號與主要聯絡人。', + groups: { + identity: { title: '主體資訊' }, + address: { title: '登記地址' }, + contact: { title: '聯絡方式' }, + }, + keys: { + legal_name: { label: '法定名稱', help: '組織的登記法定名稱,與合約和發票上所載相同。' }, + registration_number: { label: '登記號碼', help: '公司登記/設立登記號碼(例如 EIN)。' }, + tax_id: { label: '稅籍編號/VAT', help: '顯示在發票上(例如 VAT、GST、ABN)。' }, + address_line1: { label: '地址第 1 行' }, + address_line2: { label: '地址第 2 行' }, + city: { label: '城市' }, + state: { label: '州/省' }, + postal_code: { label: '郵遞區號' }, + country: { + label: '國家/地區', + help: '登記地址所在的國家/地區。輸入國家/地區名稱並從清單中選擇。', + options: countryOptionLabels('zh-Hant'), + }, + phone: { label: '電話', help: '建議使用 E.164 格式,例如 +886 2 5555 0100。' }, + website: { label: '網站', help: '範例:https://example.com' }, + primary_contact_name: { label: '主要聯絡人' }, + primary_contact_email: { label: '主要聯絡人電子郵件', help: '範例:ops@example.com' }, + }, + }, + localization: { + title: '在地化', + description: '預設時區、語言、國家/地區、貨幣與會計年度。', + groups: { + region: { title: '區域' }, + finance: { title: '財務' }, + }, + keys: { + timezone: { + label: '預設時區', + help: '決定公式中的「今天」是哪一天、報表如何依日期分組,以及日期與時間如何顯示。可填寫任何 IANA 時區名稱(例如 Asia/Taipei)。這是組織實際生效的時區:組織記錄上的「時區」欄位不會改變它。', + options: { + UTC: 'UTC', + 'America/Los_Angeles': '(UTC−08/−07) 洛杉磯', + 'America/Denver': '(UTC−07/−06) 丹佛', + 'America/Chicago': '(UTC−06/−05) 芝加哥', + 'America/New_York': '(UTC−05/−04) 紐約', + 'America/Sao_Paulo': '(UTC−03) 聖保羅', + 'Europe/London': '(UTC±00/+01) 倫敦', + 'Europe/Paris': '(UTC+01/+02) 巴黎', + 'Europe/Berlin': '(UTC+01/+02) 柏林', + 'Europe/Moscow': '(UTC+03) 莫斯科', + 'Asia/Dubai': '(UTC+04) 杜拜', + 'Asia/Kolkata': '(UTC+05:30) 加爾各答', + 'Asia/Singapore': '(UTC+08) 新加坡', + 'Asia/Shanghai': '(UTC+08) 上海', + 'Asia/Tokyo': '(UTC+09) 東京', + 'Australia/Sydney': '(UTC+10/+11) 雪梨', + 'Pacific/Auckland': '(UTC+12/+13) 奧克蘭', + }, + }, + locale: { label: '預設語言', help: '用於訊息文案與數字/日期格式的 BCP-47 語言標籤。' }, + default_country: { + label: '預設國家/地區', + help: '用作地址與電話的預設值。輸入國家/地區名稱並從清單中選擇。', + options: countryOptionLabels('zh-Hant'), + }, + currency: { + label: '預設貨幣', + help: '貨幣欄位未指定自己的幣別時套用的 ISO 4217 代碼。留空則將沒有幣別代碼的金額顯示為一般數字。', + options: { + USD: 'USD — 美元', + EUR: 'EUR — 歐元', + GBP: 'GBP — 英鎊', + JPY: 'JPY — 日圓', + CNY: 'CNY — 人民幣', + INR: 'INR — 印度盧比', + AUD: 'AUD — 澳幣', + CAD: 'CAD — 加幣', + BRL: 'BRL — 巴西雷亞爾', + }, + }, + fiscal_year_start: { + label: '會計年度起始月', + help: '會計年度的第一個月——決定報表中的「本季/本會計年度」。', + options: { + january: '一月', + february: '二月', + march: '三月', + april: '四月', + may: '五月', + june: '六月', + july: '七月', + august: '八月', + september: '九月', + october: '十月', + november: '十一月', + december: '十二月', + }, + }, + }, + }, + auth: { + title: '驗證', + description: '登入、註冊以及內建驗證功能的控制項。', + groups: { + email_password: { title: '電子郵件與密碼', description: '控制本機電子郵件/密碼登入與自助註冊。' }, + membership: { title: '成員歸屬', description: '新建立的使用者加入什麼。與上方的自助註冊搭配設定。' }, + audience: { + title: '註冊對象', + description: '誰可以成為本系統應用程式的使用者。「允許清單中的電子郵件網域」一律強制開啟電子郵件驗證。「開放」也會強制開啟,除非部署方將其關閉;在此主控台儲存的值無法關閉。邀請、管理員建立的使用者、SCIM 佈建與企業 SSO 在任何模式下都能加入。', + }, + password_policy: { title: '密碼原則', description: '由驗證提供者在註冊與重設密碼時強制執行的長度限制。' }, + anti_abuse: { title: '防濫用', description: '暴力破解防護:依身分鎖定帳戶,並依 IP 對驗證端點限流。' }, + multi_factor: { title: '多重要素', description: '要求成員以驗證器應用程式(TOTP)保護帳戶。' }, + sessions: { title: '工作階段', description: '登入工作階段的有效時間。' }, + network: { title: '網路', description: '限制使用者可以從哪裡登入。' }, + api_keys: { title: 'API 金鑰', description: '個人 API 金鑰的有效期限。已過期、超過閒置期限未使用或已撤銷的金鑰,會以各自的錯誤碼被拒絕,補救方式一律是建立新金鑰。' }, + social: { title: '社群登入', description: '設定內建的 Google 登入提供者。伺服器設定中的憑證仍優先生效。' }, + }, + keys: { + email_password_enabled: { label: '啟用電子郵件/密碼登入', confirmOffText: '以密碼登入的使用者將需要其他登入方式。' }, + signup_enabled: { + label: '允許以電子郵件註冊', + help: '開啟電子郵件與密碼註冊表單。它接納誰由下方的「自助註冊對象」決定:「僅限邀請」下只接納持有待處理邀請的地址;「允許清單中的電子郵件網域」下接納允許清單中的網域與受邀地址;「開放」下接納任何人。關閉後,在任何對象設定下表單都會關閉,受邀者也不例外。', + }, + require_email_verification: { label: '要求驗證電子郵件' }, + membership_policy: { + label: '新使用者的成員歸屬', + help: '「自動加入」會將每位新使用者綁定到預設組織。「僅限邀請」只在明確的動作後才授予成員資格——自行建立組織、接受邀請、由管理員加入,或由 SSO 即時佈建。', + options: { auto: '自動加入預設組織', 'invite-only': '僅限邀請——絕不自動加入' }, + }, + audience_posture: { + label: '自助註冊對象', + help: '「僅限邀請」會關閉自助註冊:只有在管理員邀請、建立或匯入,或透過目錄佈建(SCIM)或企業單一登入時,才會成為使用者。「允許清單中的電子郵件網域」只對下方列出的網域開放。「開放」允許任何人。「允許清單中的電子郵件網域」與「開放」都需要設定下方的自助註冊權限集。「允許清單中的電子郵件網域」一律要求電子郵件驗證。「開放」也會要求,除非伺服器設定將其關閉;在「開放」下於此主控台關閉它會被拒絕。', + options: { invite_only: '僅限邀請——不開放自助註冊(預設)', email_domain: '僅限允許清單中的電子郵件網域', open: '開放——任何人都可自助註冊' }, + }, + audience_allowed_email_domains: { + label: '允許的電子郵件網域', + help: '純網域名稱,每行一個或以逗號分隔(例如 acme.com)。比對方式為完全相符且不分大小寫;子網域需各自列出;不支援萬用字元。', + }, + audience_self_registration_permission_set: { + label: '自助註冊權限集', + help: '每位自助註冊使用者取得的權限集名稱。標準成員權限集即可;完整管理員權限集會被拒絕。只要註冊對象允許自助註冊就必須填寫。', + }, + password_min_length: { label: '密碼最小長度' }, + password_max_length: { label: '密碼最大長度', help: '防止以超長密碼雜湊造成的阻斷服務。' }, + password_reject_breached: { + label: '拒絕已外洩的密碼', + help: '透過 Have I Been Pwned 封鎖出現在公開外洩資料庫中的密碼(k-匿名範圍檢查;絕不傳送完整密碼)。', + }, + password_require_complexity: { label: '要求複雜密碼', help: '在註冊及變更/重設密碼時,要求密碼混合多種字元類別(大寫、小寫、數字、符號)。' }, + password_min_classes: { label: '最少字元類別數', help: '密碼至少需包含四類(大寫/小寫/數字/符號)中的幾類。' }, + password_history_count: { label: '密碼歷程(禁止重複使用)', help: '變更/重設時禁止重複使用最近這麼多組舊密碼。0 表示停用此檢查。' }, + password_expiry_days: { label: '密碼有效期限(天)', help: '超過這麼多天後強制變更密碼。0 表示不會過期。密碼過期期間,使用者在變更密碼前無法存取資料。' }, + lockout_threshold: { + label: '帳戶鎖定門檻', + help: '連續登入失敗達到這個次數後鎖定帳戶——密碼錯誤與雙重驗證碼錯誤都計入。鎖定期間即使憑證正確也會拒絕登入。0 表示停用密碼階段的鎖定;此時雙重驗證仍保留其內建限制(每 15 分鐘 10 次),因為它是核發工作階段前的最後一道檢查。', + }, + lockout_duration_minutes: { label: '鎖定時間(分鐘)', help: '超過門檻後帳戶維持鎖定的時間,兩個登入階段皆適用。' }, + rate_limit_max: { label: '驗證限流:最大請求數', help: '每個 IP 在每個時間窗內對登入/註冊/重設密碼端點的最大請求數。' }, + rate_limit_window_seconds: { label: '驗證限流:時間窗(秒)', help: '計算上方請求上限所用的滑動時間窗。' }, + mfa_required: { label: '要求多重要素驗證', help: '尚未註冊驗證器的使用者,在寬限期結束後將無法存取資料。啟用此項也會開啟雙重驗證功能,讓使用者可以註冊。' }, + mfa_grace_period_days: { label: 'MFA 寬限期(天)', help: '使用者在被強制封鎖前可延後註冊的時間。0 表示立即封鎖。' }, + session_expiry_days: { label: '工作階段有效期(天)', help: '工作階段在登入後這麼多天到期。' }, + session_refresh_days: { label: '更新門檻(天)', help: '使用中的工作階段超過此時間後會自動延長。' }, + session_idle_timeout_minutes: { label: '閒置逾時(分鐘)', help: '閒置這麼多分鐘後將使用者登出。0 表示停用。' }, + session_absolute_max_hours: { label: '工作階段絕對有效期(小時)', help: '登入後經過這麼多小時強制重新驗證,不論是否有活動。0 表示停用。' }, + max_concurrent_sessions_per_user: { label: '每位使用者的最大同時工作階段數', help: '限制每位使用者同時登入的工作階段數;超過上限時最舊的會被登出。0 = 不限制。' }, + allowed_ip_ranges: { + label: '允許的 IP 範圍', + help: 'CIDR 範圍或精確 IP(每行一個,或以逗號分隔),例如 203.0.113.0/24。設定後,來自這些範圍以外的登入會被拒絕,伺服器無法確定用戶端位址的請求也會被拒絕。留空 = 不限制。判定的是連線來源位址;部署在反向代理之後時,必須設定伺服器信任該代理,才會改為判定原始用戶端位址。', + }, + api_key_default_expiry_days: { label: '預設金鑰有效期(天)', help: '未指定到期日而建立的金鑰,會在建立後這麼多天到期。絕不超過下方的最長有效期。' }, + api_key_max_expiry_days: { label: '金鑰最長有效期(天)', help: '新金鑰可要求的最遠到期日,從建立時起算。更長的會被拒絕;剛好這麼多天則可接受。' }, + api_key_inactivity_days: { + label: '閒置失效(天)', + help: '這麼多天內沒有使用紀錄的金鑰會被拒絕。它不會被撤銷;請建立新金鑰。從未使用的金鑰從建立時起算,若早於下方的原則開始時間則從原則開始時間起算。0 表示停用閒置失效。', + }, + api_key_policy_started_at: { + label: '金鑰原則開始時間', + help: '伺服器第一次以此原則執行時自動記錄(UTC,ISO 8601)。在此之前建立且沒有到期日的金鑰,於此時間一年後到期;沒有使用紀錄的金鑰從此時間起算閒置期。修改它會移動尚未取得這兩個日期之金鑰的日期。', + }, + google_enabled: { + label: '啟用 Google 登入', + help: '用戶端 ID 與用戶端密鑰都可用時才提供 Google 登入:來自下方儲存的值,或伺服器設定(優先於儲存的值)。兩者都可用之前,即使此開關已開啟,登入頁也不會顯示 Google 按鈕。關閉後,即使伺服器設定了兩者,也會撤下 Google 登入。請在 Google Cloud Console 建立 OAuth 用戶端。', + }, + google_client_id: { label: 'Google 用戶端 ID', help: '來自 Google Cloud Console 的 OAuth 用戶端 ID。也可在伺服器設定中設定。' }, + google_client_secret: { label: 'Google 用戶端密鑰', help: '加密儲存。也可在伺服器設定中設定。' }, + }, + }, + storage: { + title: '檔案儲存', + description: '附件、匯出檔案與使用者上傳所使用的儲存後端。', + groups: { + adapter: { title: '儲存後端', description: '選擇上傳檔案的存放位置。' }, + local: { title: '本機' }, + s3: { title: 'S3' }, + limits: { title: '限制' }, + }, + keys: { + adapter: { + label: '轉接器', + options: { local: '本機檔案系統', s3: 'S3/S3 相容' }, + }, + local_root: { label: '根目錄', help: '存放檔案的檔案系統路徑。相對路徑以伺服器的工作目錄(CWD)為基準解析。' }, + s3_bucket: { label: 'Bucket', help: '共用主機 Bucket。各環境的檔案以 projects// 前綴區隔。' }, + s3_region: { label: '區域', help: '範例:us-east-1' }, + s3_endpoint: { label: '端點', help: 'S3 相容服務(R2、MinIO、Wasabi)的自訂端點。使用 AWS S3 時請留空。' }, + s3_access_key_id: { label: '存取金鑰 ID' }, + s3_secret_access_key: { label: '私密存取金鑰' }, + s3_force_path_style: { label: '強制使用路徑樣式 URL', help: 'MinIO 與大多數 S3 相容服務請開啟;AWS S3 請關閉。' }, + presigned_ttl: { label: '預簽 URL 有效期(秒)' }, + session_ttl: { label: '上傳工作階段有效期(秒)', help: '分段上傳工作階段可續傳的時間。' }, + max_upload_mb: { label: '最大上傳大小(MB)' }, + }, + actions: { + test: { label: '測試連線' }, + }, + banners: { + adapter_switch_warning: { title: '既有檔案不會遷移', text: '切換轉接器不會遷移既有檔案——以前一個轉接器上傳的檔案,將無法透過新的轉接器存取。' }, + }, + }, + ai: { + title: 'AI 與 Embedder', + description: '平台 AI 與知識庫服務使用的 LLM 提供者、模型、憑證與 Embedder 設定。', + groups: { + provider: { title: '提供者', description: '選擇 LLM 後端。Memory 模式只會回傳輸入內容——適合測試,絕不可用於正式環境。' }, + gateway: { title: 'Vercel AI Gateway', description: '多提供者路由器。模型寫成 provider/model,例如 openai/gpt-4o。' }, + openai: { title: 'OpenAI' }, + anthropic: { title: 'Anthropic' }, + google: { title: 'Google' }, + deepseek: { title: 'DeepSeek', description: '位於 https://api.deepseek.com 的 OpenAI 相容 API。Base URL 會自動填入。' }, + dashscope: { + title: '阿里通義 DashScope', + description: '位於 dashscope.aliyuncs.com/compatible-mode/v1 的 OpenAI 相容端點。Base URL 會自動填入。', + }, + cloudflare: { + title: 'Cloudflare AI Gateway', + description: '使用 /compat 端點,因此模型寫成 provider/model(例如 openai/gpt-4o-mini、anthropic/claude-3-5-sonnet、deepseek/deepseek-chat)。注意:Cloudflare /compat 不支援 alibaba/qwen*——Qwen 請使用 DashScope 提供者。', + }, + siliconflow: { + title: '矽基流動 SiliconFlow', + description: '位於 api.siliconflow.cn/v1 的 OpenAI 相容端點。Base URL 會自動填入。', + }, + openrouter: { title: 'OpenRouter', description: '位於 openrouter.ai/api/v1 的多提供者路由器。Base URL 會自動填入。' }, + titles: { title: '對話標題', description: '為新對話自動產生簡短的摘要標題。' }, + defaults: { title: '生成預設值', description: '當 Agent 或聊天請求未指定自己的值時套用。' }, + observability: { title: '可觀測性' }, + embedder: { title: 'Embedder', description: '知識來源與 RAG 使用的文字→向量提供者。與上方的聊天提供者彼此獨立。' }, + }, + keys: { + provider: { + label: '提供者', + options: { + memory: 'Memory(回傳輸入——僅供測試)', + gateway: 'Vercel AI Gateway', + openai: 'OpenAI', + anthropic: 'Anthropic', + google: 'Google Generative AI', + deepseek: 'DeepSeek(OpenAI 相容)', + dashscope: '阿里通義 DashScope(OpenAI 相容)', + cloudflare: 'Cloudflare AI Gateway(OpenAI 相容)', + siliconflow: '矽基流動 SiliconFlow(OpenAI 相容)', + openrouter: 'OpenRouter(OpenAI 相容)', + }, + }, + gateway_model: { label: 'Gateway 模型', help: '取代伺服器設定中的模型。範例:openai/gpt-4o' }, + gateway_api_key: { label: 'Gateway API 金鑰', help: '選填——僅在 Gateway 強制驗證時需要。' }, + openai_api_key: { label: 'OpenAI API 金鑰', help: '取代伺服器設定中的金鑰。加密儲存。' }, + openai_model: { label: '模型', help: '預設模型 ID。各 Agent 的覆寫值優先。' }, + openai_base_url: { label: 'Base URL', help: '用於 Azure OpenAI 或自架閘道的覆寫值。使用 api.openai.com 時請留空。' }, + anthropic_api_key: { label: 'Anthropic API 金鑰', help: '取代伺服器設定中的金鑰。加密儲存。' }, + anthropic_model: { label: '模型' }, + google_api_key: { label: 'Google API 金鑰', help: '取代伺服器設定中的金鑰。加密儲存。' }, + google_model: { label: '模型' }, + deepseek_api_key: { label: 'DeepSeek API 金鑰', help: 'sk-...——由 platform.deepseek.com 核發。' }, + deepseek_model: { label: '模型', help: '範例:deepseek-chat(V3)、deepseek-reasoner(R1 推理)。' }, + dashscope_api_key: { label: 'DashScope API 金鑰', help: 'sk-...——由 dashscope.console.aliyun.com 核發。' }, + dashscope_model: { label: '模型' }, + cloudflare_account_id: { label: 'Cloudflare 帳戶 ID', help: 'Cloudflare 主控台網址中 32 個字元的十六進位 ID。' }, + cloudflare_gateway_id: { label: 'Gateway ID', help: '在 Cloudflare → AI Gateway 中設定的閘道名稱。預設為 "default"。' }, + cloudflare_api_key: { + label: 'Cloudflare AI Gateway 權杖', + help: '在 AI Gateway →「API tokens」分頁核發(cfut_… 或 sk_…)。', + }, + cloudflare_model: { label: '模型' }, + siliconflow_api_key: { label: 'SiliconFlow API 金鑰' }, + siliconflow_model: { + label: '模型', + help: '範例:Qwen/Qwen2.5-72B-Instruct、deepseek-ai/DeepSeek-V3、meta-llama/Meta-Llama-3.1-8B-Instruct。', + }, + openrouter_api_key: { label: 'OpenRouter API 金鑰', help: 'sk-or-...' }, + openrouter_model: { + label: '模型', + help: '格式:provider/model(例如 anthropic/claude-3.5-sonnet、deepseek/deepseek-chat)。', + }, + title_generation_enabled: { label: '自動為對話產生摘要標題' }, + title_max_length: { label: '標題最大長度(字元)', help: '產生標題的硬性上限。超過的部分會在伺服器端截斷。' }, + temperature: { label: '溫度', help: '0 = 確定性,2 = 高度發散。' }, + max_tokens: { label: '最大輸出 token 數', help: '每次回應產生 token 數的硬性上限。' }, + request_timeout_ms: { label: '請求逾時(毫秒)' }, + trace_enabled: { label: '記錄追蹤', help: '記錄每次 AI 請求及其回應,以便出問題時檢視並重播對話。' }, + log_prompts: { label: '記錄完整提示詞', help: '在追蹤記錄中包含完整的提示詞(不只是中繼資料)。' }, + embedder_provider: { + label: '提供者', + options: { + none: '停用(不產生向量)', + openai: 'OpenAI', + azure: 'Azure OpenAI', + dashscope: '阿里通義 DashScope', + zhipu: '智譜 BigModel', + siliconflow: '矽基流動 SiliconFlow', + doubao: '火山引擎 Doubao', + minimax: 'MiniMax', + ollama: 'Ollama(本機)', + custom: '自訂(OpenAI 相容)', + }, + }, + embedder_api_key: { label: 'Embedder API 金鑰', help: '以 Authorization 標頭傳送的 Bearer 權杖。Ollama 可填任何非空值。' }, + embedder_model: { + label: '模型', + help: '範例——OpenAI: text-embedding-3-small · 阿里通義: text-embedding-v3 · 智譜: embedding-3 · 矽基流動: BAAI/bge-m3 · Ollama: bge-m3', + }, + embedder_base_url: { label: 'Base URL', help: '端點根路徑(不含 /embeddings)。依預設組合自動填入;使用代理或自架閘道時可覆寫。' }, + embedder_dimensions: { label: '維度', help: '覆寫輸出維度(僅限 Matryoshka 模型)。留空則使用模型預設值。' }, + embedder_batch_size: { label: '批次大小', help: '單次請求傳給嵌入提供者的區塊數。碰到提供者的速率/大小限制時請調低。' }, + }, + actions: { + test: { label: '測試連線' }, + test_embedder: { label: '測試 Embedder' }, + reset: { label: '重設為環境預設值' }, + }, + banners: { + log_prompts_warning: { title: '提示詞可能含有個人資料', text: '可能外洩個人資料——受監管環境請關閉。' }, + }, + }, + knowledge: { + title: '知識庫', + description: 'RAG/知識來源使用的向量儲存後端。', + groups: { + adapter: { title: '後端', description: '選擇文件分塊及其向量的存放位置。' }, + turso: { title: 'Turso / libSQL', description: '可搭配託管 Turso、本機檔案或記憶體使用。' }, + ragflow: { title: 'RAGFlow', description: '外部 RAGFlow 部署。自行架設說明見 https://ragflow.io。' }, + indexing: { title: '索引預設值', description: '在知識來源本身設定的值優先於這些預設值。' }, + permissions: { title: '權限' }, + }, + keys: { + adapter: { + label: '轉接器', + options: { memory: '記憶體(僅限開發/測試)', turso: 'Turso / libSQL(雲端或本機)', ragflow: 'RAGFlow(外部)' }, + }, + turso_url: { + label: '連線 URL', + help: '範例:libsql://your-tenant.turso.io · file:./.objectstack/knowledge.db · :memory:', + }, + turso_auth_token: { label: '驗證權杖', help: '僅託管 Turso URL 需要。' }, + ragflow_base_url: { label: 'Base URL', help: '範例:http://localhost:9380' }, + ragflow_api_key: { label: 'API 金鑰' }, + ragflow_default_dataset: { label: '預設資料集 ID', help: '知識來源未指定自己的 RAGFlow 資料集時使用。' }, + chunk_target: { label: '目標區塊大小(字元)', help: '在依 token 切分啟動前,區塊大小的軟性上限。' }, + chunk_overlap: { label: '區塊重疊(字元)', help: '從前一個區塊保留的字元數,讓上下文能跨越邊界。' }, + over_fetch: { label: '超量擷取倍數', help: '搜尋時取回所要求結果數的這個倍數,以便依文件資訊篩選後仍有足夠結果可傳回。' }, + enforce_rls: { label: '搜尋時強制套用 RLS', help: '依呼叫者的記錄層級權限重新檢查每一筆命中結果。' }, + }, + actions: { + test: { label: '測試連線' }, + }, + banners: { + adapter_switch_warning: { title: '既有索引不會遷移', text: '切換轉接器「不會」遷移既有索引——以前一個轉接器建立索引的文件,在重新建立索引前都無法存取。' }, + enforce_rls_warning: { title: '正式環境請保持 RLS 開啟', text: '關閉會略過平台防止向量儲存資料外洩的獨有防護——正式環境請保持開啟。' }, + }, + }, + lifecycle: { + title: '資料生命週期', + description: '系統資料保留多久:覆寫保留期、為每張資料表設定列數上限,並在資料表意外成長時發出警示。平台會在週期性清理時套用這些設定。', + keys: { + enabled: { label: '執行生命週期原則', help: '週期性清理的總開關。關閉 = 已宣告的保留期/TTL/輪替原則不再執行(資料會再度無限成長)。' }, + retention_overrides: { + label: '保留期覆寫', + help: '依物件覆寫保留期:{ "": { "maxAge": "1y", "expireAfter": "30d" } }。時長使用 h/d/w/y。依組織設定,因此受監管的組織可保留數年,開發用組織只保留數天。短於某項平台功能所需最短期限(例如工作佇列的重複偵測期限)的覆寫值會被忽略並記錄為錯誤,繼續套用預設期限。', + }, + quotas: { label: '列數配額', help: '依物件的最大列數:{ "": 1000000 }。超出時會發出治理警示——配額絕不會刪除超出已宣告原則的資料。' }, + quota_defaults: { + label: '依類別的預設配額', + help: '依生命週期類別的備援配額:{ "telemetry": 1000000, "transient": 500000, "event": 100000, "audit": 5000000 }。', + }, + growth_alert_rows: { label: '成長警示門檻(每次清理的列數)', help: '宣告了生命週期的資料表在兩次清理之間增加超過這麼多列時發出警示。0 表示停用成長警示。' }, + }, + }, + }, +};