From 22ca38900c441cdf3b7b61f49f26823ca1a62623 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 07:44:39 +0000 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20complete=20the=20locale=20backfil?= =?UTF-8?q?l=20=E2=80=94=20all=20ten=20packs=20reach=20full=20key=20parity?= =?UTF-8?q?=20(#2872)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translates the remaining 275 keys x 8 packs = 2,200 strings, closing #2872. Largest namespaces: `grid` (101, mostly the import wizard), `gantt` (58), `dashboard` (25), plus a long tail across list/auth/fields/marketplace/ capability and nine others. Every pack is now at parity with en: 2,495 of 2,499 keys, zero keys en lacks. The four-key remainder is the outbound-message set, absent by design so t() falls through to English and the cloud confirm gate keeps recognising it. P3 is now enforceable. The four-namespace ratchet from #2905 is replaced by all-locales-key-parity.test.ts, asserting: every pack defines every en key; no pack defines a key en lacks (#2872 part b was 74 keys of exactly this); and placeholders match en per string — both `{{count}}` and the single-brace `{count}` form that two gantt.autoScheduleDlg keys use because their call site does a literal .replace('{count}', …) instead of i18next interpolation. All three assertions were mutation-tested, single-brace form included. A bug the test suite could not have caught: the first merge pass produced DUPLICATE keys in four packs. The key list is the union of what is missing across all eight, but the insert ran unconditionally, so packs that already had detail.created/detail.updated got a second copy. Every test still passed — at runtime the later property wins, so the parity check saw a consistent object. tsc caught it as TS1117 during `turbo build`; ESLint does not flag it and a runtime test cannot, since the duplicate is collapsed before JS sees the object. The merge script now filters per pack. Translations are model-generated, and dense domain terminology (Gantt dependency types, the import wizard's upsert/match-field vocabulary) is where that is weakest. Shipping as a reviewable first draft, not a finished localization. Mechanically verified: parity both directions, placeholder shape per string, and that no outbound agent message was translated. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TubWYdWquVkS9dj733sDmC --- .changeset/complete-locale-backfill.md | 55 +++ .../__tests__/all-locales-key-parity.test.ts | 99 ++++++ .../high-frequency-namespace-parity.test.ts | 83 ----- packages/i18n/src/locales/ar.ts | 333 ++++++++++++++++++ packages/i18n/src/locales/de.ts | 331 +++++++++++++++++ packages/i18n/src/locales/es.ts | 331 +++++++++++++++++ packages/i18n/src/locales/fr.ts | 333 ++++++++++++++++++ packages/i18n/src/locales/ja.ts | 331 +++++++++++++++++ packages/i18n/src/locales/ko.ts | 331 +++++++++++++++++ packages/i18n/src/locales/pt.ts | 333 ++++++++++++++++++ packages/i18n/src/locales/ru.ts | 333 ++++++++++++++++++ 11 files changed, 2810 insertions(+), 83 deletions(-) create mode 100644 .changeset/complete-locale-backfill.md create mode 100644 packages/i18n/src/__tests__/all-locales-key-parity.test.ts delete mode 100644 packages/i18n/src/__tests__/high-frequency-namespace-parity.test.ts diff --git a/.changeset/complete-locale-backfill.md b/.changeset/complete-locale-backfill.md new file mode 100644 index 0000000000..df1fcf840a --- /dev/null +++ b/.changeset/complete-locale-backfill.md @@ -0,0 +1,55 @@ +--- +"@object-ui/i18n": patch +--- + +feat(i18n): complete the locale backfill — all ten packs reach full key parity (objectui#2872) + +Translates the remaining **275 keys × 8 packs = 2,200 strings**, closing +objectui#2872. The largest namespaces are `grid` (101, mostly the import +wizard), `gantt` (58) and `dashboard` (25), plus a long tail across `list`, +`auth`, `fields`, `marketplace`, `capability` and nine others. + +Every pack is now at parity with `en`: **2,495 of 2,499 keys**, zero keys that +`en` lacks. The four-key remainder is the outbound-message set, absent by +design so `t()` falls through to English and the cloud confirm gate keeps +recognising it — `outbound-agent-messages.test.ts` owns that invariant. + +**P3 is now enforceable.** `high-frequency-namespace-parity.test.ts` was scoped +to four namespaces because full parity would have been a permanently red build. +That restriction is obsolete, so it is replaced by +`all-locales-key-parity.test.ts`, which asserts: + +- every pack defines every `en` key; +- no pack defines a key `en` lacks (objectui#2872 part b was 74 keys of exactly + this, hidden behind a component-private fallback); +- **placeholders match `en` per string** — both `{{count}}` and the single-brace + `{count}` form, which two `gantt.autoScheduleDlg.*` keys use on purpose + because their call site does a literal `.replace('{count}', …)` rather than + i18next interpolation. A translation that drops a placeholder renders a + sentence with a hole in it and no error, so this is checked mechanically + rather than by eye. + +All three assertions were mutation-tested, including the single-brace form. + +### A bug the test suite could not have caught + +The first merge pass produced **duplicate keys** in four packs: the key list is +the union of what is missing across all eight, but the insert ran +unconditionally, so packs that already had `detail.created` / `detail.updated` +got a second copy. Every test still passed — at runtime the later property +simply wins, so the parity check saw a perfectly consistent object. + +`tsc` caught it as TS1117 during `turbo build`. ESLint does not flag it, and a +runtime test *cannot* — the duplicate is already collapsed before JS sees the +object. The compiler is the only possible guard here, and CI runs it. The merge +script now filters per pack against what that pack actually defines. + +### Translation quality + +Model-generated, and dense domain terminology (Gantt dependency types, the +import wizard's upsert/match-field vocabulary) is exactly where that is +weakest. This was raised before starting and the work was requested anyway, so +it ships as a **reviewable first draft, not a finished localization** — native +review is still worthwhile. What *is* verified mechanically: key parity in both +directions, placeholder shape per string, and that no outbound agent message +was translated. diff --git a/packages/i18n/src/__tests__/all-locales-key-parity.test.ts b/packages/i18n/src/__tests__/all-locales-key-parity.test.ts new file mode 100644 index 0000000000..fcbfdc227d --- /dev/null +++ b/packages/i18n/src/__tests__/all-locales-key-parity.test.ts @@ -0,0 +1,99 @@ +/** + * Full key parity across all ten locale packs (objectui#2872 P3). + * + * This replaces the four-namespace ratchet added in objectui#2905. That one was + * deliberately scoped because the eight non-Chinese packs were still ~277 keys + * behind, and a full assertion would have been a permanently red build rather + * than a guard. With the backfill complete, the scope restriction is gone and + * the invariant is simply: **every pack defines every `en` key, and no pack + * defines a key `en` lacks.** + * + * Why this needs a test at all: `fallbackLng: 'en'` makes both failure modes + * invisible at runtime. + * + * - A key missing from `de` renders English. That reads as "not translated + * yet", not "we lost this" — and the missing-key handler is dev-only, so CI + * never sees it. + * - A key added to one pack but never to `en` cannot be translated by anyone + * else and drifts silently. objectui#2872 part (b) was exactly this, 74 keys + * deep, hidden behind a component-private fallback that made English + * "happen to" render. + * + * The only permitted exception is the outbound-message set below. + */ +import { describe, it, expect } from 'vitest'; +import { builtInLocales } from '../locales'; + +/** + * Text the console SENDS to the agent rather than displays. These are absent + * from the eight non-gate packs ON PURPOSE, so `t()` falls through to its + * English default and the cloud confirm gate keeps recognising the message — + * see `outbound-agent-messages.test.ts`, which owns that invariant and asserts + * it in both directions. Excluded here so the two guards cannot contradict + * each other. + */ +const OUTBOUND_KEYS = new Set([ + 'console.ai.planApproveMessage', + 'console.ai.planApproveDefaultsMessage', + 'console.ai.planAnswerMessage', + 'console.ai.changesConfirmMessage', +]); + +function keyPaths(node: unknown, prefix = ''): string[] { + return node !== null && typeof node === 'object' + ? Object.entries(node as Record).flatMap(([k, v]) => + keyPaths(v, prefix ? `${prefix}.${k}` : k), + ) + : [prefix]; +} + +const keysOf = (pack: unknown) => new Set(keyPaths(pack).filter((k) => !OUTBOUND_KEYS.has(k))); + +const EN = keysOf(builtInLocales.en); +const OTHER_LOCALES = Object.keys(builtInLocales).filter((l) => l !== 'en'); + +describe('all locale packs are at full key parity with en (objectui#2872)', () => { + it('the comparison covers the whole pack — not an empty assertion', () => { + // If a refactor breaks `keyPaths`, every diff below becomes trivially empty + // and the suite would pass while asserting nothing. + expect(EN.size).toBeGreaterThan(2000); + expect(OTHER_LOCALES).toHaveLength(9); + }); + + it.each(OTHER_LOCALES)('%s defines every en key', (lang) => { + const missing = [...EN].filter((k) => !keysOf(builtInLocales[lang]).has(k)).sort(); + expect(missing, `${lang} is missing ${missing.length} key(s)`).toEqual([]); + }); + + it.each(OTHER_LOCALES)('%s defines no key that en lacks', (lang) => { + const extra = [...keysOf(builtInLocales[lang])].filter((k) => !EN.has(k)).sort(); + expect(extra, `${lang} has ${extra.length} key(s) absent from en`).toEqual([]); + }); + + it('placeholders match en in every pack', () => { + // A translation that drops `{{count}}` renders a sentence with a hole in it + // and no error. Two gantt keys use SINGLE braces on purpose — their call + // site does a literal `.replace('{count}', …)` instead of i18next + // interpolation — so both forms are compared. + const DOUBLE = /\{\{\w+\}\}/g; + const SINGLE = /(? + typeof v === 'string' + ? [...(v.match(DOUBLE) ?? []), ...(v.match(SINGLE) ?? [])].sort().join(',') + : null; + const at = (pack: unknown, dotted: string) => + dotted.split('.').reduce((n, p) => (n as Record)?.[p], pack); + + const mismatches: string[] = []; + for (const lang of OTHER_LOCALES) { + for (const key of EN) { + const a = shape(at(builtInLocales.en, key)); + const b = shape(at(builtInLocales[lang], key)); + if (a !== null && b !== null && a !== b) { + mismatches.push(`${lang} ${key}: en[${a}] vs ${lang}[${b}]`); + } + } + } + expect(mismatches).toEqual([]); + }); +}); diff --git a/packages/i18n/src/__tests__/high-frequency-namespace-parity.test.ts b/packages/i18n/src/__tests__/high-frequency-namespace-parity.test.ts deleted file mode 100644 index 7593a3af82..0000000000 --- a/packages/i18n/src/__tests__/high-frequency-namespace-parity.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Ratchet for the namespaces backfilled in objectui#2872 part (a). - * - * `console`, `home`, `topbar` and `layout` were translated into all ten packs - * (objectui#2903) because they carry the AI console, the home screen, the top - * bar and the system navigation — the surfaces a non-English admin cannot avoid. - * - * `fallbackLng: 'en'` makes a regression here invisible: drop a key from `de` - * and the UI renders English, which looks like "not translated yet" rather than - * "we lost this". Nothing errors, and the dev-only missing-key handler never - * fires in CI. So the backfill needs a ratchet or it quietly erodes. - * - * Scope is deliberately these four namespaces and no more. The rest of the - * packs are still ~277 keys behind (`grid` 101, `gantt` 58, `dashboard` 25 and - * a long tail) and stay tracked under objectui#2872 — asserting full parity - * today would just be a red CI with no action attached to it. Widen this list - * as each namespace is completed; do not widen it ahead of the translations. - */ -import { describe, it, expect } from 'vitest'; -import { builtInLocales } from '../locales'; - -/** - * The four keys the console SENDS to the agent rather than displays. They are - * intentionally absent from the eight non-gate packs so `t()` falls through to - * its English default — see `outbound-agent-messages.test.ts` for the full - * reasoning. Excluded here so the two guards do not contradict each other. - */ -const OUTBOUND_KEYS = new Set([ - 'console.ai.planApproveMessage', - 'console.ai.planApproveDefaultsMessage', - 'console.ai.planAnswerMessage', - 'console.ai.changesConfirmMessage', -]); - -const RATCHETED_NAMESPACES = ['console', 'home', 'topbar', 'layout'] as const; - -function keyPaths(node: unknown, prefix = ''): string[] { - return node !== null && typeof node === 'object' - ? Object.entries(node as Record).flatMap(([k, v]) => - keyPaths(v, prefix ? `${prefix}.${k}` : k), - ) - : [prefix]; -} - -function ratchetedKeys(pack: unknown): Set { - return new Set( - RATCHETED_NAMESPACES.flatMap((ns) => - keyPaths((pack as Record)[ns], ns), - ).filter((k) => !OUTBOUND_KEYS.has(k)), - ); -} - -const EN = ratchetedKeys(builtInLocales.en); -const OTHER_LOCALES = Object.keys(builtInLocales).filter((l) => l !== 'en'); - -describe('high-frequency namespaces stay at parity with en (objectui#2872 part a)', () => { - it('the ratchet covers a substantial key set — not an empty assertion', () => { - // Guards against the whole file silently becoming a no-op if a namespace - // is renamed and `keyPaths` starts returning nothing. - expect(EN.size).toBeGreaterThan(300); - for (const ns of RATCHETED_NAMESPACES) { - expect([...EN].some((k) => k.startsWith(`${ns}.`)), `${ns} contributed no keys`).toBe( - true, - ); - } - }); - - it.each(OTHER_LOCALES)('%s defines every ratcheted en key', (lang) => { - const missing = [...EN].filter((k) => !ratchetedKeys(builtInLocales[lang]).has(k)).sort(); - expect( - missing, - `${lang} is missing ${missing.length} key(s) from the backfilled namespaces`, - ).toEqual([]); - }); - - it.each(OTHER_LOCALES)('%s defines no ratcheted key that en lacks', (lang) => { - // The other direction: a key added to one pack but never to `en` cannot be - // translated by anyone else and will drift. objectui#2872 part (b) was - // exactly this, 74 keys deep, hidden behind a component-private fallback. - const extra = [...ratchetedKeys(builtInLocales[lang])].filter((k) => !EN.has(k)).sort(); - expect(extra, `${lang} has ${extra.length} key(s) absent from en`).toEqual([]); - }); -}); diff --git a/packages/i18n/src/locales/ar.ts b/packages/i18n/src/locales/ar.ts index 52de220dad..8832bf144c 100644 --- a/packages/i18n/src/locales/ar.ts +++ b/packages/i18n/src/locales/ar.ts @@ -1,6 +1,15 @@ const ar = { // objectui#2600 B5 — capability picker scope group headers (labels come from the sys_capability registry). capability: { + label: { + manage_users: "إدارة المستخدمين", + manage_org_users: "إدارة مستخدمي المؤسسة", + manage_metadata: "إدارة البيانات الوصفية", + manage_platform_settings: "إدارة إعدادات المنصة", + setup_access: "الوصول إلى الإعداد", + setup_write: "كتابة الإعدادات", + studio_access: "الوصول إلى Studio", + }, group: { platform: 'منصة', org: 'منظمة', @@ -98,6 +107,10 @@ const ar = { type: "{{field}} يجب أن يكون {{type}} صالحاً", }, form: { + discardTitle: "تجاهل التغييرات؟", + discardMessage: "لديك تغييرات غير محفوظة. إذا أغلقت هذا النموذج الآن، ستفقد تعديلاتك.", + keepEditing: "متابعة التحرير", + discard: "تجاهل", addItem: "إضافة عنصر", removeItem: "إزالة عنصر", fieldRequired: "هذا الحقل مطلوب", @@ -118,6 +131,19 @@ const ar = { deleteSuccess: "تم حذف {{object}}", }, fields: { + image: { + upload: "رفع صورة", + addMore: "إضافة المزيد من الصور", + uploading: "جارٍ الرفع…", + imageAlt: "صورة {{index}}", + crop: "قص الصورة {{index}}", + remove: "إزالة الصورة {{index}}", + enlarge: "تكبير {{name}}", + preview: "معاينة الصورة", + previous: "الصورة السابقة", + next: "الصورة التالية", + counter: "{{current}} / {{total}}", + }, relativeDate: { overdue: "متأخر {{count}} يوم", }, @@ -176,6 +202,36 @@ const ar = { delete: "حذف", }, grid: { + bulkSelected: "تم تحديد {{count}}", + bulkSelectedAllMatches: "تم تحديد {{count}} (كل النتائج المطابقة)", + bulkClear: "إلغاء التحديد", + bulkAllOnPage: "تم تحديد جميع العناصر ({{count}}) في هذه الصفحة.", + bulkSelectAllMatching: "تحديد كل النتائج المطابقة ({{count}})", + bulkAllMatchingSelected: "تم تحديد جميع السجلات المطابقة ({{count}}).", + bulk: { + confirmDefault: "سيُطبَّق هذا على {{count}} من السجلات.", + overLimit: "التحديد ({{count}}) يتجاوز حد الإجراء ({{limit}}). قلّل التحديد للمتابعة.", + affectedRecords: "السجلات المتأثرة ({{count}}):", + rowFallback: "الصف {{index}}", + andMore: "… و{{count}} أخرى", + processed: "تمت معالجة {{count}} / {{total}}", + processedFailed: " · فشل {{count}}", + undonePrefix: "تم التراجع — ", + succeeded: "نجح {{count}} / {{total}}", + resultFailed: " · فشل {{count}}", + retry: "إعادة المحاولة", + downloadErrorCsv: "تنزيل ملف CSV للأخطاء", + cancel: "إلغاء", + back: "رجوع", + next: "التالي", + run: "تشغيل", + running: "قيد التشغيل…", + undo: "تراجع", + undoing: "جارٍ التراجع…", + done: "تم", + selectPlaceholder: "اختر…", + loading: "جارٍ التحميل…", + }, actions: "إجراءات", edit: "تعديل", delete: "حذف", @@ -201,6 +257,85 @@ const ar = { densityCycleShortHint: "انقر للتغيير", }, import: { + downloadTemplate: "تنزيل القالب", + downloadTemplateHint: "احصل على ملف CSV بالأعمدة الصحيحة (الحقول المطلوبة معلَّمة بـ *).", + templateFileName: "{{object}}-import-template", + autoMatched: "تمت المطابقة تلقائيًا", + autoMatchedSummary: "تمت مطابقة {{count}} عمودًا تلقائيًا — راجعها وعدّلها أدناه.", + confidence: { + high: "ثقة عالية", + medium: "ثقة متوسطة", + low: "ثقة منخفضة", + }, + asyncQueued: "في الانتظار — يجري تجهيز الاستيراد…", + asyncProcessing: "جارٍ استيراد {{processed}} من {{total}} صفًا… {{progress}}%", + asyncLargeHint: "هذا الملف كبير، لذلك سيتم استيراده في الخلفية.", + largeSampleNotice: "معاينة أول {{shown}} صفًا من أصل {{total}}.", + cancelImport: "إلغاء الاستيراد", + importCancelled: "تم إلغاء الاستيراد", + resultsTruncated: "عرض نتائج أول {{count}} صفًا (من أصل {{total}}).", + createdCount: "تم إنشاء {{count}}", + updatedCount: "تم تحديث {{count}}", + downloadFailed: "تنزيل الصفوف الفاشلة", + options: "خيارات الاستيراد", + writeMode: "عند تطابق صف مع سجل موجود", + writeModeOpt: { + insert: "إنشاء سجل جديد دائمًا", + update: "تحديث الموجود (تخطٍّ عند عدم التطابق)", + upsert: "التحديث عند التطابق، وإلا الإنشاء", + }, + matchFields: "المطابقة حسب", + matchFieldsPlaceholder: "اختر حقول المطابقة…", + matchFieldsHint: "تتم مطابقة الصفوف بالسجلات الموجودة عبر هذه الحقول.", + needMatchFields: "اختر حقلًا واحدًا على الأقل للمطابقة.", + optCreateOptions: "الاحتفاظ بقيم الخيارات غير المعروفة", + optRunAutomations: "تشغيل الأتمتة والمشغّلات", + optTreatHistorical: "الاستيراد كبيانات تاريخية", + optTreatHistoricalHint: "(استيراد السجلات المكتملة كما هي — تخطي فحوصات آلة الحالات والاحتفاظ بالطوابع الزمنية والمؤلف الأصليين بدل ختمها الآن)", + optSkipBlankKey: "تخطي الصفوف التي تكون قيمة المطابقة فيها فارغة", + optBackground: "الاستيراد في الخلفية", + optBackgroundHint: "(يعمل كمهمة قابلة للتراجع)", + validate: "التحقق من البيانات", + validating: "جارٍ التحقق…", + validateHint: "التحقق من كل صف على الخادم قبل الاستيراد.", + validatePassed: "جميع الصفوف ({{ok}}) صالحة.", + validateFailed: "{{ok}} صالحة، و{{errors}} بها أخطاء.", + errorRowPrefix: "الصف {{row}}: ", + referenceNotFound: "لا يوجد سجل مطابق لـ «{{value}}»", + referenceAmbiguous: "«{{value}}» يطابق أكثر من سجل — استخدم قيمة فريدة أو معرّف السجل", + invalidBoolean: "«{{value}}» ليست قيمة صواب/خطأ صالحة", + invalidNumber: "«{{value}}» ليست رقمًا صالحًا", + invalidDate: "«{{value}}» ليست تاريخًا صالحًا", + invalidOption: "«{{value}}» ليست ضمن الخيارات المسموح بها", + requiredValue: "هذا الحقل مطلوب", + matchAmbiguous: "يطابق أكثر من سجل موجود — استخدم قيمة فريدة أو معرّف السجل", + history: "السجل", + historyBack: "العودة إلى الاستيراد", + historyDescription: "عمليات الاستيراد الأخيرة لهذا الكائن.", + historyHint: "مهام الاستيراد في الخلفية، الأحدث أولًا.", + historyRefresh: "تحديث", + historyLoading: "جارٍ التحميل…", + historyEmpty: "لا توجد عمليات استيراد بعد.", + historyUnsupported: "سجل الاستيراد غير متاح لمصدر البيانات هذا.", + historyColStatus: "الحالة", + historyColRows: "الصفوف", + historyColResult: "النتيجة", + historyColTime: "الوقت", + errorCount: "{{count}} من الأخطاء", + undoImport: "التراجع عن الاستيراد", + undoing: "جارٍ التراجع…", + undoConfirm: "هل تريد التراجع عن هذا الاستيراد؟ ستُحذف السجلات التي أنشأها وتُستعاد السجلات التي حدّثها إلى قيمها السابقة.", + reverted: "تم التراجع", + jobStatus: { + pending: "قيد الانتظار", + running: "قيد التنفيذ", + succeeded: "نجح", + failed: "فشل", + cancelled: "أُلغي", + }, + legacyReferenceBlocked: "تم حظر الاستيراد: {{fields}} حقول علاقات تحتاج إلى مسار الاستيراد على الخادم لتحويل الأسماء إلى معرّفات سجلات، وهذا الاتصال لا يدعم ذلك. استيرادها كنص عادي سيُفسد البيانات. حدّث الخادم/العميل، أو ألغِ ربط هذه الأعمدة واستوردها على حدة.", + missingRequiredHint: "لا يمكن المتابعة — حقول مطلوبة غير مرتبطة: {{fields}}. أضف عمودًا مطابقًا إلى ملفك، أو ارجع وارفع ملفًا يتضمنه.", + legacyFallbackNotice: "تم الاستيراد عبر وضع توافقي: هذا الاتصال لا يدعم مسار الاستيراد على الخادم، لذلك حُفظت القيم كنص دون تحويل للأنواع على الخادم. حدّث الخادم/العميل للحصول على دعم الاستيراد الكامل (تحويل الأنواع والبحث عن العلاقات).", title: "استيراد {{object}}", notAllowed: "هذا الكائن غير متاح للاستيراد.", stepUpload: "رفع", @@ -278,6 +413,36 @@ const ar = { moreEvents: "+{{count}} المزيد", }, list: { + firstRunTitle: "لا يوجد شيء هنا بعد", + firstRunMessage: "أنشئ سجلك الأول للبدء.", + noMatches: "لا توجد سجلات مطابقة", + noMatchesMessage: "لا توجد سجلات تطابق عوامل التصفية أو البحث الحالية. حاول تعديلها أو مسحها.", + loadErrorTitle: "تعذّر تحميل السجلات", + loadErrorMessage: "حدث خطأ أثناء تحميل هذه البيانات. تحقق من اتصالك وحاول مرة أخرى.", + retry: "إعادة المحاولة", + managedBy: { + system: { + title: "لا يوجد شيء هنا بعد", + message: "تظهر الإدخالات تلقائيًا عند تنفيذ الإجراء المصدر (مثل إرسال للموافقة، أو المشاركة، أو الدعوة). نفّذ أحدها على سجل مصدر لإنشاء صف.", + }, + appendOnly: { + title: "لم تُسجَّل أي أحداث", + message: "هذا سجل تدقيق غير قابل للتعديل. تكتب المنصة الصفوف عند وقوع الأحداث — يمكنك تصدير السجل لكن لا يمكنك إنشاء إدخالات من هنا.", + }, + betterAuth: { + title: "لا توجد سجلات هوية", + message: "ينشئ مزوّد المصادقة هذه السجلات — من خلال تسجيل الدخول والتزويد وإجراءات الأمان — ولا تُضاف يدويًا من هنا.", + }, + betterAuthUser: { + title: "لا يوجد مستخدمون بعد", + message: "يزوّد مزوّد المصادقة حسابات المستخدمين، ولا تُنشأ من هنا. ادعُ زملاءك إلى مؤسستك وسيظهرون تلقائيًا عند أول تسجيل دخول (التزويد الفوري عبر SSO). أما مستخدمو التطبيق النهائيون فيُضافون عند تسجيلهم عبر تطبيقك.", + }, + betterAuthTeam: { + title: "لا توجد فرق بعد", + message: "تجمع الفرق الأعضاء داخل المؤسسة. أنشئ فريقًا عبر «إنشاء فريق»، أو تأتي الفرق من خلال إجراءات تزويد المؤسسات و SSO لدى مزوّد المصادقة.", + }, + }, + refresh: "تحديث", loading: 'Loading records...', recordCount: "{{count}} سجلات", recordCountOne: "{{count}} سجل", @@ -318,6 +483,11 @@ const ar = { recordEditingTitle: "تحرير السجلات", }, managedByBadge: { + systemWritable: { + short: "مخطط المنصة", + title: "تحدده المنصة وقابل للكتابة من المسؤول", + body: "تحدد المنصة مخطط هذا الكائن، لكن صفوفه لك تنشئها وتحررها هنا. ومن يحق له الكتابة تحدده الإدارة المفوَّضة والأمان على مستوى السجل، لا هذه الشارة.", + }, config: { short: "إعدادات المسؤول", title: "تكوين المسؤول", @@ -340,6 +510,7 @@ const ar = { }, }, kanban: { + uncategorized: "غير مصنّف", addCard: "إضافة بطاقة", addColumn: "إضافة عمود", moveCard: "نقل بطاقة", @@ -361,12 +532,92 @@ const ar = { }, }, gantt: { + viewMode: { + day: "يوم", + week: "أسبوع", + month: "شهر", + quarter: "ربع سنة", + year: "سنة", + }, + row: { + expand: "توسيع", + collapse: "طي", + open: "فتح التفاصيل", + }, + aria: { + taskList: "قائمة المهام", + }, + tooltip: { + days: "ي", + }, + menu: { + view: "عرض التفاصيل", + edit: "تحرير مباشر", + delete: "حذف", + addPredecessor: "إضافة مهمة سابقة…", + addSuccessor: "إضافة مهمة لاحقة…", + removeDependency: "إزالة التبعية", + noCandidates: "لا توجد مهام متاحة", + searchTasks: "البحث في المهام…", + }, + delete: { + title: "حذف هذه المهمة؟", + body: "ستُحذف «{{title}}» نهائيًا. لا يمكن التراجع عن هذا الإجراء.", + cancel: "إلغاء", + confirm: "حذف", + deleting: "جارٍ الحذف…", + }, + drawer: { + fallbackTitle: "تفاصيل المهمة", + }, + linkType: { + fs: "نهاية → بداية", + ss: "بداية → بداية", + ff: "نهاية → نهاية", + sf: "بداية → نهاية", + }, + conflict: { + title: "تعارض في الجدولة", + body: "يتعارض هذا النقل مع قيود التبعية. هل تريد إعادة جدولة {{count}} من المهام المتأثرة تلقائيًا؟", + confirm: "إعادة الجدولة تلقائيًا", + cancel: "الإبقاء كما هو", + }, + autoScheduleDlg: { + title: "الجدولة التلقائية", + body: "هل تريد تأخير {count} من المهام لتلبية روابط التبعية؟", + skipped: "{count} من المهام المقفلة تخالف الروابط أيضًا وقد تم تخطيها.", + confirm: "تطبيق", + cancel: "إلغاء", + none: "جميع التبعيات مستوفاة — لا شيء لإعادة جدولته.", + }, + resource: { + header: "المورد", + peak: "الذروة", + over: "محمّل بإفراط", + empty: "لا توجد مهام لتخصيصها.", + }, + readOnly: "للقراءة فقط", + readOnlyHint: "التحرير معطّل في هذا العرض.", + lockedHint: "لا توجد صلاحية تحرير", + writeFailed: "فشل الحفظ — تم التراجع عن التغيير", column: { taskName: "اسم المهمة", start: "البداية", end: "النهاية", }, toolbar: { + thisWeek: "هذا الأسبوع", + thisMonth: "هذا الشهر", + viewMode: "دقة الخط الزمني", + enterFullscreen: "ملء الشاشة", + exitFullscreen: "إنهاء ملء الشاشة", + criticalPath: "إبراز المسار الحرج", + autoSchedule: "جدولة التبعيات تلقائيًا", + exportPng: "تصدير بصيغة PNG", + exportPdf: "تصدير PDF", + saveLayout: "حفظ التخطيط", + undo: "تراجع", + redo: "إعادة", prevPeriod: "الفترة السابقة", nextPeriod: "الفترة التالية", zoomIn: "تكبير", @@ -412,6 +663,11 @@ const ar = { redo: 'Redo', }, detail: { + editInlineHint: "انقر نقرًا مزدوجًا للتحرير", + systemActor: "النظام", + unknownUser: "غير معروف", + created: "أُنشئ", + updated: "حُدِّث", back: "رجوع", edit: "تحرير", editInline: "تحرير", @@ -624,6 +880,35 @@ const ar = { resetZoom: "إعادة تعيين التكبير", }, dashboard: { + noRows: "لا توجد صفوف", + pickMeasures: "اختر المقاييس (القيم) لأداة مجموعة البيانات هذه.", + datasetUnsupported: "مصدر البيانات هذا لا يدعم استعلامات مجموعات البيانات.", + details: "التفاصيل", + exportCsv: "تصدير CSV", + openInList: "فتح في القائمة", + filters: { + label: "عوامل تصفية لوحة المعلومات", + dateRange: "النطاق الزمني", + allTime: "كل الأوقات", + custom: "مخصص…", + all: "الكل", + reset: "إعادة تعيين", + range: { + today: "اليوم", + yesterday: "أمس", + this_week: "هذا الأسبوع", + last_week: "الأسبوع الماضي", + this_month: "هذا الشهر", + last_month: "الشهر الماضي", + this_quarter: "هذا الربع", + last_quarter: "الربع الماضي", + this_year: "هذه السنة", + last_year: "السنة الماضية", + last_7_days: "آخر 7 أيام", + last_30_days: "آخر 30 يومًا", + last_90_days: "آخر 90 يومًا", + }, + }, addWidget: "إضافة أداة", removeWidget: "إزالة أداة", editLayout: "تعديل التخطيط", @@ -1341,7 +1626,28 @@ const ar = { }, }, auth: { + setPassword: { + title: "تعيين كلمة مرور للاسترداد", + description: "لقد سجّلت الدخول عبر الدخول الموحّد. عيّن كلمة مرور محلية لتتمكن من الدخول إلى هذه البيئة مباشرةً إذا أصبح الدخول الموحّد غير متاح.", + email: "البريد الإلكتروني", + newPassword: "كلمة المرور الجديدة", + confirmPassword: "تأكيد كلمة المرور", + submit: "تعيين كلمة المرور", + submitting: "جارٍ الحفظ…", + success: "تم تعيين كلمة المرور المحلية", + failed: "تعذّر تعيين كلمة المرور", + passwordsMismatch: "كلمتا المرور غير متطابقتين", + noSession: "انتهت صلاحية جلستك.", + backToSignIn: "تسجيل الدخول مرة أخرى", + }, login: { + signingIn: "جارٍ تسجيل دخولك…", + ssoHandoff: "المتابعة إلى {{target}}", + devAdminHint: { + title: "بيئة تطوير", + body: "سجّل الدخول بحساب مسؤول التطوير المهيأ مسبقًا:", + dismiss: "تجاهل", + }, title: "تسجيل الدخول إلى حسابك", description: "أدخل بريدك الإلكتروني وكلمة المرور للمتابعة", emailLabel: "البريد الإلكتروني", @@ -1354,6 +1660,7 @@ const ar = { noAccountText: "ليس لديك حساب؟", signUpText: "إنشاء حساب", errors: { + oauthCallbackFailed: "تعذّر إكمال الدخول الموحّد — انتهت صلاحية رابط تسجيل الدخول أو سبق استخدامه. يرجى المحاولة مرة أخرى.", invalidCredentials: "بريد إلكتروني أو كلمة مرور غير صحيحة. يرجى المحاولة مرة أخرى.", emailNotVerified: "يرجى التحقق من بريدك الإلكتروني قبل تسجيل الدخول.", }, @@ -1578,11 +1885,15 @@ const ar = { settings: "إعدادات مساحة العمل", }, help: { + onThisPage: "في هذه الصفحة", + keyboardShortcuts: "اختصارات لوحة المفاتيح", appDocs: 'وثائق هذا التطبيق', allDocs: 'كل الوثائق', onlineDocs: 'الوثائق عبر الإنترنت', }, sidebar: { + packageManagement: "إدارة الحزم", + searchNavigation: "البحث في التنقل...", settings: "الإعدادات", help: "المساعدة", helpTooltip: "المساعدة والوثائق", @@ -1843,6 +2154,8 @@ const ar = { updateFailed: "تعذر تحديث التنقل", }, objectActions: { + resetPackageSetConfirm: "مجموعة الأذونات هذه تأتي ضمن حزمة مثبَّتة ولا يمكن إزالتها. الحذف يعيدها إلى الحالة الأصلية المرفقة بالحزمة ويتجاهل تخصيصاتك في هذه البيئة. هل تريد المتابعة؟", + resetPackageSetSuccess: "تمت إعادة مجموعة الأذونات إلى حالتها الأصلية المرفقة بالحزمة", deleteSuccess: "تم حذف {{label}} بنجاح", deleteFailed: "تعذر حذف {{label}}", noRecordId: "لم يتم توفير معرف سجل", @@ -2007,6 +2320,7 @@ const ar = { }, }, organizations: { + create: "إنشاء مساحة عمل", mine: "مؤسساتي", title: "المؤسسات", heading: "مؤسساتك", @@ -2020,6 +2334,11 @@ const ar = { noMatches: "لا توجد مؤسسات تطابق بحثك.", }, notifications: { + regionLabel: "الإشعارات", + groupCount: "{{count}} من الإشعارات", + groupUnread: "{{count}} غير مقروء", + groupMarkRead: "تعليم كمقروء", + groupMarkReadTitle: "تعليم كل هذا النوع كمقروء", empty: "لا توجد إشعارات", markAllRead: "تعليم الكل كمقروء", viewAll: "عرض جميع الإشعارات", @@ -2222,6 +2541,15 @@ const ar = { }, }, marketplace: { + suggestedBindings: { + promptEveryone: "يقترح هذا التطبيق منح «{{set}}» لجميع المستخدمين المسجّلين (موضع Everyone).", + promptGuest: "يقترح هذا التطبيق منح «{{set}}» للزوار غير المصادَق عليهم (موضع Guest).", + confirm: "منح", + confirming: "جارٍ المنح…", + dismiss: "تجاهل", + confirmedToast: "تم الآن منح «{{set}}» إلى {{anchor}}.", + dismissedToast: "تم تجاهل اقتراح «{{set}}».", + }, title: "سوق التطبيقات", subtitle: "استعرض التطبيقات المعتمدة المنشورة في كتالوج ObjectStack. انقر على تطبيق لرؤية التفاصيل وتثبيته في أحد بيئاتك.", searchPlaceholder: "ابحث عن التطبيقات بالاسم أو معرف المانيفست…", @@ -2273,6 +2601,7 @@ const ar = { updateAvailable: 'يتوفر تحديث', }, action: { + installed: "مثبَّت", install: "تثبيت", reinstall: "إعادة التثبيت", working: "جارٍ المعالجة…", @@ -2524,6 +2853,10 @@ const ar = { retry: 'إعادة المحاولة', }, draftBar: { + messageClean: "معاينة المسودة — لا توجد تغييرات غير منشورة؛ كل ما هنا منشور بالفعل.", + sampleDataTitle: "تظهر البيانات التجريبية بعد النشر", + sampleDataBody: "أنت تعاين بنية تطبيقك. انشره لتحميل سجلات تجريبية وتفعيله.", + publishCta: "انشر لتراه مباشرةً", message: 'معاينة المسودة — أنت ترى تغييرات غير منشورة. لن يصبح أي شيء فعليًا حتى تنشر.', publish: 'نشر', publishing: 'جارٍ النشر…', diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index c9ae22a99e..0222698511 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -1,6 +1,15 @@ const de = { // objectui#2600 B5 — capability picker scope group headers (labels come from the sys_capability registry). capability: { + label: { + manage_users: "Benutzer verwalten", + manage_org_users: "Organisationsbenutzer verwalten", + manage_metadata: "Metadaten verwalten", + manage_platform_settings: "Plattformeinstellungen verwalten", + setup_access: "Zugriff auf die Einrichtung", + setup_write: "Einstellungen schreiben", + studio_access: "Studio-Zugriff", + }, group: { platform: 'Plattform', org: 'Organisation', @@ -98,6 +107,10 @@ const de = { type: "{{field}} muss ein gültiger {{type}} sein", }, form: { + discardTitle: "Änderungen verwerfen?", + discardMessage: "Sie haben ungespeicherte Änderungen. Wenn Sie dieses Formular jetzt schließen, gehen Ihre Bearbeitungen verloren.", + keepEditing: "Weiter bearbeiten", + discard: "Verwerfen", addItem: "Element hinzufügen", removeItem: "Element entfernen", fieldRequired: "Dieses Feld ist erforderlich", @@ -118,6 +131,19 @@ const de = { deleteSuccess: "{{object}} erfolgreich gelöscht", }, fields: { + image: { + upload: "Bild hochladen", + addMore: "Weitere Bilder hinzufügen", + uploading: "Wird hochgeladen…", + imageAlt: "Bild {{index}}", + crop: "Bild {{index}} zuschneiden", + remove: "Bild {{index}} entfernen", + enlarge: "{{name}} vergrößern", + preview: "Bildvorschau", + previous: "Vorheriges Bild", + next: "Nächstes Bild", + counter: "{{current}} / {{total}}", + }, relativeDate: { overdue: "{{count}} T. überfällig", }, @@ -176,6 +202,36 @@ const de = { delete: "Löschen", }, grid: { + bulkSelected: "{{count}} ausgewählt", + bulkSelectedAllMatches: "{{count}} ausgewählt (alle Treffer)", + bulkClear: "Auswahl aufheben", + bulkAllOnPage: "Alle {{count}} auf dieser Seite sind ausgewählt.", + bulkSelectAllMatching: "Alle {{count}} Treffer auswählen", + bulkAllMatchingSelected: "Alle {{count}} übereinstimmenden Datensätze sind ausgewählt.", + bulk: { + confirmDefault: "Dies wird auf {{count}} Datensätze angewendet.", + overLimit: "Die Auswahl ({{count}}) überschreitet das Aktionslimit ({{limit}}). Verkleinern Sie die Auswahl, um fortzufahren.", + affectedRecords: "Betroffene Datensätze ({{count}}):", + rowFallback: "Zeile {{index}}", + andMore: "… und {{count}} weitere", + processed: "{{count}} / {{total}} verarbeitet", + processedFailed: " · {{count}} fehlgeschlagen", + undonePrefix: "Rückgängig gemacht — ", + succeeded: "Erfolgreich {{count}} / {{total}}", + resultFailed: " · Fehlgeschlagen {{count}}", + retry: "Erneut versuchen", + downloadErrorCsv: "Fehler-CSV herunterladen", + cancel: "Abbrechen", + back: "Zurück", + next: "Weiter", + run: "Ausführen", + running: "Wird ausgeführt…", + undo: "Rückgängig", + undoing: "Wird rückgängig gemacht…", + done: "Fertig", + selectPlaceholder: "Auswählen…", + loading: "Wird geladen…", + }, actions: "Aktionen", edit: "Bearbeiten", delete: "Löschen", @@ -201,6 +257,85 @@ const de = { densityCycleShortHint: "Klicken zum Wechseln", }, import: { + downloadTemplate: "Vorlage herunterladen", + downloadTemplateHint: "Laden Sie eine CSV mit den richtigen Spalten herunter (Pflichtfelder sind mit * markiert).", + templateFileName: "{{object}}-import-template", + autoMatched: "Automatisch zugeordnet", + autoMatchedSummary: "{{count}} Spalte(n) automatisch zugeordnet — unten prüfen und anpassen.", + confidence: { + high: "Hohe Konfidenz", + medium: "Mittlere Konfidenz", + low: "Geringe Konfidenz", + }, + asyncQueued: "In Warteschlange — Import wird vorbereitet…", + asyncProcessing: "{{processed}} von {{total}} Zeilen werden importiert… {{progress}} %", + asyncLargeHint: "Diese Datei ist groß und wird daher im Hintergrund importiert.", + largeSampleNotice: "Vorschau der ersten {{shown}} von {{total}} Zeilen.", + cancelImport: "Import abbrechen", + importCancelled: "Import abgebrochen", + resultsTruncated: "Es werden die ersten {{count}} Zeilenergebnisse (von {{total}}) angezeigt.", + createdCount: "{{count}} erstellt", + updatedCount: "{{count}} aktualisiert", + downloadFailed: "Fehlgeschlagene Zeilen herunterladen", + options: "Importoptionen", + writeMode: "Wenn eine Zeile zu einem vorhandenen Datensatz passt", + writeModeOpt: { + insert: "Immer neu anlegen", + update: "Vorhandene aktualisieren (ohne Treffer überspringen)", + upsert: "Bei Treffer aktualisieren, sonst anlegen", + }, + matchFields: "Abgleich über", + matchFieldsPlaceholder: "Abgleichsfeld(er) wählen…", + matchFieldsHint: "Zeilen werden über diese Felder vorhandenen Datensätzen zugeordnet.", + needMatchFields: "Wählen Sie mindestens ein Feld für den Abgleich aus.", + optCreateOptions: "Unbekannte Optionswerte beibehalten", + optRunAutomations: "Automatisierungen und Trigger ausführen", + optTreatHistorical: "Als historische Daten importieren", + optTreatHistoricalHint: "(abgeschlossene Datensätze unverändert importieren — Zustandsautomat-Prüfungen überspringen und die ursprünglichen Zeitstempel und Autoren beibehalten, statt jetzt zu stempeln)", + optSkipBlankKey: "Zeilen mit leerem Abgleichswert überspringen", + optBackground: "Im Hintergrund importieren", + optBackgroundHint: "(läuft als rückgängig machbarer Auftrag)", + validate: "Daten prüfen", + validating: "Wird geprüft…", + validateHint: "Jede Zeile vor dem Import auf dem Server prüfen.", + validatePassed: "Alle {{ok}} Zeilen sind gültig.", + validateFailed: "{{ok}} gültig, {{errors}} mit Fehlern.", + errorRowPrefix: "Zeile {{row}}: ", + referenceNotFound: "Kein passender Datensatz für „{{value}}“", + referenceAmbiguous: "„{{value}}“ passt zu mehreren Datensätzen — verwenden Sie einen eindeutigen Wert oder die Datensatz-ID", + invalidBoolean: "„{{value}}“ ist kein gültiger Wahr/Falsch-Wert", + invalidNumber: "„{{value}}“ ist keine gültige Zahl", + invalidDate: "„{{value}}“ ist kein gültiges Datum", + invalidOption: "„{{value}}“ gehört nicht zu den zulässigen Optionen", + requiredValue: "Dieses Feld ist erforderlich", + matchAmbiguous: "Passt zu mehreren vorhandenen Datensätzen — verwenden Sie einen eindeutigen Wert oder die Datensatz-ID", + history: "Verlauf", + historyBack: "Zurück zum Import", + historyDescription: "Letzte Importe für dieses Objekt.", + historyHint: "Hintergrund-Importaufträge, neueste zuerst.", + historyRefresh: "Aktualisieren", + historyLoading: "Wird geladen…", + historyEmpty: "Noch keine Importe.", + historyUnsupported: "Für diese Datenquelle ist kein Importverlauf verfügbar.", + historyColStatus: "Status", + historyColRows: "Zeilen", + historyColResult: "Ergebnis", + historyColTime: "Wann", + errorCount: "{{count}} Fehler", + undoImport: "Import rückgängig machen", + undoing: "Wird rückgängig gemacht…", + undoConfirm: "Diesen Import rückgängig machen? Dabei erstellte Datensätze werden gelöscht und aktualisierte Datensätze auf ihre vorherigen Werte zurückgesetzt.", + reverted: "Rückgängig gemacht", + jobStatus: { + pending: "Ausstehend", + running: "Läuft", + succeeded: "Erfolgreich", + failed: "Fehlgeschlagen", + cancelled: "Abgebrochen", + }, + legacyReferenceBlocked: "Import blockiert: {{fields}} sind Beziehungsfelder, die den Server-Importweg benötigen, um Namen in Datensatz-IDs aufzulösen — diese Verbindung unterstützt das nicht. Ein Import als reiner Text würde die Daten beschädigen. Aktualisieren Sie Backend/Client, oder heben Sie die Zuordnung dieser Spalten auf und importieren Sie sie separat.", + missingRequiredHint: "Fortfahren nicht möglich — nicht zugeordnete Pflichtfelder: {{fields}}. Fügen Sie Ihrer Datei eine passende Spalte hinzu oder gehen Sie zurück und laden Sie eine Datei hoch, die sie enthält.", + legacyFallbackNotice: "Über einen Kompatibilitäts-Fallback importiert: Diese Verbindung unterstützt den Server-Importweg nicht, daher wurden die Werte ohne serverseitige Typkonvertierung als Text gespeichert. Aktualisieren Sie Backend/Client für vollständige Importunterstützung (Typkonvertierung und Beziehungssuche).", title: "{{object}} importieren", notAllowed: "Dieses Objekt ist nicht für den Import freigegeben.", stepUpload: "Hochladen", @@ -278,6 +413,36 @@ const de = { moreEvents: "+{{count}} weitere", }, list: { + firstRunTitle: "Hier ist noch nichts", + firstRunMessage: "Legen Sie Ihren ersten Datensatz an, um loszulegen.", + noMatches: "Keine passenden Datensätze", + noMatchesMessage: "Keine Datensätze entsprechen Ihren aktuellen Filtern oder Ihrer Suche. Passen Sie sie an oder setzen Sie sie zurück.", + loadErrorTitle: "Datensätze konnten nicht geladen werden", + loadErrorMessage: "Beim Laden dieser Daten ist etwas schiefgelaufen. Prüfen Sie Ihre Verbindung und versuchen Sie es erneut.", + retry: "Erneut versuchen", + managedBy: { + system: { + title: "Hier ist noch nichts", + message: "Einträge erscheinen automatisch, wenn die zugehörige Aktion ausgeführt wird (z. B. Zur Genehmigung einreichen, Teilen, Einladen). Lösen Sie eine davon an einem Quelldatensatz aus, um eine Zeile zu erzeugen.", + }, + appendOnly: { + title: "Keine Ereignisse erfasst", + message: "Dies ist ein unveränderliches Audit-Protokoll. Zeilen werden von der Plattform geschrieben, wenn Ereignisse eintreten — Sie können den Verlauf exportieren, aber hier keine Einträge anlegen.", + }, + betterAuth: { + title: "Keine Identitätsdatensätze", + message: "Diese Datensätze werden vom Authentifizierungsanbieter erstellt — durch Anmeldung, Bereitstellung und Sicherheitsabläufe — und nicht hier von Hand hinzugefügt.", + }, + betterAuthUser: { + title: "Noch keine Benutzer", + message: "Benutzerkonten werden vom Authentifizierungsanbieter bereitgestellt und nicht hier erstellt. Laden Sie Kolleginnen und Kollegen in Ihre Organisation ein — sie erscheinen bei der ersten Anmeldung automatisch (SSO Just-in-time-Provisionierung). Endnutzer Ihrer App kommen hinzu, sobald sie sich über Ihre App registrieren.", + }, + betterAuthTeam: { + title: "Noch keine Teams", + message: "Teams fassen Mitglieder innerhalb einer Organisation zusammen. Legen Sie eines über „Team erstellen“ an, oder sie entstehen über die Organisations- und SSO-Provisionierung Ihres Authentifizierungsanbieters.", + }, + }, + refresh: "Aktualisieren", loading: 'Loading records...', recordCount: "{{count}} Datensätze", recordCountOne: "{{count}} Datensatz", @@ -318,6 +483,11 @@ const de = { recordEditingTitle: "Datensatzbearbeitung", }, managedByBadge: { + systemWritable: { + short: "Plattform-Schema", + title: "Plattformdefiniert, für Admins beschreibbar", + body: "Das Schema dieses Objekts wird von der Plattform definiert, seine Zeilen können Sie hier jedoch selbst anlegen und bearbeiten. Wer schreiben darf, regeln die delegierte Administration und die Datensatzsicherheit — nicht dieses Abzeichen.", + }, config: { short: "Admin-Konfiguration", title: "Administrator-Konfiguration", @@ -340,6 +510,7 @@ const de = { }, }, kanban: { + uncategorized: "Nicht kategorisiert", addCard: "Karte hinzufügen", addColumn: "Spalte hinzufügen", moveCard: "Karte verschieben", @@ -361,12 +532,92 @@ const de = { }, }, gantt: { + viewMode: { + day: "Tag", + week: "Woche", + month: "Monat", + quarter: "Quartal", + year: "Jahr", + }, + row: { + expand: "Aufklappen", + collapse: "Zuklappen", + open: "Details öffnen", + }, + aria: { + taskList: "Vorgangsliste", + }, + tooltip: { + days: "T", + }, + menu: { + view: "Details anzeigen", + edit: "Inline bearbeiten", + delete: "Löschen", + addPredecessor: "Vorgänger hinzufügen…", + addSuccessor: "Nachfolger hinzufügen…", + removeDependency: "Abhängigkeit entfernen", + noCandidates: "Keine verfügbaren Vorgänge", + searchTasks: "Vorgänge suchen…", + }, + delete: { + title: "Diesen Vorgang löschen?", + body: "„{{title}}“ wird dauerhaft entfernt. Diese Aktion kann nicht rückgängig gemacht werden.", + cancel: "Abbrechen", + confirm: "Löschen", + deleting: "Wird gelöscht…", + }, + drawer: { + fallbackTitle: "Vorgangsdetails", + }, + linkType: { + fs: "Ende → Anfang", + ss: "Anfang → Anfang", + ff: "Ende → Ende", + sf: "Anfang → Ende", + }, + conflict: { + title: "Terminkonflikt", + body: "Diese Verschiebung verstößt gegen Abhängigkeitsbedingungen. {{count}} betroffene Vorgänge automatisch neu planen?", + confirm: "Automatisch neu planen", + cancel: "Unverändert lassen", + }, + autoScheduleDlg: { + title: "Automatisch planen", + body: "{count} Vorgänge nach hinten verschieben, um die Abhängigkeiten einzuhalten?", + skipped: "{count} gesperrte Vorgänge verletzen die Verknüpfungen ebenfalls und wurden übersprungen.", + confirm: "Anwenden", + cancel: "Abbrechen", + none: "Alle Abhängigkeiten sind erfüllt — nichts neu zu planen.", + }, + resource: { + header: "Ressource", + peak: "Spitze", + over: "überlastet", + empty: "Keine Vorgänge zum Zuweisen.", + }, + readOnly: "Schreibgeschützt", + readOnlyHint: "Die Bearbeitung ist in dieser Ansicht deaktiviert.", + lockedHint: "Keine Bearbeitungsberechtigung", + writeFailed: "Speichern fehlgeschlagen — die Änderung wurde zurückgenommen", column: { taskName: "Aufgabenname", start: "Start", end: "Ende", }, toolbar: { + thisWeek: "Diese Woche", + thisMonth: "Dieser Monat", + viewMode: "Zeitachsen-Granularität", + enterFullscreen: "Vollbild aktivieren", + exitFullscreen: "Vollbild beenden", + criticalPath: "Kritischen Pfad hervorheben", + autoSchedule: "Abhängigkeiten automatisch planen", + exportPng: "Als PNG exportieren", + exportPdf: "PDF exportieren", + saveLayout: "Layout speichern", + undo: "Rückgängig", + redo: "Wiederholen", prevPeriod: "Vorherige Periode", nextPeriod: "Nächste Periode", zoomIn: "Vergrößern", @@ -412,6 +663,9 @@ const de = { redo: 'Redo', }, detail: { + editInlineHint: "Zum Bearbeiten doppelklicken", + systemActor: "System", + unknownUser: "Unbekannt", back: "Zurück", edit: "Bearbeiten", editInline: "Bearbeiten", @@ -626,6 +880,35 @@ const de = { resetZoom: "Zoom zurücksetzen", }, dashboard: { + noRows: "Keine Zeilen", + pickMeasures: "Wählen Sie Kennzahlen (Werte) für dieses Dataset-Widget.", + datasetUnsupported: "Diese Datenquelle unterstützt keine Dataset-Abfragen.", + details: "Details", + exportCsv: "CSV exportieren", + openInList: "In Liste öffnen", + filters: { + label: "Dashboard-Filter", + dateRange: "Zeitraum", + allTime: "Gesamter Zeitraum", + custom: "Benutzerdefiniert…", + all: "Alle", + reset: "Zurücksetzen", + range: { + today: "Heute", + yesterday: "Gestern", + this_week: "Diese Woche", + last_week: "Letzte Woche", + this_month: "Dieser Monat", + last_month: "Letzter Monat", + this_quarter: "Dieses Quartal", + last_quarter: "Letztes Quartal", + this_year: "Dieses Jahr", + last_year: "Letztes Jahr", + last_7_days: "Letzte 7 Tage", + last_30_days: "Letzte 30 Tage", + last_90_days: "Letzte 90 Tage", + }, + }, addWidget: "Widget hinzufügen", removeWidget: "Widget entfernen", editLayout: "Layout bearbeiten", @@ -1343,7 +1626,28 @@ const de = { }, }, auth: { + setPassword: { + title: "Wiederherstellungspasswort festlegen", + description: "Sie haben sich über Single Sign-on angemeldet. Legen Sie ein lokales Passwort fest, damit Sie sich weiterhin direkt an dieser Umgebung anmelden können, falls SSO einmal nicht verfügbar ist.", + email: "E-Mail", + newPassword: "Neues Passwort", + confirmPassword: "Passwort bestätigen", + submit: "Passwort festlegen", + submitting: "Wird gespeichert…", + success: "Lokales Passwort festgelegt", + failed: "Passwort konnte nicht festgelegt werden", + passwordsMismatch: "Die Passwörter stimmen nicht überein", + noSession: "Ihre Sitzung ist abgelaufen.", + backToSignIn: "Erneut anmelden", + }, login: { + signingIn: "Sie werden angemeldet…", + ssoHandoff: "Weiter zu {{target}}", + devAdminHint: { + title: "Entwicklungsinstanz", + body: "Melden Sie sich mit dem vorkonfigurierten Entwickler-Admin an:", + dismiss: "Ausblenden", + }, title: "In Ihr Konto einloggen", description: "Geben Sie Ihre E-Mail-Adresse und Ihr Passwort ein, um fortzufahren", emailLabel: "E-Mail", @@ -1356,6 +1660,7 @@ const de = { noAccountText: "Noch kein Konto?", signUpText: "Registrieren", errors: { + oauthCallbackFailed: "Die Single-Sign-on-Anmeldung konnte nicht abgeschlossen werden — der Anmeldelink ist abgelaufen oder wurde bereits verwendet. Bitte versuchen Sie es erneut.", invalidCredentials: "Ungültige E-Mail oder Passwort. Bitte erneut versuchen.", emailNotVerified: "Bitte bestätigen Sie Ihre E-Mail-Adresse, bevor Sie sich anmelden.", }, @@ -1580,11 +1885,15 @@ const de = { settings: "Arbeitsbereichseinstellungen", }, help: { + onThisPage: "Auf dieser Seite", + keyboardShortcuts: "Tastenkürzel", appDocs: 'Dokumentation dieser App', allDocs: 'Gesamte Dokumentation', onlineDocs: 'Online-Dokumentation', }, sidebar: { + packageManagement: "Paketverwaltung", + searchNavigation: "Navigation durchsuchen...", settings: "Einstellungen", help: "Hilfe", helpTooltip: "Hilfe und Dokumentation", @@ -1845,6 +2154,8 @@ const de = { updateFailed: "Navigation konnte nicht aktualisiert werden", }, objectActions: { + resetPackageSetConfirm: "Dieses Berechtigungsset gehört zu einem installierten Paket und kann nicht entfernt werden. Beim Löschen wird es auf den ausgelieferten Ausgangszustand zurückgesetzt und Ihre Anpassungen in dieser Umgebung gehen verloren. Fortfahren?", + resetPackageSetSuccess: "Berechtigungsset auf den ausgelieferten Ausgangszustand zurückgesetzt", deleteSuccess: "{{label}} erfolgreich gelöscht", deleteFailed: "{{label}} konnte nicht gelöscht werden", noRecordId: "Keine Datensatz-ID angegeben", @@ -2009,6 +2320,7 @@ const de = { }, }, organizations: { + create: "Workspace erstellen", mine: "Meine Organisationen", title: "Organisationen", heading: "Ihre Organisationen", @@ -2022,6 +2334,11 @@ const de = { noMatches: "Keine Organisationen entsprechen Ihrer Suche.", }, notifications: { + regionLabel: "Benachrichtigungen", + groupCount: "{{count}} Benachrichtigungen", + groupUnread: "{{count}} ungelesen", + groupMarkRead: "Als gelesen markieren", + groupMarkReadTitle: "Alle dieses Typs als gelesen markieren", empty: "Keine Benachrichtigungen", markAllRead: "Alle als gelesen markieren", viewAll: "Alle Benachrichtigungen anzeigen", @@ -2224,6 +2541,15 @@ const de = { }, }, marketplace: { + suggestedBindings: { + promptEveryone: "Diese App empfiehlt, „{{set}}“ allen angemeldeten Benutzern zu gewähren (Position „Everyone“).", + promptGuest: "Diese App empfiehlt, „{{set}}“ nicht authentifizierten Besuchern zu gewähren (Position „Guest“).", + confirm: "Gewähren", + confirming: "Wird gewährt…", + dismiss: "Ausblenden", + confirmedToast: "„{{set}}“ ist jetzt {{anchor}} gewährt.", + dismissedToast: "Vorschlag für „{{set}}“ ausgeblendet.", + }, title: "App-Marktplatz", subtitle: "Durchsuchen Sie genehmigte Apps, die im ObjectStack-Katalog veröffentlicht wurden. Klicken Sie auf eine App, um Details zu sehen und sie in eine Ihrer Umgebungen zu installieren.", searchPlaceholder: "Apps nach Name oder Manifest-ID suchen…", @@ -2275,6 +2601,7 @@ const de = { updateAvailable: 'Update verfügbar', }, action: { + installed: "Installiert", install: "Installieren", reinstall: "Neu installieren", working: "Verarbeite…", @@ -2526,6 +2853,10 @@ const de = { retry: 'Erneut versuchen', }, draftBar: { + messageClean: "Entwurfsvorschau — keine unveröffentlichten Änderungen; alles hier ist bereits live.", + sampleDataTitle: "Beispieldaten erscheinen nach dem Veröffentlichen", + sampleDataBody: "Sie sehen eine Vorschau der Struktur Ihrer App. Veröffentlichen Sie sie, um Beispieldatensätze zu laden und sie live zu schalten.", + publishCta: "Veröffentlichen und live ansehen", message: 'Entwurfsvorschau — Sie sehen unveröffentlichte Änderungen. Nichts ist live, bis Sie veröffentlichen.', publish: 'Veröffentlichen', publishing: 'Wird veröffentlicht…', diff --git a/packages/i18n/src/locales/es.ts b/packages/i18n/src/locales/es.ts index f13bfc5e1b..92596b3f68 100644 --- a/packages/i18n/src/locales/es.ts +++ b/packages/i18n/src/locales/es.ts @@ -1,6 +1,15 @@ const es = { // objectui#2600 B5 — capability picker scope group headers (labels come from the sys_capability registry). capability: { + label: { + manage_users: "Gestionar usuarios", + manage_org_users: "Gestionar usuarios de la organización", + manage_metadata: "Gestionar metadatos", + manage_platform_settings: "Gestionar la configuración de la plataforma", + setup_access: "Acceso a la configuración", + setup_write: "Escritura de la configuración", + studio_access: "Acceso a Studio", + }, group: { platform: 'Plataforma', org: 'Organización', @@ -98,6 +107,10 @@ const es = { type: "{{field}} debe ser un {{type}} válido", }, form: { + discardTitle: "¿Descartar los cambios?", + discardMessage: "Tiene cambios sin guardar. Si cierra este formulario ahora, sus ediciones se perderán.", + keepEditing: "Seguir editando", + discard: "Descartar", addItem: "Añadir elemento", removeItem: "Eliminar elemento", fieldRequired: "Este campo es obligatorio", @@ -118,6 +131,19 @@ const es = { deleteSuccess: "{{object}} eliminado", }, fields: { + image: { + upload: "Subir imagen", + addMore: "Añadir más imágenes", + uploading: "Subiendo…", + imageAlt: "Imagen {{index}}", + crop: "Recortar la imagen {{index}}", + remove: "Eliminar la imagen {{index}}", + enlarge: "Ampliar {{name}}", + preview: "Vista previa de la imagen", + previous: "Imagen anterior", + next: "Imagen siguiente", + counter: "{{current}} / {{total}}", + }, relativeDate: { overdue: "Atrasado {{count}} d", }, @@ -176,6 +202,36 @@ const es = { delete: "Eliminar", }, grid: { + bulkSelected: "{{count}} seleccionados", + bulkSelectedAllMatches: "{{count}} seleccionados (todas las coincidencias)", + bulkClear: "Quitar selección", + bulkAllOnPage: "Están seleccionados los {{count}} de esta página.", + bulkSelectAllMatching: "Seleccionar las {{count}} coincidencias", + bulkAllMatchingSelected: "Están seleccionados los {{count}} registros coincidentes.", + bulk: { + confirmDefault: "Esto se aplicará a {{count}} registro(s).", + overLimit: "La selección ({{count}}) supera el límite de la acción ({{limit}}). Reduzca la selección para continuar.", + affectedRecords: "Registros afectados ({{count}}):", + rowFallback: "Fila {{index}}", + andMore: "… y {{count}} más", + processed: "{{count}} / {{total}} procesados", + processedFailed: " · {{count}} con error", + undonePrefix: "Deshecho: ", + succeeded: "Correctos {{count}} / {{total}}", + resultFailed: " · Fallidos {{count}}", + retry: "Reintentar", + downloadErrorCsv: "Descargar CSV de errores", + cancel: "Cancelar", + back: "Atrás", + next: "Siguiente", + run: "Ejecutar", + running: "Ejecutando…", + undo: "Deshacer", + undoing: "Deshaciendo…", + done: "Hecho", + selectPlaceholder: "Seleccionar…", + loading: "Cargando…", + }, actions: "Acciones", edit: "Editar", delete: "Eliminar", @@ -201,6 +257,85 @@ const es = { densityCycleShortHint: "Clic para cambiar", }, import: { + downloadTemplate: "Descargar plantilla", + downloadTemplateHint: "Obtenga un CSV con las columnas correctas (los campos obligatorios llevan *).", + templateFileName: "{{object}}-import-template", + autoMatched: "Emparejado automáticamente", + autoMatchedSummary: "{{count}} columna(s) emparejada(s) automáticamente: revise y ajuste abajo.", + confidence: { + high: "Confianza alta", + medium: "Confianza media", + low: "Confianza baja", + }, + asyncQueued: "En cola: preparando la importación…", + asyncProcessing: "Importando {{processed}} de {{total}} filas… {{progress}} %", + asyncLargeHint: "Este archivo es grande, así que se importará en segundo plano.", + largeSampleNotice: "Vista previa de las primeras {{shown}} de {{total}} filas.", + cancelImport: "Cancelar la importación", + importCancelled: "Importación cancelada", + resultsTruncated: "Mostrando los primeros {{count}} resultados de filas (de {{total}}).", + createdCount: "{{count}} creados", + updatedCount: "{{count}} actualizados", + downloadFailed: "Descargar las filas con error", + options: "Opciones de importación", + writeMode: "Cuando una fila coincide con un registro existente", + writeModeOpt: { + insert: "Crear siempre uno nuevo", + update: "Actualizar el existente (omitir si no hay coincidencia)", + upsert: "Actualizar si coincide; si no, crear", + }, + matchFields: "Coincidir por", + matchFieldsPlaceholder: "Elija el campo o campos de coincidencia…", + matchFieldsHint: "Las filas se emparejan con registros existentes por estos campos.", + needMatchFields: "Seleccione al menos un campo por el que coincidir.", + optCreateOptions: "Conservar los valores de opción desconocidos", + optRunAutomations: "Ejecutar automatizaciones y disparadores", + optTreatHistorical: "Importar como datos históricos", + optTreatHistoricalHint: "(importa los registros completados tal cual: omite las comprobaciones de la máquina de estados y conserva sus marcas de tiempo y autor originales en lugar de sellarlos ahora)", + optSkipBlankKey: "Omitir las filas con valor de coincidencia vacío", + optBackground: "Importar en segundo plano", + optBackgroundHint: "(se ejecuta como un trabajo reversible)", + validate: "Validar los datos", + validating: "Validando…", + validateHint: "Comprobar cada fila en el servidor antes de importar.", + validatePassed: "Las {{ok}} filas son válidas.", + validateFailed: "{{ok}} válidas, {{errors}} con errores.", + errorRowPrefix: "Fila {{row}}: ", + referenceNotFound: "No hay ningún registro que coincida con «{{value}}»", + referenceAmbiguous: "«{{value}}» coincide con más de un registro: use un valor único o el id del registro", + invalidBoolean: "«{{value}}» no es un valor verdadero/falso válido", + invalidNumber: "«{{value}}» no es un número válido", + invalidDate: "«{{value}}» no es una fecha válida", + invalidOption: "«{{value}}» no es una de las opciones permitidas", + requiredValue: "Este campo es obligatorio", + matchAmbiguous: "Coincide con más de un registro existente: use un valor único o el id del registro", + history: "Historial", + historyBack: "Volver a la importación", + historyDescription: "Importaciones recientes de este objeto.", + historyHint: "Trabajos de importación en segundo plano, los más recientes primero.", + historyRefresh: "Actualizar", + historyLoading: "Cargando…", + historyEmpty: "Todavía no hay importaciones.", + historyUnsupported: "El historial de importaciones no está disponible para esta fuente de datos.", + historyColStatus: "Estado", + historyColRows: "Filas", + historyColResult: "Resultado", + historyColTime: "Cuándo", + errorCount: "{{count}} errores", + undoImport: "Deshacer la importación", + undoing: "Deshaciendo…", + undoConfirm: "¿Deshacer esta importación? Los registros que creó se eliminarán y los que actualizó se restaurarán a sus valores anteriores.", + reverted: "Deshecha", + jobStatus: { + pending: "Pendiente", + running: "En ejecución", + succeeded: "Correcta", + failed: "Fallida", + cancelled: "Cancelada", + }, + legacyReferenceBlocked: "Importación bloqueada: {{fields}} son campos de relación que necesitan la ruta de importación del servidor para convertir nombres en identificadores de registro, y esta conexión no la admite. Importarlos como texto plano dañaría los datos. Actualice el backend/cliente, o desasigne estas columnas e impórtelas por separado.", + missingRequiredHint: "No se puede continuar: campos obligatorios sin asignar: {{fields}}. Añada una columna correspondiente a su archivo, o vuelva atrás y suba uno que la incluya.", + legacyFallbackNotice: "Importado mediante un modo de compatibilidad: esta conexión no admite la ruta de importación del servidor, por lo que los valores se guardaron como texto sin conversión de tipos en el servidor. Actualice el backend/cliente para disponer de la importación completa (conversión de tipos y búsqueda de relaciones).", title: "Importar {{object}}", notAllowed: "Este objeto no está habilitado para la importación.", stepUpload: "Cargar", @@ -278,6 +413,36 @@ const es = { moreEvents: "+{{count}} más", }, list: { + firstRunTitle: "Aquí todavía no hay nada", + firstRunMessage: "Cree su primer registro para empezar.", + noMatches: "No hay registros coincidentes", + noMatchesMessage: "Ningún registro coincide con sus filtros o su búsqueda actuales. Pruebe a ajustarlos o borrarlos.", + loadErrorTitle: "No se pudieron cargar los registros", + loadErrorMessage: "Algo salió mal al cargar estos datos. Compruebe su conexión y vuelva a intentarlo.", + retry: "Reintentar", + managedBy: { + system: { + title: "Aquí todavía no hay nada", + message: "Las entradas aparecen automáticamente cuando se ejecuta su acción de origen (por ejemplo, Enviar para aprobación, Compartir, Invitar). Ejecute una de ellas en un registro de origen para crear una fila.", + }, + appendOnly: { + title: "No se han registrado eventos", + message: "Este es un registro de auditoría inmutable. Las filas las escribe la plataforma cuando ocurren eventos: puede exportar el historial, pero no crear entradas desde aquí.", + }, + betterAuth: { + title: "No hay registros de identidad", + message: "Estos registros los crea el proveedor de autenticación —mediante el inicio de sesión, el aprovisionamiento y los flujos de seguridad— y no se añaden a mano aquí.", + }, + betterAuthUser: { + title: "Todavía no hay usuarios", + message: "Las cuentas de usuario las aprovisiona el proveedor de autenticación; no se crean aquí. Invite a sus compañeros a la organización y aparecerán automáticamente en su primer inicio de sesión (aprovisionamiento SSO en el momento). Los usuarios finales de la aplicación llegan cuando se registran a través de ella.", + }, + betterAuthTeam: { + title: "Todavía no hay equipos", + message: "Los equipos agrupan a los miembros dentro de una organización. Cree uno con «Crear equipo», o llegarán a través de los flujos de aprovisionamiento de organización y SSO de su proveedor de autenticación.", + }, + }, + refresh: "Actualizar", loading: 'Loading records...', recordCount: "{{count}} registros", recordCountOne: "{{count}} registro", @@ -318,6 +483,11 @@ const es = { recordEditingTitle: "Edición de registros", }, managedByBadge: { + systemWritable: { + short: "Esquema de la plataforma", + title: "Definido por la plataforma, editable por el administrador", + body: "El esquema de este objeto lo define la plataforma, pero sus filas son suyas: puede crearlas y editarlas aquí. Quién puede escribir lo determinan la administración delegada y la seguridad a nivel de registro, no esta insignia.", + }, config: { short: "Configuración", title: "Configuración del administrador", @@ -340,6 +510,7 @@ const es = { }, }, kanban: { + uncategorized: "Sin categoría", addCard: "Añadir tarjeta", addColumn: "Añadir columna", moveCard: "Mover tarjeta", @@ -361,12 +532,92 @@ const es = { }, }, gantt: { + viewMode: { + day: "Día", + week: "Semana", + month: "Mes", + quarter: "Trimestre", + year: "Año", + }, + row: { + expand: "Expandir", + collapse: "Contraer", + open: "Abrir los detalles", + }, + aria: { + taskList: "Lista de tareas", + }, + tooltip: { + days: "d", + }, + menu: { + view: "Ver los detalles", + edit: "Editar en línea", + delete: "Eliminar", + addPredecessor: "Añadir predecesora…", + addSuccessor: "Añadir sucesora…", + removeDependency: "Quitar la dependencia", + noCandidates: "No hay tareas disponibles", + searchTasks: "Buscar tareas…", + }, + delete: { + title: "¿Eliminar esta tarea?", + body: "«{{title}}» se eliminará permanentemente. Esta acción no se puede deshacer.", + cancel: "Cancelar", + confirm: "Eliminar", + deleting: "Eliminando…", + }, + drawer: { + fallbackTitle: "Detalles de la tarea", + }, + linkType: { + fs: "Fin → Inicio", + ss: "Inicio → Inicio", + ff: "Fin → Fin", + sf: "Inicio → Fin", + }, + conflict: { + title: "Conflicto de programación", + body: "Este movimiento entra en conflicto con las restricciones de dependencia. ¿Reprogramar automáticamente {{count}} tarea(s) afectada(s)?", + confirm: "Reprogramar automáticamente", + cancel: "Dejarlo como está", + }, + autoScheduleDlg: { + title: "Programación automática", + body: "¿Retrasar {count} tarea(s) para respetar los vínculos de dependencia?", + skipped: "{count} tarea(s) bloqueada(s) también incumplen los vínculos y se han omitido.", + confirm: "Aplicar", + cancel: "Cancelar", + none: "Todas las dependencias se cumplen: no hay nada que reprogramar.", + }, + resource: { + header: "Recurso", + peak: "Pico", + over: "sobrecargado", + empty: "No hay tareas que asignar.", + }, + readOnly: "Solo lectura", + readOnlyHint: "La edición está deshabilitada en esta vista.", + lockedHint: "Sin permiso de edición", + writeFailed: "Error al guardar: el cambio se ha revertido", column: { taskName: "Nombre de tarea", start: "Inicio", end: "Fin", }, toolbar: { + thisWeek: "Esta semana", + thisMonth: "Este mes", + viewMode: "Granularidad de la línea de tiempo", + enterFullscreen: "Pantalla completa", + exitFullscreen: "Salir de pantalla completa", + criticalPath: "Resaltar la ruta crítica", + autoSchedule: "Programar automáticamente las dependencias", + exportPng: "Exportar como PNG", + exportPdf: "Exportar PDF", + saveLayout: "Guardar la disposición", + undo: "Deshacer", + redo: "Rehacer", prevPeriod: "Período anterior", nextPeriod: "Período siguiente", zoomIn: "Acercar", @@ -412,6 +663,9 @@ const es = { redo: 'Redo', }, detail: { + editInlineHint: "Haga doble clic para editar", + systemActor: "Sistema", + unknownUser: "Desconocido", back: "Volver", edit: "Editar", editInline: "Editar", @@ -626,6 +880,35 @@ const es = { resetZoom: "Restablecer zoom", }, dashboard: { + noRows: "Sin filas", + pickMeasures: "Elija medidas (valores) para este widget de dataset.", + datasetUnsupported: "Esta fuente de datos no admite consultas de dataset.", + details: "Detalles", + exportCsv: "Exportar CSV", + openInList: "Abrir en la lista", + filters: { + label: "Filtros del panel", + dateRange: "Intervalo de fechas", + allTime: "Todo el periodo", + custom: "Personalizado…", + all: "Todo", + reset: "Restablecer", + range: { + today: "Hoy", + yesterday: "Ayer", + this_week: "Esta semana", + last_week: "La semana pasada", + this_month: "Este mes", + last_month: "El mes pasado", + this_quarter: "Este trimestre", + last_quarter: "El trimestre pasado", + this_year: "Este año", + last_year: "El año pasado", + last_7_days: "Últimos 7 días", + last_30_days: "Últimos 30 días", + last_90_days: "Últimos 90 días", + }, + }, addWidget: "Añadir widget", removeWidget: "Eliminar widget", editLayout: "Editar diseño", @@ -1343,7 +1626,28 @@ const es = { }, }, auth: { + setPassword: { + title: "Establecer una contraseña de recuperación", + description: "Ha iniciado sesión mediante inicio de sesión único. Establezca una contraseña local para poder seguir accediendo directamente a este entorno si el SSO deja de estar disponible.", + email: "Correo electrónico", + newPassword: "Nueva contraseña", + confirmPassword: "Confirmar la contraseña", + submit: "Establecer la contraseña", + submitting: "Guardando…", + success: "Contraseña local establecida", + failed: "No se pudo establecer la contraseña", + passwordsMismatch: "Las contraseñas no coinciden", + noSession: "Su sesión ha caducado.", + backToSignIn: "Volver a iniciar sesión", + }, login: { + signingIn: "Iniciando sesión…", + ssoHandoff: "Continuar a {{target}}", + devAdminHint: { + title: "Instancia de desarrollo", + body: "Inicie sesión con el administrador de desarrollo predefinido:", + dismiss: "Descartar", + }, title: "Inicia sesión en tu cuenta", description: "Ingresa tu correo electrónico y contraseña para continuar", emailLabel: "Correo electrónico", @@ -1356,6 +1660,7 @@ const es = { noAccountText: "¿No tienes una cuenta?", signUpText: "Regístrate", errors: { + oauthCallbackFailed: "No se pudo completar el inicio de sesión único: el enlace de acceso caducó o ya se había utilizado. Vuelva a intentarlo.", invalidCredentials: "Correo o contraseña inválidos. Inténtelo de nuevo.", emailNotVerified: "Verifique su correo electrónico antes de iniciar sesión.", }, @@ -1580,11 +1885,15 @@ const es = { settings: "Configuración del espacio de trabajo", }, help: { + onThisPage: "En esta página", + keyboardShortcuts: "Atajos de teclado", appDocs: 'Documentación de esta app', allDocs: 'Toda la documentación', onlineDocs: 'Documentación en línea', }, sidebar: { + packageManagement: "Gestión de paquetes", + searchNavigation: "Buscar en la navegación...", settings: "Configuración", help: "Ayuda", helpTooltip: "Ayuda y documentación", @@ -1845,6 +2154,8 @@ const es = { updateFailed: "No se pudo actualizar la navegación", }, objectActions: { + resetPackageSetConfirm: "Este conjunto de permisos viene con un paquete instalado y no se puede eliminar. Al eliminarlo se restablece a la versión original del paquete y se descartan sus personalizaciones en este entorno. ¿Continuar?", + resetPackageSetSuccess: "Conjunto de permisos restablecido a la versión original del paquete", deleteSuccess: "{{label}} eliminado correctamente", deleteFailed: "No se pudo eliminar {{label}}", noRecordId: "No se proporcionó ID de registro", @@ -2009,6 +2320,7 @@ const es = { }, }, organizations: { + create: "Crear espacio de trabajo", mine: "Mis organizaciones", title: "Organizaciones", heading: "Tus organizaciones", @@ -2022,6 +2334,11 @@ const es = { noMatches: "Ninguna organización coincide con tu búsqueda.", }, notifications: { + regionLabel: "Notificaciones", + groupCount: "{{count}} notificaciones", + groupUnread: "{{count}} sin leer", + groupMarkRead: "Marcar como leídas", + groupMarkReadTitle: "Marcar como leídas todas las de este tipo", empty: "Sin notificaciones", markAllRead: "Marcar todo como leído", viewAll: "Ver todas las notificaciones", @@ -2224,6 +2541,15 @@ const es = { }, }, marketplace: { + suggestedBindings: { + promptEveryone: "Esta aplicación sugiere conceder «{{set}}» a todos los usuarios con sesión iniciada (la posición Everyone).", + promptGuest: "Esta aplicación sugiere conceder «{{set}}» a los visitantes no autenticados (la posición Guest).", + confirm: "Conceder", + confirming: "Concediendo…", + dismiss: "Descartar", + confirmedToast: "«{{set}}» ya está concedido a {{anchor}}.", + dismissedToast: "Sugerencia para «{{set}}» descartada.", + }, title: "Marketplace de aplicaciones", subtitle: "Explore aplicaciones aprobadas publicadas en el catálogo de ObjectStack. Haga clic en una aplicación para ver detalles e instalarla en uno de sus entornos.", searchPlaceholder: "Buscar aplicaciones por nombre o ID de manifiesto…", @@ -2275,6 +2601,7 @@ const es = { updateAvailable: 'Actualización disponible', }, action: { + installed: "Instalada", install: "Instalar", reinstall: "Reinstalar", working: "Procesando…", @@ -2526,6 +2853,10 @@ const es = { retry: 'Reintentar', }, draftBar: { + messageClean: "Vista previa del borrador: no hay cambios sin publicar; todo lo que ve aquí ya está activo.", + sampleDataTitle: "Los datos de ejemplo aparecen tras publicar", + sampleDataBody: "Está viendo la estructura de su aplicación. Publíquela para cargar registros de ejemplo y activarla.", + publishCta: "Publicar para verlo activo", message: 'Vista previa de borrador — estás viendo cambios sin publicar. Nada está en producción hasta que publiques.', publish: 'Publicar', publishing: 'Publicando…', diff --git a/packages/i18n/src/locales/fr.ts b/packages/i18n/src/locales/fr.ts index f756233b83..09b6d661b8 100644 --- a/packages/i18n/src/locales/fr.ts +++ b/packages/i18n/src/locales/fr.ts @@ -1,6 +1,15 @@ const fr = { // objectui#2600 B5 — capability picker scope group headers (labels come from the sys_capability registry). capability: { + label: { + manage_users: "Gérer les utilisateurs", + manage_org_users: "Gérer les utilisateurs de l'organisation", + manage_metadata: "Gérer les métadonnées", + manage_platform_settings: "Gérer les paramètres de la plateforme", + setup_access: "Accès à la configuration", + setup_write: "Écriture des paramètres", + studio_access: "Accès à Studio", + }, group: { platform: 'Plateforme', org: 'Organisation', @@ -98,6 +107,10 @@ const fr = { type: "{{field}} doit être un {{type}} valide", }, form: { + discardTitle: "Abandonner les modifications ?", + discardMessage: "Vous avez des modifications non enregistrées. Si vous fermez ce formulaire maintenant, vos modifications seront perdues.", + keepEditing: "Continuer l'édition", + discard: "Abandonner", addItem: "Ajouter un élément", removeItem: "Supprimer un élément", fieldRequired: "Ce champ est obligatoire", @@ -118,6 +131,19 @@ const fr = { deleteSuccess: "{{object}} supprimé", }, fields: { + image: { + upload: "Téléverser une image", + addMore: "Ajouter d'autres images", + uploading: "Téléversement…", + imageAlt: "Image {{index}}", + crop: "Recadrer l'image {{index}}", + remove: "Supprimer l'image {{index}}", + enlarge: "Agrandir {{name}}", + preview: "Aperçu de l'image", + previous: "Image précédente", + next: "Image suivante", + counter: "{{current}} / {{total}}", + }, relativeDate: { overdue: "En retard de {{count}} j", }, @@ -176,6 +202,36 @@ const fr = { delete: "Supprimer", }, grid: { + bulkSelected: "{{count}} sélectionné(s)", + bulkSelectedAllMatches: "{{count}} sélectionné(s) (tous les résultats)", + bulkClear: "Effacer", + bulkAllOnPage: "Les {{count}} éléments de cette page sont sélectionnés.", + bulkSelectAllMatching: "Sélectionner les {{count}} correspondances", + bulkAllMatchingSelected: "Les {{count}} enregistrements correspondants sont tous sélectionnés.", + bulk: { + confirmDefault: "Ceci s'appliquera à {{count}} enregistrement(s).", + overLimit: "La sélection ({{count}}) dépasse la limite de l'action ({{limit}}). Réduisez la sélection pour continuer.", + affectedRecords: "Enregistrements concernés ({{count}}) :", + rowFallback: "Ligne {{index}}", + andMore: "… et {{count}} de plus", + processed: "{{count}} / {{total}} traités", + processedFailed: " · {{count}} en échec", + undonePrefix: "Annulé — ", + succeeded: "Réussis {{count}} / {{total}}", + resultFailed: " · Échecs {{count}}", + retry: "Réessayer", + downloadErrorCsv: "Télécharger le CSV d'erreurs", + cancel: "Annuler", + back: "Retour", + next: "Suivant", + run: "Exécuter", + running: "Exécution…", + undo: "Annuler", + undoing: "Annulation…", + done: "Terminé", + selectPlaceholder: "Sélectionner…", + loading: "Chargement…", + }, actions: "Actions", edit: "Modifier", delete: "Supprimer", @@ -201,6 +257,85 @@ const fr = { densityCycleShortHint: "Cliquer pour changer", }, import: { + downloadTemplate: "Télécharger le modèle", + downloadTemplateHint: "Obtenez un CSV avec les bonnes colonnes (les champs obligatoires sont marqués d'un *).", + templateFileName: "{{object}}-import-template", + autoMatched: "Associé automatiquement", + autoMatchedSummary: "{{count}} colonne(s) associée(s) automatiquement — vérifiez et ajustez ci-dessous.", + confidence: { + high: "Confiance élevée", + medium: "Confiance moyenne", + low: "Confiance faible", + }, + asyncQueued: "En file d'attente — préparation de l'import…", + asyncProcessing: "Import de {{processed}} lignes sur {{total}}… {{progress}} %", + asyncLargeHint: "Ce fichier est volumineux, il sera donc importé en arrière-plan.", + largeSampleNotice: "Aperçu des {{shown}} premières lignes sur {{total}}.", + cancelImport: "Annuler l'import", + importCancelled: "Import annulé", + resultsTruncated: "Affichage des {{count}} premiers résultats de lignes (sur {{total}}).", + createdCount: "{{count}} créé(s)", + updatedCount: "{{count}} mis à jour", + downloadFailed: "Télécharger les lignes en échec", + options: "Options d'import", + writeMode: "Lorsqu'une ligne correspond à un enregistrement existant", + writeModeOpt: { + insert: "Toujours créer", + update: "Mettre à jour l'existant (ignorer sans correspondance)", + upsert: "Mettre à jour si correspondance, sinon créer", + }, + matchFields: "Correspondance sur", + matchFieldsPlaceholder: "Choisir le ou les champs de correspondance…", + matchFieldsHint: "Les lignes sont rapprochées des enregistrements existants via ces champs.", + needMatchFields: "Sélectionnez au moins un champ de correspondance.", + optCreateOptions: "Conserver les valeurs d'option inconnues", + optRunAutomations: "Exécuter les automatisations et déclencheurs", + optTreatHistorical: "Importer comme données historiques", + optTreatHistoricalHint: "(importer les enregistrements terminés tels quels — ignorer les contrôles de la machine à états et conserver leurs horodatages et auteurs d'origine au lieu d'horodater maintenant)", + optSkipBlankKey: "Ignorer les lignes dont la valeur de correspondance est vide", + optBackground: "Importer en arrière-plan", + optBackgroundHint: "(s'exécute comme une tâche annulable)", + validate: "Valider les données", + validating: "Validation…", + validateHint: "Vérifier chaque ligne auprès du serveur avant l'import.", + validatePassed: "Les {{ok}} lignes sont toutes valides.", + validateFailed: "{{ok}} valides, {{errors}} en erreur.", + errorRowPrefix: "Ligne {{row}} : ", + referenceNotFound: "Aucun enregistrement correspondant à « {{value}} »", + referenceAmbiguous: "« {{value}} » correspond à plusieurs enregistrements — utilisez une valeur unique ou l'identifiant de l'enregistrement", + invalidBoolean: "« {{value}} » n'est pas une valeur vrai/faux valide", + invalidNumber: "« {{value}} » n'est pas un nombre valide", + invalidDate: "« {{value}} » n'est pas une date valide", + invalidOption: "« {{value}} » ne fait pas partie des options autorisées", + requiredValue: "Ce champ est obligatoire", + matchAmbiguous: "Correspond à plusieurs enregistrements existants — utilisez une valeur unique ou l'identifiant de l'enregistrement", + history: "Historique", + historyBack: "Retour à l'import", + historyDescription: "Imports récents pour cet objet.", + historyHint: "Tâches d'import en arrière-plan, les plus récentes d'abord.", + historyRefresh: "Actualiser", + historyLoading: "Chargement…", + historyEmpty: "Aucun import pour l'instant.", + historyUnsupported: "L'historique des imports n'est pas disponible pour cette source de données.", + historyColStatus: "Statut", + historyColRows: "Lignes", + historyColResult: "Résultat", + historyColTime: "Quand", + errorCount: "{{count}} erreurs", + undoImport: "Annuler l'import", + undoing: "Annulation…", + undoConfirm: "Annuler cet import ? Les enregistrements créés seront supprimés et les enregistrements mis à jour seront rétablis à leurs valeurs précédentes.", + reverted: "Annulé", + jobStatus: { + pending: "En attente", + running: "En cours", + succeeded: "Réussi", + failed: "Échec", + cancelled: "Annulé", + }, + legacyReferenceBlocked: "Import bloqué : {{fields}} sont des champs de relation qui nécessitent la route d'import côté serveur pour convertir les noms en identifiants d'enregistrement, et cette connexion ne la prend pas en charge. Les importer en texte brut corromprait les données. Mettez à jour le backend/client, ou retirez l'association de ces colonnes et importez-les séparément.", + missingRequiredHint: "Impossible de continuer — champs obligatoires non associés : {{fields}}. Ajoutez une colonne correspondante à votre fichier, ou revenez en arrière et téléversez-en un qui la contient.", + legacyFallbackNotice: "Importé via un mode de compatibilité : cette connexion ne prend pas en charge la route d'import côté serveur, les valeurs ont donc été enregistrées comme texte sans conversion de type côté serveur. Mettez à jour le backend/client pour un import complet (conversion de type et résolution des relations).", title: "Importer {{object}}", notAllowed: "Cet objet n'est pas ouvert à l'import.", stepUpload: "Télécharger", @@ -278,6 +413,36 @@ const fr = { moreEvents: "+{{count}} de plus", }, list: { + firstRunTitle: "Rien pour l'instant", + firstRunMessage: "Créez votre premier enregistrement pour commencer.", + noMatches: "Aucun enregistrement correspondant", + noMatchesMessage: "Aucun enregistrement ne correspond à vos filtres ou à votre recherche. Essayez de les ajuster ou de les effacer.", + loadErrorTitle: "Impossible de charger les enregistrements", + loadErrorMessage: "Une erreur s'est produite lors du chargement de ces données. Vérifiez votre connexion et réessayez.", + retry: "Réessayer", + managedBy: { + system: { + title: "Rien pour l'instant", + message: "Les entrées apparaissent automatiquement lorsque leur action source s'exécute (par ex. Soumettre pour approbation, Partager, Inviter). Déclenchez l'une d'elles sur un enregistrement source pour créer une ligne.", + }, + appendOnly: { + title: "Aucun événement enregistré", + message: "Ceci est un journal d'audit immuable. Les lignes sont écrites par la plateforme lorsque des événements surviennent — vous pouvez exporter l'historique mais pas créer d'entrées ici.", + }, + betterAuth: { + title: "Aucun enregistrement d'identité", + message: "Ces enregistrements sont créés par le fournisseur d'authentification — via la connexion, le provisionnement et les flux de sécurité — et non ajoutés à la main ici.", + }, + betterAuthUser: { + title: "Aucun utilisateur pour l'instant", + message: "Les comptes utilisateur sont provisionnés par le fournisseur d'authentification et ne sont pas créés ici. Invitez vos collègues dans votre organisation : ils apparaissent automatiquement à leur première connexion (provisionnement SSO à la volée). Les utilisateurs finaux arrivent lorsqu'ils s'inscrivent via votre application.", + }, + betterAuthTeam: { + title: "Aucune équipe pour l'instant", + message: "Les équipes regroupent les membres au sein d'une organisation. Créez-en une avec « Créer une équipe », ou elles arrivent via les flux de provisionnement d'organisation et SSO de votre fournisseur d'authentification.", + }, + }, + refresh: "Actualiser", loading: 'Loading records...', recordCount: "{{count}} enregistrements", recordCountOne: "{{count}} enregistrement", @@ -318,6 +483,11 @@ const fr = { recordEditingTitle: "Modification des enregistrements", }, managedByBadge: { + systemWritable: { + short: "Schéma de la plateforme", + title: "Défini par la plateforme, modifiable par l'administrateur", + body: "Le schéma de cet objet est défini par la plateforme, mais ses lignes vous appartiennent : vous pouvez les créer et les modifier ici. Qui peut écrire est déterminé par l'administration déléguée et la sécurité au niveau des enregistrements, pas par ce badge.", + }, config: { short: "Config admin", title: "Configuration administrateur", @@ -340,6 +510,7 @@ const fr = { }, }, kanban: { + uncategorized: "Non catégorisé", addCard: "Ajouter une carte", addColumn: "Ajouter une colonne", moveCard: "Déplacer la carte", @@ -361,12 +532,92 @@ const fr = { }, }, gantt: { + viewMode: { + day: "Jour", + week: "Semaine", + month: "Mois", + quarter: "Trimestre", + year: "Année", + }, + row: { + expand: "Développer", + collapse: "Réduire", + open: "Ouvrir les détails", + }, + aria: { + taskList: "Liste des tâches", + }, + tooltip: { + days: "j", + }, + menu: { + view: "Afficher les détails", + edit: "Modifier en ligne", + delete: "Supprimer", + addPredecessor: "Ajouter un prédécesseur…", + addSuccessor: "Ajouter un successeur…", + removeDependency: "Supprimer la dépendance", + noCandidates: "Aucune tâche disponible", + searchTasks: "Rechercher des tâches…", + }, + delete: { + title: "Supprimer cette tâche ?", + body: "« {{title}} » sera définitivement supprimée. Cette action est irréversible.", + cancel: "Annuler", + confirm: "Supprimer", + deleting: "Suppression…", + }, + drawer: { + fallbackTitle: "Détails de la tâche", + }, + linkType: { + fs: "Fin → Début", + ss: "Début → Début", + ff: "Fin → Fin", + sf: "Début → Fin", + }, + conflict: { + title: "Conflit de planning", + body: "Ce déplacement entre en conflit avec les contraintes de dépendance. Replanifier automatiquement {{count}} tâche(s) concernée(s) ?", + confirm: "Replanifier automatiquement", + cancel: "Laisser tel quel", + }, + autoScheduleDlg: { + title: "Planification automatique", + body: "Décaler {count} tâche(s) plus tard pour respecter les liens de dépendance ?", + skipped: "{count} tâche(s) verrouillée(s) violent aussi les liens et ont été ignorées.", + confirm: "Appliquer", + cancel: "Annuler", + none: "Toutes les dépendances sont respectées — rien à replanifier.", + }, + resource: { + header: "Ressource", + peak: "Pic", + over: "surchargée", + empty: "Aucune tâche à affecter.", + }, + readOnly: "Lecture seule", + readOnlyHint: "L'édition est désactivée pour cette vue.", + lockedHint: "Aucune autorisation de modification", + writeFailed: "Échec de l'enregistrement — la modification a été annulée", column: { taskName: "Nom de la tâche", start: "Début", end: "Fin", }, toolbar: { + thisWeek: "Cette semaine", + thisMonth: "Ce mois-ci", + viewMode: "Granularité de la chronologie", + enterFullscreen: "Passer en plein écran", + exitFullscreen: "Quitter le plein écran", + criticalPath: "Mettre en évidence le chemin critique", + autoSchedule: "Planifier automatiquement les dépendances", + exportPng: "Exporter en PNG", + exportPdf: "Exporter en PDF", + saveLayout: "Enregistrer la disposition", + undo: "Annuler", + redo: "Rétablir", prevPeriod: "Période précédente", nextPeriod: "Période suivante", zoomIn: "Zoom avant", @@ -412,6 +663,11 @@ const fr = { redo: 'Redo', }, detail: { + editInlineHint: "Double-cliquez pour modifier", + systemActor: "Système", + unknownUser: "Inconnu", + created: "Créé", + updated: "Mis à jour", back: "Retour", edit: "Modifier", editInline: "Modifier", @@ -624,6 +880,35 @@ const fr = { resetZoom: "Réinitialiser le zoom", }, dashboard: { + noRows: "Aucune ligne", + pickMeasures: "Choisissez des mesures (valeurs) pour ce widget de dataset.", + datasetUnsupported: "Cette source de données ne prend pas en charge les requêtes de dataset.", + details: "Détails", + exportCsv: "Exporter en CSV", + openInList: "Ouvrir dans la liste", + filters: { + label: "Filtres du tableau de bord", + dateRange: "Plage de dates", + allTime: "Depuis toujours", + custom: "Personnalisé…", + all: "Tout", + reset: "Réinitialiser", + range: { + today: "Aujourd'hui", + yesterday: "Hier", + this_week: "Cette semaine", + last_week: "La semaine dernière", + this_month: "Ce mois-ci", + last_month: "Le mois dernier", + this_quarter: "Ce trimestre", + last_quarter: "Le trimestre dernier", + this_year: "Cette année", + last_year: "L'année dernière", + last_7_days: "7 derniers jours", + last_30_days: "30 derniers jours", + last_90_days: "90 derniers jours", + }, + }, addWidget: "Ajouter un widget", removeWidget: "Supprimer un widget", editLayout: "Modifier la mise en page", @@ -1341,7 +1626,28 @@ const fr = { }, }, auth: { + setPassword: { + title: "Définir un mot de passe de secours", + description: "Vous vous êtes connecté via l'authentification unique. Définissez un mot de passe local afin de pouvoir toujours vous connecter directement à cet environnement si le SSO devenait indisponible.", + email: "E-mail", + newPassword: "Nouveau mot de passe", + confirmPassword: "Confirmer le mot de passe", + submit: "Définir le mot de passe", + submitting: "Enregistrement…", + success: "Mot de passe local défini", + failed: "Impossible de définir le mot de passe", + passwordsMismatch: "Les mots de passe ne correspondent pas", + noSession: "Votre session a expiré.", + backToSignIn: "Se reconnecter", + }, login: { + signingIn: "Connexion en cours…", + ssoHandoff: "Continuer vers {{target}}", + devAdminHint: { + title: "Instance de développement", + body: "Connectez-vous avec l'administrateur de développement prédéfini :", + dismiss: "Ignorer", + }, title: "Connectez-vous à votre compte", description: "Entrez votre e-mail et votre mot de passe pour continuer", emailLabel: "E-mail", @@ -1354,6 +1660,7 @@ const fr = { noAccountText: "Vous n'avez pas de compte ?", signUpText: "S'inscrire", errors: { + oauthCallbackFailed: "L'authentification unique n'a pas pu aboutir — le lien de connexion a expiré ou a déjà été utilisé. Veuillez réessayer.", invalidCredentials: "E-mail ou mot de passe invalide. Veuillez réessayer.", emailNotVerified: "Veuillez vérifier votre adresse e-mail avant de vous connecter.", }, @@ -1578,11 +1885,15 @@ const fr = { settings: "Paramètres de l'espace de travail", }, help: { + onThisPage: "Sur cette page", + keyboardShortcuts: "Raccourcis clavier", appDocs: 'Documentation de cette app', allDocs: 'Toute la documentation', onlineDocs: 'Documentation en ligne', }, sidebar: { + packageManagement: "Gestion des packages", + searchNavigation: "Rechercher dans la navigation...", settings: "Paramètres", help: "Aide", helpTooltip: "Aide et documentation", @@ -1843,6 +2154,8 @@ const fr = { updateFailed: "Impossible de mettre à jour la navigation", }, objectActions: { + resetPackageSetConfirm: "Ce jeu d'autorisations est fourni avec un package installé et ne peut pas être supprimé. La suppression le réinitialise à la version livrée et abandonne vos personnalisations dans cet environnement. Continuer ?", + resetPackageSetSuccess: "Jeu d'autorisations réinitialisé à la version livrée", deleteSuccess: "{{label}} supprimé avec succès", deleteFailed: "Impossible de supprimer {{label}}", noRecordId: "Aucun ID d'enregistrement fourni", @@ -2007,6 +2320,7 @@ const fr = { }, }, organizations: { + create: "Créer un espace de travail", mine: "Mes organisations", title: "Organisations", heading: "Vos organisations", @@ -2020,6 +2334,11 @@ const fr = { noMatches: "Aucune organisation ne correspond à votre recherche.", }, notifications: { + regionLabel: "Notifications", + groupCount: "{{count}} notifications", + groupUnread: "{{count}} non lues", + groupMarkRead: "Marquer comme lu", + groupMarkReadTitle: "Tout marquer comme lu pour ce type", empty: "Aucune notification", markAllRead: "Tout marquer comme lu", viewAll: "Voir toutes les notifications", @@ -2222,6 +2541,15 @@ const fr = { }, }, marketplace: { + suggestedBindings: { + promptEveryone: "Cette application suggère d'accorder « {{set}} » à tous les utilisateurs connectés (position Everyone).", + promptGuest: "Cette application suggère d'accorder « {{set}} » aux visiteurs non authentifiés (position Guest).", + confirm: "Accorder", + confirming: "Attribution…", + dismiss: "Ignorer", + confirmedToast: "« {{set}} » est désormais accordé à {{anchor}}.", + dismissedToast: "Suggestion pour « {{set}} » ignorée.", + }, title: "Marketplace d'applications", subtitle: "Parcourez les applications approuvées publiées dans le catalogue ObjectStack. Cliquez sur une application pour voir les détails et l'installer dans l'un de vos environnements.", searchPlaceholder: "Rechercher des applications par nom ou ID de manifeste…", @@ -2273,6 +2601,7 @@ const fr = { updateAvailable: 'Mise à jour disponible', }, action: { + installed: "Installé", install: "Installer", reinstall: "Réinstaller", working: "Traitement…", @@ -2524,6 +2853,10 @@ const fr = { retry: 'Réessayer', }, draftBar: { + messageClean: "Aperçu du brouillon — aucune modification non publiée ; tout ce qui figure ici est déjà en ligne.", + sampleDataTitle: "Les données d'exemple apparaissent après publication", + sampleDataBody: "Vous prévisualisez la structure de votre application. Publiez-la pour charger des enregistrements d'exemple et la mettre en ligne.", + publishCta: "Publier pour voir en ligne", message: "Aperçu du brouillon — vous voyez des modifications non publiées. Rien n'est en ligne avant publication.", publish: 'Publier', publishing: 'Publication…', diff --git a/packages/i18n/src/locales/ja.ts b/packages/i18n/src/locales/ja.ts index 27d17b1f51..91d9a47b19 100644 --- a/packages/i18n/src/locales/ja.ts +++ b/packages/i18n/src/locales/ja.ts @@ -1,6 +1,15 @@ const ja = { // objectui#2600 B5 — capability picker scope group headers (labels come from the sys_capability registry). capability: { + label: { + manage_users: "ユーザー管理", + manage_org_users: "組織ユーザーの管理", + manage_metadata: "メタデータ管理", + manage_platform_settings: "プラットフォーム設定の管理", + setup_access: "設定へのアクセス", + setup_write: "設定の書き込み", + studio_access: "Studio へのアクセス", + }, group: { platform: 'プラットフォーム', org: '組織', @@ -98,6 +107,10 @@ const ja = { type: "{{field}}は有効な{{type}}である必要があります", }, form: { + discardTitle: "変更を破棄しますか?", + discardMessage: "保存されていない変更があります。このままフォームを閉じると編集内容は失われます。", + keepEditing: "編集を続ける", + discard: "破棄", addItem: "項目を追加", removeItem: "項目を削除", fieldRequired: "この項目は必須です", @@ -118,6 +131,19 @@ const ja = { deleteSuccess: "{{object}}が削除されました", }, fields: { + image: { + upload: "画像をアップロード", + addMore: "画像を追加", + uploading: "アップロード中…", + imageAlt: "画像 {{index}}", + crop: "画像 {{index}} を切り抜く", + remove: "画像 {{index}} を削除", + enlarge: "{{name}} を拡大", + preview: "画像プレビュー", + previous: "前の画像", + next: "次の画像", + counter: "{{current}} / {{total}}", + }, relativeDate: { overdue: "期限超過 {{count}}日", }, @@ -176,6 +202,36 @@ const ja = { delete: "削除", }, grid: { + bulkSelected: "{{count}} 件を選択中", + bulkSelectedAllMatches: "{{count}} 件を選択中(該当すべて)", + bulkClear: "選択解除", + bulkAllOnPage: "このページの {{count}} 件がすべて選択されています。", + bulkSelectAllMatching: "該当する {{count}} 件すべてを選択", + bulkAllMatchingSelected: "該当する {{count}} 件のレコードがすべて選択されています。", + bulk: { + confirmDefault: "{{count}} 件のレコードに適用されます。", + overLimit: "選択件数({{count}})がアクションの上限({{limit}})を超えています。選択を減らしてください。", + affectedRecords: "対象レコード({{count}}):", + rowFallback: "{{index}} 行目", + andMore: "… 他 {{count}} 件", + processed: "{{count}} / {{total}} 件を処理", + processedFailed: " · 失敗 {{count}} 件", + undonePrefix: "取り消し済み — ", + succeeded: "成功 {{count}} / {{total}}", + resultFailed: " · 失敗 {{count}}", + retry: "再試行", + downloadErrorCsv: "エラー CSV をダウンロード", + cancel: "キャンセル", + back: "戻る", + next: "次へ", + run: "実行", + running: "実行中…", + undo: "取り消す", + undoing: "取り消し中…", + done: "完了", + selectPlaceholder: "選択…", + loading: "読み込み中…", + }, actions: "アクション", edit: "編集", delete: "削除", @@ -201,6 +257,85 @@ const ja = { densityCycleShortHint: "クリックで切り替え", }, import: { + downloadTemplate: "テンプレートをダウンロード", + downloadTemplateHint: "正しい列を備えた CSV を取得します(必須項目には * が付きます)。", + templateFileName: "{{object}}-import-template", + autoMatched: "自動マッピング済み", + autoMatchedSummary: "{{count}} 列を自動マッピングしました — 下で確認・調整してください。", + confidence: { + high: "確度: 高", + medium: "確度: 中", + low: "確度: 低", + }, + asyncQueued: "待機中 — インポートを準備しています…", + asyncProcessing: "{{total}} 行中 {{processed}} 行をインポート中… {{progress}}%", + asyncLargeHint: "このファイルは大きいため、バックグラウンドでインポートされます。", + largeSampleNotice: "{{total}} 行のうち先頭 {{shown}} 行をプレビューしています。", + cancelImport: "インポートを中止", + importCancelled: "インポートを中止しました", + resultsTruncated: "{{total}} 行のうち先頭 {{count}} 行の結果を表示しています。", + createdCount: "{{count}} 件を作成", + updatedCount: "{{count}} 件を更新", + downloadFailed: "失敗した行をダウンロード", + options: "インポート設定", + writeMode: "既存レコードに一致した行の扱い", + writeModeOpt: { + insert: "常に新規作成", + update: "既存を更新(一致しなければスキップ)", + upsert: "一致すれば更新、なければ作成", + }, + matchFields: "照合キー", + matchFieldsPlaceholder: "照合する項目を選択…", + matchFieldsHint: "これらの項目で既存レコードと照合します。", + needMatchFields: "照合する項目を 1 つ以上選択してください。", + optCreateOptions: "未知の選択肢の値を保持する", + optRunAutomations: "自動化とトリガーを実行する", + optTreatHistorical: "過去データとしてインポートする", + optTreatHistoricalHint: "(完了済みレコードをそのまま取り込みます — ステートマシンの検査を省略し、現在時刻を打刻せずに元のタイムスタンプと作成者を保持します)", + optSkipBlankKey: "照合値が空の行をスキップする", + optBackground: "バックグラウンドでインポートする", + optBackgroundHint: "(取り消し可能なジョブとして実行されます)", + validate: "データを検証", + validating: "検証中…", + validateHint: "インポート前に全行をサーバーで検査します。", + validatePassed: "{{ok}} 行すべてが有効です。", + validateFailed: "有効 {{ok}} 行、エラー {{errors}} 行。", + errorRowPrefix: "{{row}} 行目: ", + referenceNotFound: "「{{value}}」に一致するレコードがありません", + referenceAmbiguous: "「{{value}}」は複数のレコードに一致します — 一意の値かレコード ID を使用してください", + invalidBoolean: "「{{value}}」は有効な true/false の値ではありません", + invalidNumber: "「{{value}}」は有効な数値ではありません", + invalidDate: "「{{value}}」は有効な日付ではありません", + invalidOption: "「{{value}}」は許可された選択肢に含まれていません", + requiredValue: "この項目は必須です", + matchAmbiguous: "既存の複数レコードに一致します — 一意の値かレコード ID を使用してください", + history: "履歴", + historyBack: "インポートに戻る", + historyDescription: "このオブジェクトの最近のインポート。", + historyHint: "バックグラウンドのインポートジョブ、新しい順。", + historyRefresh: "更新", + historyLoading: "読み込み中…", + historyEmpty: "インポートはまだありません。", + historyUnsupported: "このデータソースではインポート履歴を利用できません。", + historyColStatus: "ステータス", + historyColRows: "行数", + historyColResult: "結果", + historyColTime: "日時", + errorCount: "エラー {{count}} 件", + undoImport: "インポートを取り消す", + undoing: "取り消し中…", + undoConfirm: "このインポートを取り消しますか?作成されたレコードは削除され、更新されたレコードは以前の値に戻されます。", + reverted: "取り消し済み", + jobStatus: { + pending: "待機中", + running: "実行中", + succeeded: "成功", + failed: "失敗", + cancelled: "中止", + }, + legacyReferenceBlocked: "インポートを中止しました: {{fields}} は関連項目で、名前をレコード ID に解決するためサーバー側のインポート経路が必要ですが、この接続では対応していません。プレーンテキストとして取り込むとデータが壊れます。バックエンド/クライアントを更新するか、これらの列のマッピングを外して別途インポートしてください。", + missingRequiredHint: "続行できません — 必須項目が未マッピングです: {{fields}}。対応する列をファイルに追加するか、戻ってその列を含むファイルをアップロードしてください。", + legacyFallbackNotice: "互換フォールバックでインポートしました: この接続はサーバー側のインポート経路に対応していないため、値はサーバー側の型変換なしでテキストとして保存されました。完全なインポート機能(型変換と関連レコードの解決)にはバックエンド/クライアントの更新が必要です。", title: "{{object}}をインポート", notAllowed: "このオブジェクトはインポートに対応していません。", stepUpload: "アップロード", @@ -278,6 +413,36 @@ const ja = { moreEvents: "+{{count}} 件", }, list: { + firstRunTitle: "まだ何もありません", + firstRunMessage: "最初のレコードを作成して始めましょう。", + noMatches: "該当するレコードがありません", + noMatchesMessage: "現在のフィルターや検索条件に一致するレコードはありません。条件を調整するか解除してください。", + loadErrorTitle: "レコードを読み込めませんでした", + loadErrorMessage: "データの読み込み中に問題が発生しました。接続を確認して再試行してください。", + retry: "再試行", + managedBy: { + system: { + title: "まだ何もありません", + message: "元となるアクション(承認申請、共有、招待など)が実行されると自動的に登録されます。元のレコードでいずれかを実行すると行が作成されます。", + }, + appendOnly: { + title: "記録されたイベントはありません", + message: "これは変更不可の監査ログです。行はイベント発生時にプラットフォームが書き込みます — 履歴のエクスポートはできますが、ここから作成することはできません。", + }, + betterAuth: { + title: "ID レコードがありません", + message: "これらのレコードは認証プロバイダーがサインイン・プロビジョニング・セキュリティ処理を通じて作成するもので、ここで手動追加するものではありません。", + }, + betterAuthUser: { + title: "ユーザーがまだいません", + message: "ユーザーアカウントは認証プロバイダーがプロビジョニングするもので、ここで作成するものではありません。組織にメンバーを招待すると、初回サインイン時に自動的に表示されます(SSO のジャストインタイム プロビジョニング)。アプリのエンドユーザーは、アプリ経由でサインアップした時点で追加されます。", + }, + betterAuthTeam: { + title: "チームがまだありません", + message: "チームは組織内のメンバーをまとめるものです。「チームを作成」から作成するか、認証プロバイダーの組織・SSO プロビジョニングを通じて追加されます。", + }, + }, + refresh: "更新", loading: 'Loading records...', recordCount: "{{count}} 件のレコード", recordCountOne: "{{count}} 件のレコード", @@ -318,6 +483,11 @@ const ja = { recordEditingTitle: "レコード編集", }, managedByBadge: { + systemWritable: { + short: "プラットフォーム スキーマ", + title: "プラットフォーム定義・管理者が編集可能", + body: "このオブジェクトのスキーマはプラットフォームが定義しますが、行の作成・編集はここで行えます。誰が書き込めるかはこのバッジではなく、委任管理とレコードレベルのセキュリティで決まります。", + }, config: { short: "管理設定", title: "管理者設定", @@ -340,6 +510,7 @@ const ja = { }, }, kanban: { + uncategorized: "未分類", addCard: "カードを追加", addColumn: "カラムを追加", moveCard: "カードを移動", @@ -361,12 +532,92 @@ const ja = { }, }, gantt: { + viewMode: { + day: "日", + week: "週", + month: "月", + quarter: "四半期", + year: "年", + }, + row: { + expand: "展開", + collapse: "折りたたむ", + open: "詳細を開く", + }, + aria: { + taskList: "タスク一覧", + }, + tooltip: { + days: "日", + }, + menu: { + view: "詳細を表示", + edit: "インライン編集", + delete: "削除", + addPredecessor: "先行タスクを追加…", + addSuccessor: "後続タスクを追加…", + removeDependency: "依存関係を削除", + noCandidates: "利用できるタスクがありません", + searchTasks: "タスクを検索…", + }, + delete: { + title: "このタスクを削除しますか?", + body: "「{{title}}」は完全に削除されます。この操作は取り消せません。", + cancel: "キャンセル", + confirm: "削除", + deleting: "削除中…", + }, + drawer: { + fallbackTitle: "タスクの詳細", + }, + linkType: { + fs: "終了 → 開始", + ss: "開始 → 開始", + ff: "終了 → 終了", + sf: "開始 → 終了", + }, + conflict: { + title: "スケジュールの競合", + body: "この移動は依存関係の制約と競合します。影響を受ける {{count}} 件のタスクを自動で再スケジュールしますか?", + confirm: "自動で再スケジュール", + cancel: "そのままにする", + }, + autoScheduleDlg: { + title: "自動スケジュール", + body: "依存リンクを満たすため {count} 件のタスクを後ろにずらしますか?", + skipped: "ロックされた {count} 件のタスクもリンクに違反していますが、スキップされました。", + confirm: "適用", + cancel: "キャンセル", + none: "依存関係はすべて満たされています — 再スケジュールの必要はありません。", + }, + resource: { + header: "リソース", + peak: "ピーク", + over: "過負荷", + empty: "割り当てるタスクがありません。", + }, + readOnly: "読み取り専用", + readOnlyHint: "このビューでは編集できません。", + lockedHint: "編集権限がありません", + writeFailed: "保存に失敗しました — 変更は取り消されました", column: { taskName: "タスク名", start: "開始", end: "終了", }, toolbar: { + thisWeek: "今週", + thisMonth: "今月", + viewMode: "タイムラインの粒度", + enterFullscreen: "全画面表示", + exitFullscreen: "全画面表示を終了", + criticalPath: "クリティカルパスを強調", + autoSchedule: "依存関係を自動スケジュール", + exportPng: "PNG で書き出す", + exportPdf: "PDF で書き出す", + saveLayout: "レイアウトを保存", + undo: "元に戻す", + redo: "やり直す", prevPeriod: "前の期間", nextPeriod: "次の期間", zoomIn: "拡大", @@ -412,6 +663,9 @@ const ja = { redo: 'Redo', }, detail: { + editInlineHint: "ダブルクリックで編集", + systemActor: "システム", + unknownUser: "不明", back: "戻る", edit: "編集", editInline: "編集", @@ -626,6 +880,35 @@ const ja = { resetZoom: "ズームをリセット", }, dashboard: { + noRows: "行がありません", + pickMeasures: "このデータセットウィジェットの指標(値)を選択してください。", + datasetUnsupported: "このデータソースはデータセットクエリに対応していません。", + details: "詳細", + exportCsv: "CSV で書き出す", + openInList: "リストで開く", + filters: { + label: "ダッシュボードのフィルター", + dateRange: "期間", + allTime: "全期間", + custom: "カスタム…", + all: "すべて", + reset: "リセット", + range: { + today: "今日", + yesterday: "昨日", + this_week: "今週", + last_week: "先週", + this_month: "今月", + last_month: "先月", + this_quarter: "今四半期", + last_quarter: "前四半期", + this_year: "今年", + last_year: "昨年", + last_7_days: "過去 7 日間", + last_30_days: "過去 30 日間", + last_90_days: "過去 90 日間", + }, + }, addWidget: "ウィジェットを追加", removeWidget: "ウィジェットを削除", editLayout: "レイアウトを編集", @@ -1343,7 +1626,28 @@ const ja = { }, }, auth: { + setPassword: { + title: "リカバリー用パスワードを設定", + description: "シングルサインオンでサインインしました。SSO が利用できなくなったときにもこの環境へ直接サインインできるよう、ローカルパスワードを設定してください。", + email: "メールアドレス", + newPassword: "新しいパスワード", + confirmPassword: "パスワードの確認", + submit: "パスワードを設定", + submitting: "保存中…", + success: "ローカルパスワードを設定しました", + failed: "パスワードを設定できませんでした", + passwordsMismatch: "パスワードが一致しません", + noSession: "セッションの有効期限が切れました。", + backToSignIn: "もう一度サインイン", + }, login: { + signingIn: "サインインしています…", + ssoHandoff: "{{target}} に進む", + devAdminHint: { + title: "開発用インスタンス", + body: "初期設定の開発用管理者でサインインします:", + dismiss: "閉じる", + }, title: "アカウントにサインイン", description: "メールアドレスとパスワードを入力してください", emailLabel: "メールアドレス", @@ -1356,6 +1660,7 @@ const ja = { noAccountText: "アカウントをお持ちでないですか?", signUpText: "新規登録", errors: { + oauthCallbackFailed: "シングルサインオンを完了できませんでした — サインインリンクの有効期限が切れたか、すでに使用されています。もう一度お試しください。", invalidCredentials: "メールアドレスまたはパスワードが正しくありません。再試行してください。", emailNotVerified: "サインインする前にメールアドレスを確認してください。", }, @@ -1580,11 +1885,15 @@ const ja = { settings: "ワークスペース設定", }, help: { + onThisPage: "このページの内容", + keyboardShortcuts: "キーボードショートカット", appDocs: 'このアプリのドキュメント', allDocs: 'すべてのドキュメント', onlineDocs: 'オンラインドキュメント', }, sidebar: { + packageManagement: "パッケージ管理", + searchNavigation: "ナビゲーションを検索...", settings: "設定", help: "ヘルプ", helpTooltip: "ヘルプとドキュメント", @@ -1845,6 +2154,8 @@ const ja = { updateFailed: "ナビゲーションの更新に失敗しました", }, objectActions: { + resetPackageSetConfirm: "この権限セットはインストール済みパッケージに含まれているため削除できません。削除を実行すると、パッケージ同梱の初期状態に戻り、この環境でのカスタマイズは破棄されます。続行しますか?", + resetPackageSetSuccess: "権限セットをパッケージ同梱の初期状態に戻しました", deleteSuccess: "{{label}} を削除しました", deleteFailed: "{{label}} の削除に失敗しました", noRecordId: "レコードIDが指定されていません", @@ -2009,6 +2320,7 @@ const ja = { }, }, organizations: { + create: "ワークスペースを作成", mine: "マイ組織", title: "組織", heading: "あなたの組織", @@ -2022,6 +2334,11 @@ const ja = { noMatches: "検索条件に一致する組織がありません。", }, notifications: { + regionLabel: "通知", + groupCount: "通知 {{count}} 件", + groupUnread: "未読 {{count}} 件", + groupMarkRead: "既読にする", + groupMarkReadTitle: "この種類をすべて既読にする", empty: "通知はありません", markAllRead: "すべて既読にする", viewAll: "すべての通知を表示", @@ -2224,6 +2541,15 @@ const ja = { }, }, marketplace: { + suggestedBindings: { + promptEveryone: "このアプリは、サインイン済みの全ユーザー(Everyone ポジション)に「{{set}}」を付与することを推奨しています。", + promptGuest: "このアプリは、未認証の訪問者(Guest ポジション)に「{{set}}」を付与することを推奨しています。", + confirm: "付与", + confirming: "付与しています…", + dismiss: "閉じる", + confirmedToast: "「{{set}}」を {{anchor}} に付与しました。", + dismissedToast: "「{{set}}」の提案を閉じました。", + }, title: "アプリマーケットプレイス", subtitle: "ObjectStackカタログに公開された承認済みアプリを参照します。アプリをクリックして詳細を表示し、環境にインストールしてください。", searchPlaceholder: "名前またはマニフェストIDでアプリを検索…", @@ -2275,6 +2601,7 @@ const ja = { updateAvailable: 'アップデートあり', }, action: { + installed: "インストール済み", install: "インストール", reinstall: "再インストール", working: "処理中…", @@ -2526,6 +2853,10 @@ const ja = { retry: '再試行', }, draftBar: { + messageClean: "ドラフトプレビュー — 未公開の変更はありません。ここに表示されている内容はすべて公開済みです。", + sampleDataTitle: "サンプルデータは公開後に表示されます", + sampleDataBody: "アプリの構造をプレビューしています。公開するとサンプルレコードが読み込まれ、アプリが有効になります。", + publishCta: "公開して確認する", message: 'ドラフトプレビュー — 未公開の変更を表示しています。公開するまで本番には反映されません。', publish: '公開', publishing: '公開中…', diff --git a/packages/i18n/src/locales/ko.ts b/packages/i18n/src/locales/ko.ts index 45bf11676e..eb71c7f666 100644 --- a/packages/i18n/src/locales/ko.ts +++ b/packages/i18n/src/locales/ko.ts @@ -1,6 +1,15 @@ const ko = { // objectui#2600 B5 — capability picker scope group headers (labels come from the sys_capability registry). capability: { + label: { + manage_users: "사용자 관리", + manage_org_users: "조직 사용자 관리", + manage_metadata: "메타데이터 관리", + manage_platform_settings: "플랫폼 설정 관리", + setup_access: "설정 접근", + setup_write: "설정 쓰기", + studio_access: "Studio 접근", + }, group: { platform: '플랫폼', org: '조직', @@ -98,6 +107,10 @@ const ko = { type: "{{field}}은(는) 유효한 {{type}}이어야 합니다", }, form: { + discardTitle: "변경 내용을 버릴까요?", + discardMessage: "저장하지 않은 변경 내용이 있습니다. 지금 이 양식을 닫으면 편집 내용이 사라집니다.", + keepEditing: "계속 편집", + discard: "버리기", addItem: "항목 추가", removeItem: "항목 제거", fieldRequired: "이 필드는 필수입니다", @@ -118,6 +131,19 @@ const ko = { deleteSuccess: "{{object}} 삭제됨", }, fields: { + image: { + upload: "이미지 업로드", + addMore: "이미지 더 추가", + uploading: "업로드 중…", + imageAlt: "이미지 {{index}}", + crop: "이미지 {{index}} 자르기", + remove: "이미지 {{index}} 삭제", + enlarge: "{{name}} 확대", + preview: "이미지 미리보기", + previous: "이전 이미지", + next: "다음 이미지", + counter: "{{current}} / {{total}}", + }, relativeDate: { overdue: "기한 초과 {{count}}일", }, @@ -176,6 +202,36 @@ const ko = { delete: "삭제", }, grid: { + bulkSelected: "{{count}}건 선택됨", + bulkSelectedAllMatches: "{{count}}건 선택됨(일치 항목 전체)", + bulkClear: "선택 해제", + bulkAllOnPage: "이 페이지의 {{count}}건이 모두 선택되었습니다.", + bulkSelectAllMatching: "일치하는 {{count}}건 모두 선택", + bulkAllMatchingSelected: "일치하는 레코드 {{count}}건이 모두 선택되었습니다.", + bulk: { + confirmDefault: "레코드 {{count}}건에 적용됩니다.", + overLimit: "선택 건수({{count}})가 작업 한도({{limit}})를 초과합니다. 선택을 줄여 주세요.", + affectedRecords: "영향을 받는 레코드({{count}}):", + rowFallback: "{{index}}행", + andMore: "… 외 {{count}}건", + processed: "{{count}} / {{total}} 처리됨", + processedFailed: " · 실패 {{count}}건", + undonePrefix: "되돌림 — ", + succeeded: "성공 {{count}} / {{total}}", + resultFailed: " · 실패 {{count}}", + retry: "다시 시도", + downloadErrorCsv: "오류 CSV 다운로드", + cancel: "취소", + back: "뒤로", + next: "다음", + run: "실행", + running: "실행 중…", + undo: "되돌리기", + undoing: "되돌리는 중…", + done: "완료", + selectPlaceholder: "선택…", + loading: "불러오는 중…", + }, actions: "작업", edit: "편집", delete: "삭제", @@ -201,6 +257,85 @@ const ko = { densityCycleShortHint: "클릭하여 변경", }, import: { + downloadTemplate: "템플릿 다운로드", + downloadTemplateHint: "올바른 열이 포함된 CSV를 받습니다(필수 필드에는 * 표시).", + templateFileName: "{{object}}-import-template", + autoMatched: "자동 매핑됨", + autoMatchedSummary: "{{count}}개 열을 자동 매핑했습니다 — 아래에서 확인하고 조정하세요.", + confidence: { + high: "신뢰도 높음", + medium: "신뢰도 보통", + low: "신뢰도 낮음", + }, + asyncQueued: "대기 중 — 가져오기를 준비하고 있습니다…", + asyncProcessing: "{{total}}행 중 {{processed}}행 가져오는 중… {{progress}}%", + asyncLargeHint: "파일이 커서 백그라운드에서 가져옵니다.", + largeSampleNotice: "{{total}}행 중 처음 {{shown}}행을 미리 보고 있습니다.", + cancelImport: "가져오기 취소", + importCancelled: "가져오기를 취소했습니다", + resultsTruncated: "{{total}}행 중 처음 {{count}}행의 결과를 표시합니다.", + createdCount: "{{count}}건 생성", + updatedCount: "{{count}}건 업데이트", + downloadFailed: "실패한 행 다운로드", + options: "가져오기 옵션", + writeMode: "기존 레코드와 일치하는 행의 처리", + writeModeOpt: { + insert: "항상 새로 생성", + update: "기존 항목 업데이트(일치하지 않으면 건너뜀)", + upsert: "일치하면 업데이트, 없으면 생성", + }, + matchFields: "일치 기준", + matchFieldsPlaceholder: "일치 기준 필드 선택…", + matchFieldsHint: "이 필드를 기준으로 기존 레코드와 대조합니다.", + needMatchFields: "일치 기준 필드를 하나 이상 선택하세요.", + optCreateOptions: "알 수 없는 선택 값 유지", + optRunAutomations: "자동화 및 트리거 실행", + optTreatHistorical: "과거 데이터로 가져오기", + optTreatHistoricalHint: "(완료된 레코드를 그대로 가져옵니다 — 상태 머신 검사를 건너뛰고 현재 시각을 찍는 대신 원래 타임스탬프와 작성자를 유지합니다)", + optSkipBlankKey: "일치 값이 비어 있는 행 건너뛰기", + optBackground: "백그라운드에서 가져오기", + optBackgroundHint: "(되돌릴 수 있는 작업으로 실행됩니다)", + validate: "데이터 검증", + validating: "검증 중…", + validateHint: "가져오기 전에 모든 행을 서버에서 검사합니다.", + validatePassed: "{{ok}}행 모두 유효합니다.", + validateFailed: "유효 {{ok}}행, 오류 {{errors}}행.", + errorRowPrefix: "{{row}}행: ", + referenceNotFound: "\"{{value}}\"과(와) 일치하는 레코드가 없습니다", + referenceAmbiguous: "\"{{value}}\"이(가) 여러 레코드와 일치합니다 — 고유한 값이나 레코드 ID를 사용하세요", + invalidBoolean: "\"{{value}}\"은(는) 유효한 true/false 값이 아닙니다", + invalidNumber: "\"{{value}}\"은(는) 유효한 숫자가 아닙니다", + invalidDate: "\"{{value}}\"은(는) 유효한 날짜가 아닙니다", + invalidOption: "\"{{value}}\"은(는) 허용된 선택지에 없습니다", + requiredValue: "필수 필드입니다", + matchAmbiguous: "기존 레코드 여러 건과 일치합니다 — 고유한 값이나 레코드 ID를 사용하세요", + history: "기록", + historyBack: "가져오기로 돌아가기", + historyDescription: "이 객체의 최근 가져오기입니다.", + historyHint: "백그라운드 가져오기 작업, 최신순.", + historyRefresh: "새로고침", + historyLoading: "불러오는 중…", + historyEmpty: "아직 가져오기가 없습니다.", + historyUnsupported: "이 데이터 소스는 가져오기 기록을 지원하지 않습니다.", + historyColStatus: "상태", + historyColRows: "행 수", + historyColResult: "결과", + historyColTime: "시각", + errorCount: "오류 {{count}}건", + undoImport: "가져오기 되돌리기", + undoing: "되돌리는 중…", + undoConfirm: "이 가져오기를 되돌릴까요? 생성된 레코드는 삭제되고 업데이트된 레코드는 이전 값으로 복원됩니다.", + reverted: "되돌림", + jobStatus: { + pending: "대기 중", + running: "실행 중", + succeeded: "성공", + failed: "실패", + cancelled: "취소됨", + }, + legacyReferenceBlocked: "가져오기가 차단되었습니다: {{fields}}은(는) 이름을 레코드 ID로 변환하기 위해 서버 가져오기 경로가 필요한 관계 필드인데, 이 연결은 이를 지원하지 않습니다. 일반 텍스트로 가져오면 데이터가 손상됩니다. 백엔드/클라이언트를 업그레이드하거나, 이 열들의 매핑을 해제하고 별도로 가져오세요.", + missingRequiredHint: "계속할 수 없습니다 — 매핑되지 않은 필수 필드: {{fields}}. 파일에 해당 열을 추가하거나, 뒤로 돌아가 그 열이 포함된 파일을 업로드하세요.", + legacyFallbackNotice: "호환 폴백으로 가져왔습니다: 이 연결은 서버 가져오기 경로를 지원하지 않아 값이 서버 측 형 변환 없이 텍스트로 저장되었습니다. 완전한 가져오기 기능(형 변환 및 관계 조회)을 사용하려면 백엔드/클라이언트를 업그레이드하세요.", title: "{{object}} 가져오기", notAllowed: "이 객체는 가져오기가 허용되지 않습니다.", stepUpload: "업로드", @@ -278,6 +413,36 @@ const ko = { moreEvents: "+{{count}} 더보기", }, list: { + firstRunTitle: "아직 아무것도 없습니다", + firstRunMessage: "첫 레코드를 만들어 시작하세요.", + noMatches: "일치하는 레코드가 없습니다", + noMatchesMessage: "현재 필터나 검색어와 일치하는 레코드가 없습니다. 조건을 조정하거나 지워 보세요.", + loadErrorTitle: "레코드를 불러오지 못했습니다", + loadErrorMessage: "이 데이터를 불러오는 중 문제가 발생했습니다. 연결을 확인하고 다시 시도하세요.", + retry: "다시 시도", + managedBy: { + system: { + title: "아직 아무것도 없습니다", + message: "원본 작업(승인 요청, 공유, 초대 등)이 실행되면 항목이 자동으로 나타납니다. 원본 레코드에서 그중 하나를 실행하면 행이 생성됩니다.", + }, + appendOnly: { + title: "기록된 이벤트가 없습니다", + message: "변경할 수 없는 감사 로그입니다. 행은 이벤트 발생 시 플랫폼이 기록합니다 — 기록을 내보낼 수는 있지만 여기서 항목을 만들 수는 없습니다.", + }, + betterAuth: { + title: "ID 레코드가 없습니다", + message: "이 레코드들은 인증 공급자가 로그인·프로비저닝·보안 절차를 통해 생성하며, 여기서 직접 추가하는 것이 아닙니다.", + }, + betterAuthUser: { + title: "아직 사용자가 없습니다", + message: "사용자 계정은 인증 공급자가 프로비저닝하며 여기서 생성하지 않습니다. 조직에 동료를 초대하면 첫 로그인 시 자동으로 표시됩니다(SSO 적시 프로비저닝). 앱 사용자는 앱을 통해 가입할 때 추가됩니다.", + }, + betterAuthTeam: { + title: "아직 팀이 없습니다", + message: "팀은 조직 내 구성원을 묶습니다. \"팀 만들기\"로 생성하거나, 인증 공급자의 조직 및 SSO 프로비저닝 절차를 통해 추가됩니다.", + }, + }, + refresh: "새로고침", loading: 'Loading records...', recordCount: "{{count}}개 레코드", recordCountOne: "{{count}}개 레코드", @@ -318,6 +483,11 @@ const ko = { recordEditingTitle: "레코드 편집", }, managedByBadge: { + systemWritable: { + short: "플랫폼 스키마", + title: "플랫폼 정의, 관리자 쓰기 가능", + body: "이 객체의 스키마는 플랫폼이 정의하지만, 행은 여기서 직접 만들고 편집할 수 있습니다. 누가 쓸 수 있는지는 이 배지가 아니라 위임 관리와 레코드 수준 보안이 결정합니다.", + }, config: { short: "관리 구성", title: "관리자 구성", @@ -340,6 +510,7 @@ const ko = { }, }, kanban: { + uncategorized: "미분류", addCard: "카드 추가", addColumn: "열 추가", moveCard: "카드 이동", @@ -361,12 +532,92 @@ const ko = { }, }, gantt: { + viewMode: { + day: "일", + week: "주", + month: "월", + quarter: "분기", + year: "년", + }, + row: { + expand: "펼치기", + collapse: "접기", + open: "세부 정보 열기", + }, + aria: { + taskList: "작업 목록", + }, + tooltip: { + days: "일", + }, + menu: { + view: "세부 정보 보기", + edit: "인라인 편집", + delete: "삭제", + addPredecessor: "선행 작업 추가…", + addSuccessor: "후행 작업 추가…", + removeDependency: "종속성 제거", + noCandidates: "사용할 수 있는 작업이 없습니다", + searchTasks: "작업 검색…", + }, + delete: { + title: "이 작업을 삭제할까요?", + body: "\"{{title}}\"이(가) 영구히 삭제됩니다. 이 작업은 되돌릴 수 없습니다.", + cancel: "취소", + confirm: "삭제", + deleting: "삭제 중…", + }, + drawer: { + fallbackTitle: "작업 세부 정보", + }, + linkType: { + fs: "종료 → 시작", + ss: "시작 → 시작", + ff: "종료 → 종료", + sf: "시작 → 종료", + }, + conflict: { + title: "일정 충돌", + body: "이 이동은 종속성 제약과 충돌합니다. 영향을 받는 작업 {{count}}건의 일정을 자동으로 조정할까요?", + confirm: "자동 일정 조정", + cancel: "그대로 두기", + }, + autoScheduleDlg: { + title: "자동 일정 조정", + body: "종속성 연결을 충족하도록 작업 {count}건을 뒤로 미룰까요?", + skipped: "잠긴 작업 {count}건도 연결을 위반하지만 건너뛰었습니다.", + confirm: "적용", + cancel: "취소", + none: "모든 종속성이 충족되었습니다 — 조정할 일정이 없습니다.", + }, + resource: { + header: "리소스", + peak: "최대", + over: "과부하", + empty: "배정할 작업이 없습니다.", + }, + readOnly: "읽기 전용", + readOnlyHint: "이 보기에서는 편집할 수 없습니다.", + lockedHint: "편집 권한 없음", + writeFailed: "저장 실패 — 변경이 취소되었습니다", column: { taskName: "작업 이름", start: "시작", end: "종료", }, toolbar: { + thisWeek: "이번 주", + thisMonth: "이번 달", + viewMode: "타임라인 단위", + enterFullscreen: "전체 화면", + exitFullscreen: "전체 화면 종료", + criticalPath: "주 공정 경로 강조", + autoSchedule: "종속성 자동 일정 조정", + exportPng: "PNG로 내보내기", + exportPdf: "PDF로 내보내기", + saveLayout: "레이아웃 저장", + undo: "실행 취소", + redo: "다시 실행", prevPeriod: "이전 기간", nextPeriod: "다음 기간", zoomIn: "확대", @@ -412,6 +663,9 @@ const ko = { redo: 'Redo', }, detail: { + editInlineHint: "더블클릭하여 편집", + systemActor: "시스템", + unknownUser: "알 수 없음", back: "뒤로", edit: "편집", editInline: "편집", @@ -626,6 +880,35 @@ const ko = { resetZoom: "확대/축소 초기화", }, dashboard: { + noRows: "행 없음", + pickMeasures: "이 데이터셋 위젯의 측정값(값)을 선택하세요.", + datasetUnsupported: "이 데이터 소스는 데이터셋 쿼리를 지원하지 않습니다.", + details: "세부 정보", + exportCsv: "CSV 내보내기", + openInList: "목록에서 열기", + filters: { + label: "대시보드 필터", + dateRange: "기간", + allTime: "전체 기간", + custom: "사용자 지정…", + all: "전체", + reset: "초기화", + range: { + today: "오늘", + yesterday: "어제", + this_week: "이번 주", + last_week: "지난주", + this_month: "이번 달", + last_month: "지난달", + this_quarter: "이번 분기", + last_quarter: "지난 분기", + this_year: "올해", + last_year: "작년", + last_7_days: "최근 7일", + last_30_days: "최근 30일", + last_90_days: "최근 90일", + }, + }, addWidget: "위젯 추가", removeWidget: "위젯 제거", editLayout: "레이아웃 편집", @@ -1343,7 +1626,28 @@ const ko = { }, }, auth: { + setPassword: { + title: "복구 비밀번호 설정", + description: "싱글 사인온으로 로그인했습니다. SSO를 사용할 수 없게 되더라도 이 환경에 직접 로그인할 수 있도록 로컬 비밀번호를 설정하세요.", + email: "이메일", + newPassword: "새 비밀번호", + confirmPassword: "비밀번호 확인", + submit: "비밀번호 설정", + submitting: "저장 중…", + success: "로컬 비밀번호가 설정되었습니다", + failed: "비밀번호를 설정하지 못했습니다", + passwordsMismatch: "비밀번호가 일치하지 않습니다", + noSession: "세션이 만료되었습니다.", + backToSignIn: "다시 로그인", + }, login: { + signingIn: "로그인하는 중…", + ssoHandoff: "{{target}}(으)로 계속", + devAdminHint: { + title: "개발 인스턴스", + body: "시드된 개발용 관리자 계정으로 로그인하세요:", + dismiss: "닫기", + }, title: "계정에 로그인", description: "이메일과 비밀번호를 입력하세요", emailLabel: "이메일", @@ -1356,6 +1660,7 @@ const ko = { noAccountText: "계정이 없으신가요?", signUpText: "회원가입", errors: { + oauthCallbackFailed: "싱글 사인온을 완료하지 못했습니다 — 로그인 링크가 만료되었거나 이미 사용되었습니다. 다시 시도해 주세요.", invalidCredentials: "잘못된 이메일 또는 비밀번호입니다. 다시 시도하세요.", emailNotVerified: "로그인하기 전에 이메일을 확인하세요.", }, @@ -1580,11 +1885,15 @@ const ko = { settings: "워크스페이스 설정", }, help: { + onThisPage: "이 페이지에서", + keyboardShortcuts: "키보드 단축키", appDocs: '이 앱의 문서', allDocs: '전체 문서', onlineDocs: '온라인 문서', }, sidebar: { + packageManagement: "패키지 관리", + searchNavigation: "내비게이션 검색...", settings: "설정", help: "도움말", helpTooltip: "도움말 및 문서", @@ -1845,6 +2154,8 @@ const ko = { updateFailed: "탐색을 업데이트할 수 없습니다", }, objectActions: { + resetPackageSetConfirm: "이 권한 세트는 설치된 패키지에 포함되어 있어 제거할 수 없습니다. 삭제하면 패키지 기본 상태로 초기화되며 이 환경의 커스터마이징은 폐기됩니다. 계속할까요?", + resetPackageSetSuccess: "권한 세트를 패키지 기본 상태로 초기화했습니다", deleteSuccess: "{{label}} 삭제 성공", deleteFailed: "{{label}} 삭제 실패", noRecordId: "레코드 ID가 없습니다", @@ -2009,6 +2320,7 @@ const ko = { }, }, organizations: { + create: "워크스페이스 만들기", mine: "내 조직", title: "조직", heading: "내 조직", @@ -2022,6 +2334,11 @@ const ko = { noMatches: "검색과 일치하는 조직이 없습니다.", }, notifications: { + regionLabel: "알림", + groupCount: "알림 {{count}}건", + groupUnread: "읽지 않음 {{count}}건", + groupMarkRead: "읽음 표시", + groupMarkReadTitle: "이 유형 전체를 읽음으로 표시", empty: "알림 없음", markAllRead: "모두 읽음으로 표시", viewAll: "모든 알림 보기", @@ -2224,6 +2541,15 @@ const ko = { }, }, marketplace: { + suggestedBindings: { + promptEveryone: "이 앱은 로그인한 모든 사용자(Everyone 위치)에게 \"{{set}}\"을(를) 부여할 것을 제안합니다.", + promptGuest: "이 앱은 인증되지 않은 방문자(Guest 위치)에게 \"{{set}}\"을(를) 부여할 것을 제안합니다.", + confirm: "부여", + confirming: "부여하는 중…", + dismiss: "닫기", + confirmedToast: "\"{{set}}\"이(가) 이제 {{anchor}}에 부여되었습니다.", + dismissedToast: "\"{{set}}\" 제안을 닫았습니다.", + }, title: "앱 마켓플레이스", subtitle: "ObjectStack 카탈로그에 게시된 승인된 앱을 탐색하세요. 앱을 클릭하여 세부 정보를 보고 환경 중 하나에 설치하세요.", searchPlaceholder: "이름 또는 매니페스트 ID로 앱 검색…", @@ -2275,6 +2601,7 @@ const ko = { updateAvailable: '업데이트 가능', }, action: { + installed: "설치됨", install: "설치", reinstall: "재설치", working: "처리 중…", @@ -2526,6 +2853,10 @@ const ko = { retry: '다시 시도', }, draftBar: { + messageClean: "초안 미리보기 — 게시되지 않은 변경이 없습니다. 여기 있는 내용은 모두 이미 반영되어 있습니다.", + sampleDataTitle: "샘플 데이터는 게시 후에 표시됩니다", + sampleDataBody: "앱의 구조를 미리 보고 있습니다. 게시하면 예시 레코드가 로드되고 앱이 활성화됩니다.", + publishCta: "게시하고 확인하기", message: '드래프트 미리보기 — 게시되지 않은 변경 사항을 보고 있습니다. 게시 전에는 실제로 반영되지 않습니다.', publish: '게시', publishing: '게시 중…', diff --git a/packages/i18n/src/locales/pt.ts b/packages/i18n/src/locales/pt.ts index b97eb17c26..233e042017 100644 --- a/packages/i18n/src/locales/pt.ts +++ b/packages/i18n/src/locales/pt.ts @@ -1,6 +1,15 @@ const pt = { // objectui#2600 B5 — capability picker scope group headers (labels come from the sys_capability registry). capability: { + label: { + manage_users: "Gerenciar usuários", + manage_org_users: "Gerenciar usuários da organização", + manage_metadata: "Gerenciar metadados", + manage_platform_settings: "Gerenciar configurações da plataforma", + setup_access: "Acesso à configuração", + setup_write: "Gravação de configurações", + studio_access: "Acesso ao Studio", + }, group: { platform: 'Plataforma', org: 'Organização', @@ -98,6 +107,10 @@ const pt = { type: "{{field}} deve ser um {{type}} válido", }, form: { + discardTitle: "Descartar as alterações?", + discardMessage: "Você tem alterações não salvas. Se fechar este formulário agora, suas edições serão perdidas.", + keepEditing: "Continuar editando", + discard: "Descartar", addItem: "Adicionar item", removeItem: "Remover item", fieldRequired: "Este campo é obrigatório", @@ -118,6 +131,19 @@ const pt = { deleteSuccess: "{{object}} excluído", }, fields: { + image: { + upload: "Enviar imagem", + addMore: "Adicionar mais imagens", + uploading: "Enviando…", + imageAlt: "Imagem {{index}}", + crop: "Recortar a imagem {{index}}", + remove: "Remover a imagem {{index}}", + enlarge: "Ampliar {{name}}", + preview: "Pré-visualização da imagem", + previous: "Imagem anterior", + next: "Próxima imagem", + counter: "{{current}} / {{total}}", + }, relativeDate: { overdue: "Atrasado {{count}} d", }, @@ -176,6 +202,36 @@ const pt = { delete: "Excluir", }, grid: { + bulkSelected: "{{count}} selecionados", + bulkSelectedAllMatches: "{{count}} selecionados (todas as correspondências)", + bulkClear: "Limpar seleção", + bulkAllOnPage: "Os {{count}} desta página estão selecionados.", + bulkSelectAllMatching: "Selecionar todas as {{count}} correspondências", + bulkAllMatchingSelected: "Todos os {{count}} registros correspondentes estão selecionados.", + bulk: { + confirmDefault: "Isto será aplicado a {{count}} registro(s).", + overLimit: "A seleção ({{count}}) excede o limite da ação ({{limit}}). Reduza a seleção para prosseguir.", + affectedRecords: "Registros afetados ({{count}}):", + rowFallback: "Linha {{index}}", + andMore: "… e mais {{count}}", + processed: "{{count}} / {{total}} processados", + processedFailed: " · {{count}} com falha", + undonePrefix: "Desfeito — ", + succeeded: "Concluídos {{count}} / {{total}}", + resultFailed: " · Falhas {{count}}", + retry: "Tentar novamente", + downloadErrorCsv: "Baixar CSV de erros", + cancel: "Cancelar", + back: "Voltar", + next: "Avançar", + run: "Executar", + running: "Executando…", + undo: "Desfazer", + undoing: "Desfazendo…", + done: "Concluído", + selectPlaceholder: "Selecionar…", + loading: "Carregando…", + }, actions: "Ações", edit: "Editar", delete: "Excluir", @@ -201,6 +257,85 @@ const pt = { densityCycleShortHint: "Clique para alterar", }, import: { + downloadTemplate: "Baixar modelo", + downloadTemplateHint: "Obtenha um CSV com as colunas corretas (campos obrigatórios marcados com *).", + templateFileName: "{{object}}-import-template", + autoMatched: "Correspondência automática", + autoMatchedSummary: "{{count}} coluna(s) com correspondência automática — revise e ajuste abaixo.", + confidence: { + high: "Confiança alta", + medium: "Confiança média", + low: "Confiança baixa", + }, + asyncQueued: "Na fila — preparando a importação…", + asyncProcessing: "Importando {{processed}} de {{total}} linhas… {{progress}}%", + asyncLargeHint: "Este arquivo é grande, portanto será importado em segundo plano.", + largeSampleNotice: "Pré-visualizando as primeiras {{shown}} de {{total}} linhas.", + cancelImport: "Cancelar a importação", + importCancelled: "Importação cancelada", + resultsTruncated: "Mostrando os primeiros {{count}} resultados de linhas (de {{total}}).", + createdCount: "{{count}} criados", + updatedCount: "{{count}} atualizados", + downloadFailed: "Baixar as linhas com falha", + options: "Opções de importação", + writeMode: "Quando uma linha corresponde a um registro existente", + writeModeOpt: { + insert: "Sempre criar novo", + update: "Atualizar o existente (ignorar se não houver correspondência)", + upsert: "Atualizar se corresponder, senão criar", + }, + matchFields: "Corresponder por", + matchFieldsPlaceholder: "Escolha o(s) campo(s) de correspondência…", + matchFieldsHint: "As linhas são associadas a registros existentes por estes campos.", + needMatchFields: "Selecione pelo menos um campo de correspondência.", + optCreateOptions: "Manter valores de opção desconhecidos", + optRunAutomations: "Executar automações e gatilhos", + optTreatHistorical: "Importar como dados históricos", + optTreatHistoricalHint: "(importa registros concluídos como estão — ignora as verificações da máquina de estados e mantém os carimbos de data/hora e o autor originais em vez de carimbar agora)", + optSkipBlankKey: "Ignorar linhas com valor de correspondência em branco", + optBackground: "Importar em segundo plano", + optBackgroundHint: "(executa como um trabalho reversível)", + validate: "Validar os dados", + validating: "Validando…", + validateHint: "Verificar cada linha no servidor antes de importar.", + validatePassed: "Todas as {{ok}} linhas são válidas.", + validateFailed: "{{ok}} válidas, {{errors}} com erros.", + errorRowPrefix: "Linha {{row}}: ", + referenceNotFound: "Nenhum registro corresponde a \"{{value}}\"", + referenceAmbiguous: "\"{{value}}\" corresponde a mais de um registro — use um valor único ou o id do registro", + invalidBoolean: "\"{{value}}\" não é um valor verdadeiro/falso válido", + invalidNumber: "\"{{value}}\" não é um número válido", + invalidDate: "\"{{value}}\" não é uma data válida", + invalidOption: "\"{{value}}\" não está entre as opções permitidas", + requiredValue: "Este campo é obrigatório", + matchAmbiguous: "Corresponde a mais de um registro existente — use um valor único ou o id do registro", + history: "Histórico", + historyBack: "Voltar para a importação", + historyDescription: "Importações recentes deste objeto.", + historyHint: "Trabalhos de importação em segundo plano, dos mais recentes aos mais antigos.", + historyRefresh: "Atualizar", + historyLoading: "Carregando…", + historyEmpty: "Ainda não há importações.", + historyUnsupported: "O histórico de importações não está disponível para esta fonte de dados.", + historyColStatus: "Status", + historyColRows: "Linhas", + historyColResult: "Resultado", + historyColTime: "Quando", + errorCount: "{{count}} erros", + undoImport: "Desfazer a importação", + undoing: "Desfazendo…", + undoConfirm: "Desfazer esta importação? Os registros criados serão excluídos e os registros atualizados voltarão aos valores anteriores.", + reverted: "Desfeita", + jobStatus: { + pending: "Pendente", + running: "Em execução", + succeeded: "Concluída", + failed: "Falhou", + cancelled: "Cancelada", + }, + legacyReferenceBlocked: "Importação bloqueada: {{fields}} são campos de relação que precisam da rota de importação do servidor para converter nomes em IDs de registro, e esta conexão não oferece suporte a isso. Importá-los como texto simples corromperia os dados. Atualize o backend/cliente, ou desfaça o mapeamento dessas colunas e importe-as separadamente.", + missingRequiredHint: "Não é possível continuar — campos obrigatórios não mapeados: {{fields}}. Adicione uma coluna correspondente ao seu arquivo, ou volte e envie um arquivo que a inclua.", + legacyFallbackNotice: "Importado por um modo de compatibilidade: esta conexão não oferece suporte à rota de importação do servidor, então os valores foram salvos como texto sem conversão de tipos no servidor. Atualize o backend/cliente para ter a importação completa (conversão de tipos e busca de relações).", title: "Importar {{object}}", notAllowed: "Este objeto não está aberto para importação.", stepUpload: "Enviar", @@ -278,6 +413,36 @@ const pt = { moreEvents: "+{{count}} mais", }, list: { + firstRunTitle: "Ainda não há nada aqui", + firstRunMessage: "Crie seu primeiro registro para começar.", + noMatches: "Nenhum registro correspondente", + noMatchesMessage: "Nenhum registro corresponde aos seus filtros ou à sua busca atuais. Tente ajustá-los ou limpá-los.", + loadErrorTitle: "Não foi possível carregar os registros", + loadErrorMessage: "Algo deu errado ao carregar estes dados. Verifique sua conexão e tente novamente.", + retry: "Tentar novamente", + managedBy: { + system: { + title: "Ainda não há nada aqui", + message: "As entradas aparecem automaticamente quando a ação de origem é executada (por exemplo, Enviar para aprovação, Compartilhar, Convidar). Acione uma delas em um registro de origem para criar uma linha.", + }, + appendOnly: { + title: "Nenhum evento registrado", + message: "Este é um log de auditoria imutável. As linhas são gravadas pela plataforma quando ocorrem eventos — você pode exportar o histórico, mas não criar entradas aqui.", + }, + betterAuth: { + title: "Nenhum registro de identidade", + message: "Estes registros são criados pelo provedor de autenticação — por meio de login, provisionamento e fluxos de segurança — e não adicionados manualmente aqui.", + }, + betterAuthUser: { + title: "Ainda não há usuários", + message: "As contas de usuário são provisionadas pelo provedor de autenticação, não criadas aqui. Convide colegas para sua organização e eles aparecerão automaticamente no primeiro login (provisionamento SSO just-in-time). Os usuários finais do app chegam quando se cadastram por meio dele.", + }, + betterAuthTeam: { + title: "Ainda não há equipes", + message: "As equipes agrupam membros dentro de uma organização. Crie uma com \"Criar equipe\", ou elas surgem pelos fluxos de provisionamento de organização e SSO do seu provedor de autenticação.", + }, + }, + refresh: "Atualizar", loading: 'Loading records...', recordCount: "{{count}} registros", recordCountOne: "{{count}} registro", @@ -318,6 +483,11 @@ const pt = { recordEditingTitle: "Edição de registros", }, managedByBadge: { + systemWritable: { + short: "Esquema da plataforma", + title: "Definido pela plataforma, gravável pelo administrador", + body: "O esquema deste objeto é definido pela plataforma, mas as linhas são suas para criar e editar aqui. Quem pode gravar é determinado pela administração delegada e pela segurança em nível de registro, não por este selo.", + }, config: { short: "Config. de admin", title: "Configuração do administrador", @@ -340,6 +510,7 @@ const pt = { }, }, kanban: { + uncategorized: "Sem categoria", addCard: "Adicionar cartão", addColumn: "Adicionar coluna", moveCard: "Mover cartão", @@ -361,12 +532,92 @@ const pt = { }, }, gantt: { + viewMode: { + day: "Dia", + week: "Semana", + month: "Mês", + quarter: "Trimestre", + year: "Ano", + }, + row: { + expand: "Expandir", + collapse: "Recolher", + open: "Abrir detalhes", + }, + aria: { + taskList: "Lista de tarefas", + }, + tooltip: { + days: "d", + }, + menu: { + view: "Ver detalhes", + edit: "Editar em linha", + delete: "Excluir", + addPredecessor: "Adicionar predecessora…", + addSuccessor: "Adicionar sucessora…", + removeDependency: "Remover a dependência", + noCandidates: "Nenhuma tarefa disponível", + searchTasks: "Pesquisar tarefas…", + }, + delete: { + title: "Excluir esta tarefa?", + body: "\"{{title}}\" será removida permanentemente. Esta ação não pode ser desfeita.", + cancel: "Cancelar", + confirm: "Excluir", + deleting: "Excluindo…", + }, + drawer: { + fallbackTitle: "Detalhes da tarefa", + }, + linkType: { + fs: "Término → Início", + ss: "Início → Início", + ff: "Término → Término", + sf: "Início → Término", + }, + conflict: { + title: "Conflito de agendamento", + body: "Esta movimentação conflita com as restrições de dependência. Reagendar automaticamente {{count}} tarefa(s) afetada(s)?", + confirm: "Reagendar automaticamente", + cancel: "Manter como está", + }, + autoScheduleDlg: { + title: "Agendamento automático", + body: "Adiar {count} tarefa(s) para atender aos vínculos de dependência?", + skipped: "{count} tarefa(s) bloqueada(s) também violam os vínculos e foram ignoradas.", + confirm: "Aplicar", + cancel: "Cancelar", + none: "Todas as dependências foram atendidas — nada a reagendar.", + }, + resource: { + header: "Recurso", + peak: "Pico", + over: "sobrecarregado", + empty: "Nenhuma tarefa para alocar.", + }, + readOnly: "Somente leitura", + readOnlyHint: "A edição está desativada nesta visão.", + lockedHint: "Sem permissão de edição", + writeFailed: "Falha ao salvar — a alteração foi revertida", column: { taskName: "Nome da tarefa", start: "Início", end: "Fim", }, toolbar: { + thisWeek: "Esta semana", + thisMonth: "Este mês", + viewMode: "Granularidade da linha do tempo", + enterFullscreen: "Entrar em tela cheia", + exitFullscreen: "Sair da tela cheia", + criticalPath: "Destacar o caminho crítico", + autoSchedule: "Agendar dependências automaticamente", + exportPng: "Exportar como PNG", + exportPdf: "Exportar PDF", + saveLayout: "Salvar o layout", + undo: "Desfazer", + redo: "Refazer", prevPeriod: "Período anterior", nextPeriod: "Próximo período", zoomIn: "Ampliar", @@ -412,6 +663,11 @@ const pt = { redo: 'Redo', }, detail: { + editInlineHint: "Clique duas vezes para editar", + systemActor: "Sistema", + unknownUser: "Desconhecido", + created: "Criado", + updated: "Atualizado", back: "Voltar", edit: "Editar", editInline: "Editar", @@ -624,6 +880,35 @@ const pt = { resetZoom: "Redefinir zoom", }, dashboard: { + noRows: "Sem linhas", + pickMeasures: "Escolha medidas (valores) para este widget de dataset.", + datasetUnsupported: "Esta fonte de dados não oferece suporte a consultas de dataset.", + details: "Detalhes", + exportCsv: "Exportar CSV", + openInList: "Abrir na lista", + filters: { + label: "Filtros do painel", + dateRange: "Período", + allTime: "Todo o período", + custom: "Personalizado…", + all: "Tudo", + reset: "Redefinir", + range: { + today: "Hoje", + yesterday: "Ontem", + this_week: "Esta semana", + last_week: "Semana passada", + this_month: "Este mês", + last_month: "Mês passado", + this_quarter: "Este trimestre", + last_quarter: "Trimestre passado", + this_year: "Este ano", + last_year: "Ano passado", + last_7_days: "Últimos 7 dias", + last_30_days: "Últimos 30 dias", + last_90_days: "Últimos 90 dias", + }, + }, addWidget: "Adicionar widget", removeWidget: "Remover widget", editLayout: "Editar layout", @@ -1341,7 +1626,28 @@ const pt = { }, }, auth: { + setPassword: { + title: "Definir uma senha de recuperação", + description: "Você entrou por logon único. Defina uma senha local para continuar conseguindo acessar este ambiente diretamente caso o SSO fique indisponível.", + email: "E-mail", + newPassword: "Nova senha", + confirmPassword: "Confirmar a senha", + submit: "Definir a senha", + submitting: "Salvando…", + success: "Senha local definida", + failed: "Não foi possível definir a senha", + passwordsMismatch: "As senhas não coincidem", + noSession: "Sua sessão expirou.", + backToSignIn: "Entrar novamente", + }, login: { + signingIn: "Entrando…", + ssoHandoff: "Continuar para {{target}}", + devAdminHint: { + title: "Instância de desenvolvimento", + body: "Entre com o administrador de desenvolvimento pré-configurado:", + dismiss: "Dispensar", + }, title: "Entre na sua conta", description: "Digite seu e-mail e senha para continuar", emailLabel: "E-mail", @@ -1354,6 +1660,7 @@ const pt = { noAccountText: "Não tem uma conta?", signUpText: "Cadastre-se", errors: { + oauthCallbackFailed: "Não foi possível concluir o logon único — o link de acesso expirou ou já foi utilizado. Tente novamente.", invalidCredentials: "E-mail ou senha inválidos. Por favor, tente novamente.", emailNotVerified: "Por favor, verifique seu e-mail antes de fazer login.", }, @@ -1578,11 +1885,15 @@ const pt = { settings: "Configurações do espaço de trabalho", }, help: { + onThisPage: "Nesta página", + keyboardShortcuts: "Atalhos de teclado", appDocs: 'Documentação deste app', allDocs: 'Toda a documentação', onlineDocs: 'Documentação online', }, sidebar: { + packageManagement: "Gerenciamento de pacotes", + searchNavigation: "Pesquisar na navegação...", settings: "Configurações", help: "Ajuda", helpTooltip: "Ajuda e documentação", @@ -1843,6 +2154,8 @@ const pt = { updateFailed: "Não foi possível atualizar a navegação", }, objectActions: { + resetPackageSetConfirm: "Este conjunto de permissões acompanha um pacote instalado e não pode ser removido. Excluí-lo o redefine para a versão original do pacote e descarta suas personalizações neste ambiente. Continuar?", + resetPackageSetSuccess: "Conjunto de permissões redefinido para a versão original do pacote", deleteSuccess: "{{label}} excluído com sucesso", deleteFailed: "Não foi possível excluir {{label}}", noRecordId: "Nenhum ID de registro fornecido", @@ -2007,6 +2320,7 @@ const pt = { }, }, organizations: { + create: "Criar workspace", mine: "Minhas organizações", title: "Organizações", heading: "Suas organizações", @@ -2020,6 +2334,11 @@ const pt = { noMatches: "Nenhuma organização corresponde à sua pesquisa.", }, notifications: { + regionLabel: "Notificações", + groupCount: "{{count}} notificações", + groupUnread: "{{count}} não lidas", + groupMarkRead: "Marcar como lidas", + groupMarkReadTitle: "Marcar todas deste tipo como lidas", empty: "Sem notificações", markAllRead: "Marcar tudo como lido", viewAll: "Ver todas as notificações", @@ -2222,6 +2541,15 @@ const pt = { }, }, marketplace: { + suggestedBindings: { + promptEveryone: "Este app sugere conceder \"{{set}}\" a todos os usuários autenticados (a posição Everyone).", + promptGuest: "Este app sugere conceder \"{{set}}\" a visitantes não autenticados (a posição Guest).", + confirm: "Conceder", + confirming: "Concedendo…", + dismiss: "Dispensar", + confirmedToast: "\"{{set}}\" agora está concedido a {{anchor}}.", + dismissedToast: "Sugestão de \"{{set}}\" dispensada.", + }, title: "Marketplace de aplicativos", subtitle: "Explore aplicativos aprovados publicados no catálogo ObjectStack. Clique em um aplicativo para ver os detalhes e instalá-lo em um de seus ambientes.", searchPlaceholder: "Pesquisar aplicativos por nome ou ID de manifesto…", @@ -2273,6 +2601,7 @@ const pt = { updateAvailable: 'Atualização disponível', }, action: { + installed: "Instalado", install: "Instalar", reinstall: "Reinstalar", working: "Processando…", @@ -2524,6 +2853,10 @@ const pt = { retry: 'Tentar novamente', }, draftBar: { + messageClean: "Pré-visualização do rascunho — não há alterações não publicadas; tudo aqui já está no ar.", + sampleDataTitle: "Os dados de exemplo aparecem depois que você publicar", + sampleDataBody: "Você está pré-visualizando a estrutura do seu app. Publique para carregar registros de exemplo e colocá-lo no ar.", + publishCta: "Publicar para ver no ar", message: 'Pré-visualização de rascunho — você está vendo alterações não publicadas. Nada entra no ar até você publicar.', publish: 'Publicar', publishing: 'Publicando…', diff --git a/packages/i18n/src/locales/ru.ts b/packages/i18n/src/locales/ru.ts index 29ed37fdec..176b1b56b2 100644 --- a/packages/i18n/src/locales/ru.ts +++ b/packages/i18n/src/locales/ru.ts @@ -1,6 +1,15 @@ const ru = { // objectui#2600 B5 — capability picker scope group headers (labels come from the sys_capability registry). capability: { + label: { + manage_users: "Управление пользователями", + manage_org_users: "Управление пользователями организации", + manage_metadata: "Управление метаданными", + manage_platform_settings: "Управление настройками платформы", + setup_access: "Доступ к настройке", + setup_write: "Запись настроек", + studio_access: "Доступ к Studio", + }, group: { platform: 'Платформа', org: 'Организация', @@ -98,6 +107,10 @@ const ru = { type: "{{field}} должно быть допустимым {{type}}", }, form: { + discardTitle: "Отменить изменения?", + discardMessage: "Есть несохранённые изменения. Если закрыть форму сейчас, правки будут потеряны.", + keepEditing: "Продолжить редактирование", + discard: "Отменить", addItem: "Добавить элемент", removeItem: "Удалить элемент", fieldRequired: "Это поле обязательно", @@ -118,6 +131,19 @@ const ru = { deleteSuccess: "{{object}} удалён", }, fields: { + image: { + upload: "Загрузить изображение", + addMore: "Добавить ещё изображения", + uploading: "Загрузка…", + imageAlt: "Изображение {{index}}", + crop: "Обрезать изображение {{index}}", + remove: "Удалить изображение {{index}}", + enlarge: "Увеличить {{name}}", + preview: "Предпросмотр изображения", + previous: "Предыдущее изображение", + next: "Следующее изображение", + counter: "{{current}} / {{total}}", + }, relativeDate: { overdue: "Просрочено на {{count}} дн.", }, @@ -176,6 +202,36 @@ const ru = { delete: "Удалить", }, grid: { + bulkSelected: "Выбрано: {{count}}", + bulkSelectedAllMatches: "Выбрано: {{count}} (все совпадения)", + bulkClear: "Снять выделение", + bulkAllOnPage: "Выбраны все {{count}} на этой странице.", + bulkSelectAllMatching: "Выбрать все совпадения ({{count}})", + bulkAllMatchingSelected: "Выбраны все подходящие записи ({{count}}).", + bulk: { + confirmDefault: "Будет применено к записям: {{count}}.", + overLimit: "Выбрано ({{count}}) больше лимита действия ({{limit}}). Уменьшите выбор, чтобы продолжить.", + affectedRecords: "Затрагиваемые записи ({{count}}):", + rowFallback: "Строка {{index}}", + andMore: "… и ещё {{count}}", + processed: "Обработано {{count}} / {{total}}", + processedFailed: " · с ошибкой: {{count}}", + undonePrefix: "Отменено — ", + succeeded: "Успешно {{count}} / {{total}}", + resultFailed: " · Ошибок {{count}}", + retry: "Повторить", + downloadErrorCsv: "Скачать CSV с ошибками", + cancel: "Отмена", + back: "Назад", + next: "Далее", + run: "Выполнить", + running: "Выполняется…", + undo: "Отменить", + undoing: "Отмена…", + done: "Готово", + selectPlaceholder: "Выберите…", + loading: "Загрузка…", + }, actions: "Действия", edit: "Редактировать", delete: "Удалить", @@ -201,6 +257,85 @@ const ru = { densityCycleShortHint: "Нажмите для изменения", }, import: { + downloadTemplate: "Скачать шаблон", + downloadTemplateHint: "Получите CSV с нужными столбцами (обязательные поля отмечены *).", + templateFileName: "{{object}}-import-template", + autoMatched: "Сопоставлено автоматически", + autoMatchedSummary: "Автоматически сопоставлено столбцов: {{count}} — проверьте и скорректируйте ниже.", + confidence: { + high: "Высокая точность", + medium: "Средняя точность", + low: "Низкая точность", + }, + asyncQueued: "В очереди — подготовка импорта…", + asyncProcessing: "Импортировано {{processed}} из {{total}} строк… {{progress}} %", + asyncLargeHint: "Файл большой, поэтому импорт выполнится в фоновом режиме.", + largeSampleNotice: "Предпросмотр первых {{shown}} из {{total}} строк.", + cancelImport: "Отменить импорт", + importCancelled: "Импорт отменён", + resultsTruncated: "Показаны результаты первых {{count}} строк (из {{total}}).", + createdCount: "Создано: {{count}}", + updatedCount: "Обновлено: {{count}}", + downloadFailed: "Скачать строки с ошибками", + options: "Параметры импорта", + writeMode: "Если строка совпадает с существующей записью", + writeModeOpt: { + insert: "Всегда создавать новую", + update: "Обновлять существующую (пропускать без совпадения)", + upsert: "Обновлять при совпадении, иначе создавать", + }, + matchFields: "Сопоставлять по", + matchFieldsPlaceholder: "Выберите поля для сопоставления…", + matchFieldsHint: "Строки сопоставляются с существующими записями по этим полям.", + needMatchFields: "Выберите хотя бы одно поле для сопоставления.", + optCreateOptions: "Сохранять неизвестные значения списка", + optRunAutomations: "Выполнять автоматизации и триггеры", + optTreatHistorical: "Импортировать как исторические данные", + optTreatHistoricalHint: "(импортирует завершённые записи как есть — пропускает проверки конечного автомата и сохраняет исходные отметки времени и автора вместо текущих)", + optSkipBlankKey: "Пропускать строки с пустым значением сопоставления", + optBackground: "Импортировать в фоновом режиме", + optBackgroundHint: "(выполняется как отменяемая задача)", + validate: "Проверить данные", + validating: "Проверка…", + validateHint: "Проверить каждую строку на сервере перед импортом.", + validatePassed: "Все строки ({{ok}}) корректны.", + validateFailed: "Корректных: {{ok}}, с ошибками: {{errors}}.", + errorRowPrefix: "Строка {{row}}: ", + referenceNotFound: "Нет записи, соответствующей «{{value}}»", + referenceAmbiguous: "«{{value}}» соответствует нескольким записям — используйте уникальное значение или идентификатор записи", + invalidBoolean: "«{{value}}» не является допустимым значением true/false", + invalidNumber: "«{{value}}» не является допустимым числом", + invalidDate: "«{{value}}» не является допустимой датой", + invalidOption: "«{{value}}» не входит в число допустимых вариантов", + requiredValue: "Это поле обязательно", + matchAmbiguous: "Соответствует нескольким существующим записям — используйте уникальное значение или идентификатор записи", + history: "История", + historyBack: "Назад к импорту", + historyDescription: "Недавние импорты для этого объекта.", + historyHint: "Фоновые задачи импорта, сначала новые.", + historyRefresh: "Обновить", + historyLoading: "Загрузка…", + historyEmpty: "Импортов пока нет.", + historyUnsupported: "Для этого источника данных история импорта недоступна.", + historyColStatus: "Статус", + historyColRows: "Строк", + historyColResult: "Результат", + historyColTime: "Когда", + errorCount: "Ошибок: {{count}}", + undoImport: "Отменить импорт", + undoing: "Отмена…", + undoConfirm: "Отменить этот импорт? Созданные им записи будут удалены, а обновлённые — восстановлены к прежним значениям.", + reverted: "Отменён", + jobStatus: { + pending: "Ожидает", + running: "Выполняется", + succeeded: "Успешно", + failed: "Ошибка", + cancelled: "Отменено", + }, + legacyReferenceBlocked: "Импорт заблокирован: {{fields}} — поля связи, которым нужен серверный маршрут импорта для преобразования имён в идентификаторы записей, а это подключение его не поддерживает. Импорт в виде обычного текста повредит данные. Обновите бэкенд/клиент либо снимите сопоставление этих столбцов и импортируйте их отдельно.", + missingRequiredHint: "Продолжение невозможно — не сопоставлены обязательные поля: {{fields}}. Добавьте соответствующий столбец в файл или вернитесь и загрузите файл, где он есть.", + legacyFallbackNotice: "Импортировано через режим совместимости: это подключение не поддерживает серверный маршрут импорта, поэтому значения сохранены как текст без приведения типов на сервере. Обновите бэкенд/клиент для полноценного импорта (приведение типов и поиск связей).", title: "Импорт {{object}}", notAllowed: "Этот объект не открыт для импорта.", stepUpload: "Загрузка", @@ -278,6 +413,36 @@ const ru = { moreEvents: "+{{count}} ещё", }, list: { + firstRunTitle: "Здесь пока пусто", + firstRunMessage: "Создайте первую запись, чтобы начать.", + noMatches: "Подходящих записей нет", + noMatchesMessage: "Ни одна запись не соответствует текущим фильтрам или поиску. Измените или сбросьте их.", + loadErrorTitle: "Не удалось загрузить записи", + loadErrorMessage: "При загрузке данных произошла ошибка. Проверьте соединение и попробуйте снова.", + retry: "Повторить", + managedBy: { + system: { + title: "Здесь пока пусто", + message: "Записи появляются автоматически при выполнении исходного действия (например, «Отправить на согласование», «Поделиться», «Пригласить»). Запустите одно из них на исходной записи, чтобы создать строку.", + }, + appendOnly: { + title: "События не зафиксированы", + message: "Это неизменяемый журнал аудита. Строки записывает платформа при наступлении событий — историю можно выгрузить, но создавать записи отсюда нельзя.", + }, + betterAuth: { + title: "Записей identity нет", + message: "Эти записи создаёт поставщик аутентификации — при входе, подготовке учётных записей и в процессах безопасности, — а не добавляются здесь вручную.", + }, + betterAuthUser: { + title: "Пользователей пока нет", + message: "Учётные записи создаёт поставщик аутентификации, а не эта страница. Пригласите коллег в организацию — они появятся автоматически при первом входе (JIT-подготовка через SSO). Конечные пользователи приложения добавляются, когда регистрируются в нём сами.", + }, + betterAuthTeam: { + title: "Команд пока нет", + message: "Команды объединяют участников внутри организации. Создайте команду кнопкой «Создать команду» либо они появятся через процессы подготовки организаций и SSO вашего поставщика аутентификации.", + }, + }, + refresh: "Обновить", loading: 'Loading records...', recordCount: "{{count}} записей", recordCountOne: "{{count}} запись", @@ -318,6 +483,11 @@ const ru = { recordEditingTitle: "Редактирование записей", }, managedByBadge: { + systemWritable: { + short: "Схема платформы", + title: "Определено платформой, доступно администратору для записи", + body: "Схему этого объекта задаёт платформа, но строки вы создаёте и редактируете здесь сами. Кто именно может писать, определяют делегированное администрирование и безопасность на уровне записей, а не этот значок.", + }, config: { short: "Конфигурация", title: "Конфигурация администратора", @@ -340,6 +510,7 @@ const ru = { }, }, kanban: { + uncategorized: "Без категории", addCard: "Добавить карточку", addColumn: "Добавить колонку", moveCard: "Переместить карточку", @@ -361,12 +532,92 @@ const ru = { }, }, gantt: { + viewMode: { + day: "День", + week: "Неделя", + month: "Месяц", + quarter: "Квартал", + year: "Год", + }, + row: { + expand: "Развернуть", + collapse: "Свернуть", + open: "Открыть подробности", + }, + aria: { + taskList: "Список задач", + }, + tooltip: { + days: "дн", + }, + menu: { + view: "Показать подробности", + edit: "Редактировать на месте", + delete: "Удалить", + addPredecessor: "Добавить предшественника…", + addSuccessor: "Добавить последователя…", + removeDependency: "Удалить зависимость", + noCandidates: "Нет доступных задач", + searchTasks: "Поиск задач…", + }, + delete: { + title: "Удалить эту задачу?", + body: "«{{title}}» будет удалена безвозвратно. Это действие нельзя отменить.", + cancel: "Отмена", + confirm: "Удалить", + deleting: "Удаление…", + }, + drawer: { + fallbackTitle: "Подробности задачи", + }, + linkType: { + fs: "Окончание → Начало", + ss: "Начало → Начало", + ff: "Окончание → Окончание", + sf: "Начало → Окончание", + }, + conflict: { + title: "Конфликт расписания", + body: "Это перемещение противоречит ограничениям зависимостей. Автоматически перепланировать затронутые задачи ({{count}})?", + confirm: "Перепланировать автоматически", + cancel: "Оставить как есть", + }, + autoScheduleDlg: { + title: "Автопланирование", + body: "Сдвинуть {count} задач(и) на более поздний срок, чтобы соблюсти связи зависимостей?", + skipped: "{count} заблокированных задач(и) также нарушают связи и были пропущены.", + confirm: "Применить", + cancel: "Отмена", + none: "Все зависимости соблюдены — перепланировать нечего.", + }, + resource: { + header: "Ресурс", + peak: "Пик", + over: "перегружен", + empty: "Нет задач для распределения.", + }, + readOnly: "Только чтение", + readOnlyHint: "Редактирование в этом представлении отключено.", + lockedHint: "Нет прав на редактирование", + writeFailed: "Не удалось сохранить — изменение отменено", column: { taskName: "Название задачи", start: "Начало", end: "Конец", }, toolbar: { + thisWeek: "Эта неделя", + thisMonth: "Этот месяц", + viewMode: "Детализация шкалы времени", + enterFullscreen: "Полноэкранный режим", + exitFullscreen: "Выйти из полноэкранного режима", + criticalPath: "Выделить критический путь", + autoSchedule: "Автоматически планировать зависимости", + exportPng: "Экспортировать в PNG", + exportPdf: "Экспортировать в PDF", + saveLayout: "Сохранить компоновку", + undo: "Отменить", + redo: "Повторить", prevPeriod: "Предыдущий период", nextPeriod: "Следующий период", zoomIn: "Увеличить", @@ -412,6 +663,11 @@ const ru = { redo: 'Redo', }, detail: { + editInlineHint: "Дважды щёлкните для редактирования", + systemActor: "Система", + unknownUser: "Неизвестно", + created: "Создано", + updated: "Обновлено", back: "Назад", edit: "Редактировать", editInline: "Редактировать", @@ -624,6 +880,35 @@ const ru = { resetZoom: "Сбросить масштаб", }, dashboard: { + noRows: "Нет строк", + pickMeasures: "Выберите меры (значения) для этого виджета набора данных.", + datasetUnsupported: "Этот источник данных не поддерживает запросы к наборам данных.", + details: "Подробности", + exportCsv: "Экспорт в CSV", + openInList: "Открыть в списке", + filters: { + label: "Фильтры дашборда", + dateRange: "Период", + allTime: "За всё время", + custom: "Произвольный…", + all: "Все", + reset: "Сбросить", + range: { + today: "Сегодня", + yesterday: "Вчера", + this_week: "Эта неделя", + last_week: "Прошлая неделя", + this_month: "Этот месяц", + last_month: "Прошлый месяц", + this_quarter: "Этот квартал", + last_quarter: "Прошлый квартал", + this_year: "Этот год", + last_year: "Прошлый год", + last_7_days: "Последние 7 дней", + last_30_days: "Последние 30 дней", + last_90_days: "Последние 90 дней", + }, + }, addWidget: "Добавить виджет", removeWidget: "Удалить виджет", editLayout: "Редактировать макет", @@ -1341,7 +1626,28 @@ const ru = { }, }, auth: { + setPassword: { + title: "Задать резервный пароль", + description: "Вы вошли через единый вход. Задайте локальный пароль, чтобы входить в эту среду напрямую, если SSO станет недоступен.", + email: "Электронная почта", + newPassword: "Новый пароль", + confirmPassword: "Подтвердите пароль", + submit: "Задать пароль", + submitting: "Сохранение…", + success: "Локальный пароль задан", + failed: "Не удалось задать пароль", + passwordsMismatch: "Пароли не совпадают", + noSession: "Срок действия сессии истёк.", + backToSignIn: "Войти снова", + }, login: { + signingIn: "Выполняется вход…", + ssoHandoff: "Перейти к {{target}}", + devAdminHint: { + title: "Среда разработки", + body: "Войдите под предустановленным администратором для разработки:", + dismiss: "Закрыть", + }, title: "Войдите в свой аккаунт", description: "Введите электронную почту и пароль для продолжения", emailLabel: "Электронная почта", @@ -1354,6 +1660,7 @@ const ru = { noAccountText: "Нет аккаунта?", signUpText: "Зарегистрироваться", errors: { + oauthCallbackFailed: "Не удалось завершить единый вход — ссылка для входа истекла или уже была использована. Попробуйте ещё раз.", invalidCredentials: "Неверный email или пароль. Попробуйте ещё раз.", emailNotVerified: "Пожалуйста, подтвердите email перед входом.", }, @@ -1578,11 +1885,15 @@ const ru = { settings: "Настройки рабочего пространства", }, help: { + onThisPage: "На этой странице", + keyboardShortcuts: "Сочетания клавиш", appDocs: 'Документация приложения', allDocs: 'Вся документация', onlineDocs: 'Онлайн-документация', }, sidebar: { + packageManagement: "Управление пакетами", + searchNavigation: "Поиск по навигации...", settings: "Настройки", help: "Помощь", helpTooltip: "Помощь и документация", @@ -1843,6 +2154,8 @@ const ru = { updateFailed: "Не удалось обновить навигацию", }, objectActions: { + resetPackageSetConfirm: "Этот набор разрешений поставляется с установленным пакетом и не может быть удалён. Удаление сбросит его к исходному состоянию из пакета и отменит ваши настройки в этой среде. Продолжить?", + resetPackageSetSuccess: "Набор разрешений сброшен к исходному состоянию из пакета", deleteSuccess: "{{label}} успешно удалён", deleteFailed: "Не удалось удалить {{label}}", noRecordId: "ID записи не указан", @@ -2007,6 +2320,7 @@ const ru = { }, }, organizations: { + create: "Создать рабочее пространство", mine: "Мои организации", title: "Организации", heading: "Ваши организации", @@ -2020,6 +2334,11 @@ const ru = { noMatches: "Нет организаций, соответствующих запросу.", }, notifications: { + regionLabel: "Уведомления", + groupCount: "Уведомлений: {{count}}", + groupUnread: "Непрочитанных: {{count}}", + groupMarkRead: "Отметить прочитанными", + groupMarkReadTitle: "Отметить прочитанными все этого типа", empty: "Нет уведомлений", markAllRead: "Отметить все как прочитанные", viewAll: "Показать все уведомления", @@ -2222,6 +2541,15 @@ const ru = { }, }, marketplace: { + suggestedBindings: { + promptEveryone: "Приложение предлагает предоставить «{{set}}» всем вошедшим пользователям (позиция Everyone).", + promptGuest: "Приложение предлагает предоставить «{{set}}» неаутентифицированным посетителям (позиция Guest).", + confirm: "Предоставить", + confirming: "Предоставление…", + dismiss: "Закрыть", + confirmedToast: "«{{set}}» теперь предоставлен: {{anchor}}.", + dismissedToast: "Предложение по «{{set}}» закрыто.", + }, title: "Маркетплейс приложений", subtitle: "Просматривайте одобренные приложения из каталога ObjectStack. Нажмите на приложение, чтобы увидеть подробности и установить его.", searchPlaceholder: "Поиск приложений по имени или ID манифеста…", @@ -2273,6 +2601,7 @@ const ru = { updateAvailable: 'Доступно обновление', }, action: { + installed: "Установлено", install: "Установить", reinstall: "Переустановить", working: "Обработка…", @@ -2524,6 +2853,10 @@ const ru = { retry: 'Повторить', }, draftBar: { + messageClean: "Предпросмотр черновика — неопубликованных изменений нет; всё здесь уже действует.", + sampleDataTitle: "Примеры данных появятся после публикации", + sampleDataBody: "Вы просматриваете структуру приложения. Опубликуйте его, чтобы загрузить примеры записей и запустить его.", + publishCta: "Опубликовать и посмотреть", message: 'Предпросмотр черновика — вы видите неопубликованные изменения. Ничего не вступит в силу до публикации.', publish: 'Опубликовать', publishing: 'Публикация…',