From 95296913ac77affc637084f4f028ef7bbaf0e405 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Wed, 22 Jul 2026 22:05:09 +0800 Subject: [PATCH] feat(browser): YAML-first translations Translators work in YAML, not JS literals. Built-in i18n table moves from a 557-line TS literal in ui.ts to six per-locale YAML files (src/i18n/strings/.yaml) parsed at build time via Vite ?raw + the yaml package. Consumer overrides gain a symmetric loadYamlTranslations() helper. API surface unchanged: inline uiStrings literals still work. Consumers who want file-based translations can adopt the new helper without touching existing config. README rewritten to make YAML the primary path. load-translations.spec.ts covers parsing, unicode, escaping, and the freeze guarantee. 183 unit + 51 e2e green. --- .changeset/yaml-first-translations.md | 20 + packages/browser/README.md | 44 +- packages/browser/src/i18n/index.ts | 1 + .../src/i18n/load-translations.spec.ts | 57 ++ .../browser/src/i18n/load-translations.ts | 46 ++ packages/browser/src/i18n/strings/ara.yaml | 71 +++ packages/browser/src/i18n/strings/eng.yaml | 113 ++++ packages/browser/src/i18n/strings/fra.yaml | 98 +++ packages/browser/src/i18n/strings/rus.yaml | 71 +++ packages/browser/src/i18n/strings/spa.yaml | 71 +++ packages/browser/src/i18n/strings/zho.yaml | 71 +++ packages/browser/src/i18n/ui.ts | 563 +----------------- packages/browser/src/virtual.d.ts | 10 + 13 files changed, 674 insertions(+), 562 deletions(-) create mode 100644 .changeset/yaml-first-translations.md create mode 100644 packages/browser/src/i18n/load-translations.spec.ts create mode 100644 packages/browser/src/i18n/load-translations.ts create mode 100644 packages/browser/src/i18n/strings/ara.yaml create mode 100644 packages/browser/src/i18n/strings/eng.yaml create mode 100644 packages/browser/src/i18n/strings/fra.yaml create mode 100644 packages/browser/src/i18n/strings/rus.yaml create mode 100644 packages/browser/src/i18n/strings/spa.yaml create mode 100644 packages/browser/src/i18n/strings/zho.yaml diff --git a/.changeset/yaml-first-translations.md b/.changeset/yaml-first-translations.md new file mode 100644 index 0000000..f784b84 --- /dev/null +++ b/.changeset/yaml-first-translations.md @@ -0,0 +1,20 @@ +--- +'@edoxen/browser': minor +--- + +YAML-first translations. + +Translators work in YAML, not JS literals. The runtime i18n table now ships as six per-locale YAML files (`src/i18n/strings/.yaml`) parsed at build time via Vite's `?raw` import + the `yaml` package. Consumer overrides gain a symmetric loader. + +### Added +- `loadYamlTranslations(yamlString)` helper, exported from `@edoxen/browser`. Parses a YAML mapping of `'key': 'value'` pairs into a frozen `UiStrings` record. Throws clearly on non-mapping input, ignores non-string values, returns `{}` for empty input. +- `src/i18n/load-translations.spec.ts` — six tests covering happy path, unicode/quote escaping, empty input, malformed input, freeze guarantee. + +### Changed +- `src/i18n/strings/{eng,fra,zho,spa,ara,rus}.yaml` — built-in locale tables extracted from the TS literal that used to live inline in `ui.ts`. Same keys, same values, just YAML. Translators can read these as worked examples. +- `src/i18n/ui.ts` — `STRINGS` const now references `BUILTIN_STRINGS` (loaded from YAML); the 557-line inline table is gone. `loadYamlTranslations` is re-exported for consumers. +- `src/virtual.d.ts` — declares `*.yaml?raw` and `*.yml?raw` modules so TypeScript accepts Vite's raw YAML imports. +- README §Custom translations rewritten to make YAML the primary path: Option A (file-based, recommended) loads via `readFileSync` + `loadYamlTranslations`; Option B (inline literals) still works for small overrides. Includes YAML escaping rules (`'` doubling inside single-quoted scalars, or backslash escapes in double-quoted). + +### Migration notes for consumers +No breaking changes — inline `uiStrings: { fra: {...} }` literals still work. Consumers who want file-based translations can adopt the new helper without touching existing config. Built-in English + French values are unchanged. diff --git a/packages/browser/README.md b/packages/browser/README.md index a400b20..77955e3 100644 --- a/packages/browser/README.md +++ b/packages/browser/README.md @@ -234,11 +234,31 @@ tabs when a decision carries multiple spellings. ### Custom translations -Provide translations via `uiStrings`; missing keys fall back to -English. See [`docs/i18n-keys.yaml`](./docs/i18n-keys.yaml) for the full -list of translatable strings — including section titles such as -`section.adoptedDecisions` ("Resolutions"), which is how you rename the -adopted-decisions section on meeting pages. +Translations live in **YAML files** — one per locale. Translators work +in YAML, not JS literals. Two ways to load them: + +**Option A — read the file in your `edoxen.config.ts` (recommended).** +Reference paths you control; the build picks up changes on reload: + +```ts +// edoxen.config.ts +import { readFileSync } from 'node:fs' +import { loadYamlTranslations } from '@edoxen/browser' + +const deu = loadYamlTranslations(readFileSync('./translations/deu.yaml', 'utf8')) +const zho = loadYamlTranslations(readFileSync('./translations/zho.yaml', 'utf8')) + +export default { + // … + uiStrings: { + deu, + zho, + }, +} +``` + +**Option B — inline (still works for small overrides).** Use JS object +literals directly when you only need to tweak a handful of strings: ```ts uiStrings: { @@ -250,6 +270,20 @@ uiStrings: { } ``` +Missing keys fall back to English automatically. See +[`docs/i18n-keys.yaml`](./docs/i18n-keys.yaml) for the **translation +template** — copy the block for your locale, fill in values, save as +`.yaml`, point `uiStrings` at it. Built-in English + French +ship in [`src/i18n/strings/`](./packages/browser/src/i18n/strings/) as +YAML — translators can read those as worked examples. + +YAML escaping rules worth knowing: + +- Inside single-quoted scalars, escape a literal `'` by doubling it: + `'L''archive'` +- Or use double-quoted scalars with backslash escapes: `"L'archive"` +- Unicode is fine in either form: `'Plénière'`, `'全体会议'` + ### Terminology — renaming "decisions" and "meetings" Committees call their records different things (TC 184/SC 4 adopts diff --git a/packages/browser/src/i18n/index.ts b/packages/browser/src/i18n/index.ts index 1345f0f..6e4cb88 100644 --- a/packages/browser/src/i18n/index.ts +++ b/packages/browser/src/i18n/index.ts @@ -88,6 +88,7 @@ export { availableUiLocales, applyTerminology, meetingTypeLabel, + loadYamlTranslations, DEFAULT_TERMINOLOGY, SUPPORTED_UI_LOCALES, LOCALE_LABELS, diff --git a/packages/browser/src/i18n/load-translations.spec.ts b/packages/browser/src/i18n/load-translations.spec.ts new file mode 100644 index 0000000..cb429b1 --- /dev/null +++ b/packages/browser/src/i18n/load-translations.spec.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest' + +import { loadYamlTranslations } from './load-translations.js' + +describe('loadYamlTranslations', () => { + it('parses a flat key:value YAML mapping', () => { + const yaml = ` + 'nav.home': Home + 'nav.about': 'About' + ` + const out = loadYamlTranslations(yaml) + expect(out).toEqual({ + 'nav.home': 'Home', + 'nav.about': 'About', + }) + }) + + it('preserves unicode, single quotes, and special characters', () => { + // YAML single-quote escaping: literal ' inside a single-quoted scalar + // must be doubled (''). Double-quoted scalars can use either. + const yaml = ` + 'meeting.type.plenary': 'Plénière' + 'phrase': 'L''archive' + 'greeting': '今日は — welcome' + ` + const out = loadYamlTranslations(yaml) + expect(out['meeting.type.plenary']).toBe('Plénière') + expect(out['phrase']).toBe("L'archive") + expect(out['greeting']).toBe('今日は — welcome') + }) + + it('returns an empty record for empty input', () => { + expect(loadYamlTranslations('')).toEqual({}) + expect(loadYamlTranslations('---')).toEqual({}) + }) + + it('ignores non-string values (arrays, objects, numbers)', () => { + const yaml = ` + 'valid': 'ok' + 'nested': + key: value + 'list': [a, b] + 'number': 42 + ` + const out = loadYamlTranslations(yaml) + expect(out).toEqual({ valid: 'ok' }) + }) + + it('throws on top-level non-mapping YAML', () => { + expect(() => loadYamlTranslations('- item1\n- item2')).toThrow(/mapping/) + }) + + it('result is frozen (translators get a read-only view)', () => { + const out = loadYamlTranslations("'k': 'v'") + expect(Object.isFrozen(out)).toBe(true) + }) +}) diff --git a/packages/browser/src/i18n/load-translations.ts b/packages/browser/src/i18n/load-translations.ts new file mode 100644 index 0000000..84994ba --- /dev/null +++ b/packages/browser/src/i18n/load-translations.ts @@ -0,0 +1,46 @@ +// YAML-first i18n loader. +// +// Translators prefer YAML over JS literals — flat key:value files, one +// per locale. This module: +// * Parses a YAML string into a UiStrings record. +// * Loads built-in locale files from ./strings/.yaml at build +// time (Vite ?raw imports + parse). +// +// Consumers can load their own YAML files the same way via +// `loadYamlTranslations(content)` in their edoxen.config.ts. + +import { parse } from 'yaml' + +import engStrings from './strings/eng.yaml?raw' +import fraStrings from './strings/fra.yaml?raw' +import zhoStrings from './strings/zho.yaml?raw' +import spaStrings from './strings/spa.yaml?raw' +import araStrings from './strings/ara.yaml?raw' +import rusStrings from './strings/rus.yaml?raw' + +export type UiStrings = Readonly> + +/** Parse a YAML string of `'key': 'value'` pairs into a UiStrings record. */ +export function loadYamlTranslations(yaml: string): UiStrings { + const parsed: unknown = parse(yaml) + if (parsed == null) return {} + if (typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new Error(`Expected a YAML mapping of key: value pairs, got ${typeof parsed}`) + } + const out: Record = {} + for (const [key, value] of Object.entries(parsed as Record)) { + if (typeof value === 'string') out[key] = value + } + return Object.freeze(out) +} + +// Built-in strings — kept private; consumers use the `t()` and +// `meetingTypeLabel()` helpers rather than reading the table directly. +export const BUILTIN_STRINGS: Readonly> = Object.freeze({ + eng: loadYamlTranslations(engStrings), + fra: loadYamlTranslations(fraStrings), + zho: loadYamlTranslations(zhoStrings), + spa: loadYamlTranslations(spaStrings), + ara: loadYamlTranslations(araStrings), + rus: loadYamlTranslations(rusStrings), +}) diff --git a/packages/browser/src/i18n/strings/ara.yaml b/packages/browser/src/i18n/strings/ara.yaml new file mode 100644 index 0000000..ab23ba2 --- /dev/null +++ b/packages/browser/src/i18n/strings/ara.yaml @@ -0,0 +1,71 @@ +'nav.home': 'الرئيسية' +'nav.decisions': 'القرارات' +'nav.meetings': 'الاجتماعات' +'nav.about': 'حول' +'page.home.heroLabel': 'أرشيف القرارات' +'page.home.stats.decisions': 'القرارات المسجلة' +'page.home.stats.meetings': 'الاجتماعات الموثقة' +'page.home.stats.recent': 'الأحدث' +'page.home.latestDecisions': 'أحدث القرارات' +'page.home.viewAll': 'عرض الكل' +'page.home.recentMeetings': 'الاجتماعات الأخيرة' +'label.body': 'الهيئة' +'label.kind': 'النوع' +'label.date': 'التاريخ' +'label.urn': 'URN' +'label.venue': 'المكان' +'label.adopted': 'اعتُمدت' +'label.effective': 'سارٍ' +'label.meeting': 'الاجتماع' +'label.acclamation': 'بالتصفيق' +'section.when': 'متى' +'section.venue': 'المكان' +'section.officers': 'المسؤولون' +'section.schedule': 'الجدول' +'section.agenda': 'جدول الأعمال' +'section.deadlines': 'المواعيد النهائية' +'section.minutes': 'محاضر الاجتماع' +'section.subject': 'الموضوع' +'section.considering': 'إذ يراعي' +'section.considerations': 'الاعتبارات' +'section.actions': 'الإجراءات' +'section.approvals': 'الموافقات' +'section.dates': 'التواريخ' +'section.referenceDocs': 'الوثائق المرجعية' +'section.adoptedAt': 'اعتُمد في' +'section.adoptedDecisions': 'القرارات' +'section.sourceDocs': 'الوثائق المصدر' +'section.declarations': 'الإعلانات' +'section.committee': 'اللجنة' +'section.hosts': 'المضيفون' +'section.note': 'ملاحظة' +'section.categories': 'الفئات' +'section.related': 'قرارات ذات صلة' +'section.overview': 'نظرة عامة' +'section.identifiers': 'المعرفات' +'page.home.stats.span': 'نطاق السنوات' +'label.scheduled': 'مجدولة' +'label.occurred': 'عُقدت' +'decisions.empty': 'لا توجد قرارات.' +'meetings.empty': 'لا توجد اجتماعات.' +'search.empty': 'لم يتم العثور على نتائج.' +'search.placeholder': 'بحث…' +'search.allBodies': 'جميع الهيئات' +'search.allKinds': 'جميع الأنواع' +'search.ariaLabel': 'البحث في القرارات' +'search.ariaLabelMeetings': 'البحث في الاجتماعات' +'search.dateFrom': 'من' +'search.dateTo': 'إلى' +'search.showMore': 'عرض المزيد' +'nav.prev': '→ السابق' +'nav.next': 'التالي ←' +'decade.browseLabel': 'تصفح حسب العقد' +'about.title': 'حول' +'about.format': 'صيغة Edoxen' +'about.formatBody': 'يعرض هذا الموقع بيانات الاجتماعات والقرارات باستخدام نموذج معلومات Edoxen — مخطط YAML للسجلات الرسمية لهيئات المعايير.' +'about.using': 'استخدام الموقع' +'about.usingDecisions': 'القرارات — تصفح أرشيف القرارات.' +'about.usingMeetings': 'الاجتماعات — شاهد الاجتماعات وجداول أعمالها ومحاضرها وقراراتها المعتمدة.' +'about.usingUrns': 'URN — لكل كيان URN مستقر للاستشهاد.' +'about.stats.decisions': 'القرارات' +'about.stats.meetings': 'الاجتماعات' diff --git a/packages/browser/src/i18n/strings/eng.yaml b/packages/browser/src/i18n/strings/eng.yaml new file mode 100644 index 0000000..529c47e --- /dev/null +++ b/packages/browser/src/i18n/strings/eng.yaml @@ -0,0 +1,113 @@ +'nav.home': 'Home' +'nav.decisions': 'Resolutions' +'nav.meetings': 'Meetings' +'nav.about': 'About' +'page.home.heroLabel': 'Resolutions Archive' +'page.home.stats.decisions': 'Decisions on record' +'page.home.stats.meetings': 'Meetings documented' +'page.home.stats.recent': 'Most recent' +'page.home.latestDecisions': 'Latest Decisions' +'page.home.viewAll': 'View all' +'page.home.recentMeetings': 'Recent Meetings' +'label.body': 'Body' +'label.kind': 'Kind' +'label.date': 'Date' +'label.urn': 'URN' +'label.venue': 'Venue' +'label.adopted': 'Adopted' +'label.effective': 'Effective' +'label.meeting': 'Meeting' +'label.acclamation': 'Acclamation' +'section.when': 'When' +'section.venue': 'Venue' +'section.officers': 'Officers' +'section.schedule': 'Schedule' +'section.agenda': 'Agenda' +'section.deadlines': 'Deadlines' +'section.minutes': 'Minutes' +'section.subject': 'Subject' +'section.considering': 'Considering' +'section.considerations': 'Considerations' +'section.actions': 'Actions' +'section.approvals': 'Approvals' +'section.dates': 'Dates' +'section.referenceDocs': 'Reference documents' +'section.adoptedAt': 'Adopted at' +'section.adoptedDecisions': 'Resolutions' +'section.sourceDocs': 'Source documents' +'section.declarations': 'Declarations' +'section.committee': 'Committee' +'section.hosts': 'Hosts' +'section.note': 'Note' +'section.categories': 'Categories' +'section.related': 'Related decisions' +'section.overview': 'Overview' +'section.identifiers': 'Identifiers' +'page.home.stats.span': 'Year span' +'label.scheduled': 'Scheduled' +'label.occurred': 'Occurred' +'decisions.empty': 'No decisions.' +'meetings.empty': 'No meetings.' +'search.empty': 'No matches found.' +'search.placeholder': 'Search…' +'search.allBodies': 'All bodies' +'search.allKinds': 'All kinds' +'search.ariaLabel': 'Search decisions' +'search.ariaLabelMeetings': 'Search meetings' +'search.dateFrom': 'From' +'search.dateTo': 'To' +'search.showMore': 'Show more' +'search.groupYear': 'Year' +'search.groupLocation': 'Location' +'search.groupType': 'Type' +'search.groupActions': 'Actions' +'search.groupBody': 'Body' +'meeting.virtual': 'Virtual' +'meeting.type.plenary': 'Plenary' +'meeting.type.working_group': 'Working Group' +'meeting.type.task_group': 'Task Group' +'meeting.type.ad_hoc': 'Ad Hoc' +'meeting.type.joint': 'Joint' +'meeting.type.general_assembly': 'General Assembly' +'meeting.type.committee': 'Committee' +'meeting.type.subcommittee': 'Subcommittee' +'meeting.type.conference': 'Conference' +'meeting.type.workshop': 'Workshop' +'meeting.type.seminar': 'Seminar' +'meeting.type.webinar': 'Webinar' +'meeting.type.hearing': 'Hearing' +'meeting.type.markup': 'Markup' +'meeting.type.board_meeting': 'Board Meeting' +'meeting.type.annual_general_meeting': 'Annual General Meeting' +'meeting.type.other': 'Other' +'nav.prev': '← Previous' +'nav.next': 'Next →' +'decade.browseLabel': 'Browse by decade' +'about.title': 'About' +'about.subtitle': 'A digital record of {count} {records} from {committee}, spanning {year} to today.' +'about.format': 'Edoxen format' +'about.formatBody': 'This site renders meeting and decision data using the Edoxen information model — a YAML-based schema for formal proceedings of standards bodies.' +'about.formatBody2': 'Each file carries a metadata block (title, date, source, location) followed by a decisions array. Every decision has a structured identifier (prefix + number), a kind (resolution, order, ruling), per-language localizations, and the considerations, actions and approvals that form its record.' +'about.actionTypesTitle': 'Action Types' +'about.actionTypesBody': 'Every resolution records typed actions — what the committee resolved to do. The type vocabulary makes the archive filterable and analyzable across decades.' +'about.using': 'Using this site' +'about.usingDecisions': 'Decisions — browse the resolutions archive.' +'about.usingMeetings': 'Meetings — see meetings and their agendas, minutes, and adopted decisions.' +'about.usingUrns': 'URNs — every entity has a stable URN for citation.' +'about.stats.decisions': 'Decisions' +'about.stats.meetings': 'Meetings' +'about.urnTitle': 'URN Identifiers' +'about.urnBody': 'Resources in this archive are assigned Uniform Resource Names (URNs) to provide persistent, location-independent identifiers.' +'about.urnDecisionUrns': 'Decision URNs' +'about.urnMeetingUrns': 'Meeting URNs' +'about.urnExample': 'Example' +'about.urnNote': 'Note that per RFC 5141, "documents at or below the Technical Committee level" are not covered by the standard urn:iso:std: namespace. Section 2.6 delegates URN management for TC resources to the Technical Committees themselves.' +'about.lifecycleTitle': 'Resolution Lifecycle' +'about.lifecycleIntro': 'Each decision in the Edoxen model follows a structured lifecycle, captured through three interconnected sections:' +'about.lifecycleConsiderations': 'Considerations' +'about.lifecycleConsiderationsDesc': 'The context and background that led to the decision. Each consideration has a type (e.g., noting, recalling, recognising) and a message explaining what the committee observed or referenced.' +'about.lifecycleActions': 'Actions' +'about.lifecycleActionsDesc': 'The decisions themselves — what the committee resolved to do. Each action carries a semantic type (e.g., requests, approves, appoints) that categorizes the nature of the decision, along with the detailed message.' +'about.lifecycleApprovals': 'Approvals' +'about.lifecycleApprovalsDesc': 'How the decision was formally adopted, including the degree of consensus (e.g., unanimous, consensus) and any relevant notes about the approval process.' +'about.lifecycleOutro': 'A single decision may contain multiple considerations, actions, and approvals — together forming a complete record of the committee\u2019s decision-making process.' diff --git a/packages/browser/src/i18n/strings/fra.yaml b/packages/browser/src/i18n/strings/fra.yaml new file mode 100644 index 0000000..caf352a --- /dev/null +++ b/packages/browser/src/i18n/strings/fra.yaml @@ -0,0 +1,98 @@ +'nav.home': 'Accueil' +'nav.decisions': 'Résolutions' +'nav.meetings': 'Réunions' +'nav.about': 'À propos' +'page.home.heroLabel': 'Archive des résolutions' +'page.home.stats.decisions': 'Décisions enregistrées' +'page.home.stats.meetings': 'Réunions documentées' +'page.home.stats.recent': 'Plus récentes' +'page.home.latestDecisions': 'Dernières décisions' +'page.home.viewAll': 'Voir tout' +'page.home.recentMeetings': 'Réunions récentes' +'label.body': 'Organe' +'label.kind': 'Type' +'label.date': 'Date' +'label.urn': 'URN' +'label.venue': 'Lieu' +'label.adopted': 'Adoptée' +'label.effective': 'En vigueur' +'label.meeting': 'Réunion' +'label.acclamation': 'Acclamation' +'section.when': 'Quand' +'section.venue': 'Lieu' +'section.officers': 'Bureau' +'section.schedule': 'Programme' +'section.agenda': 'Ordre du jour' +'section.deadlines': 'Échéances' +'section.minutes': 'Procès-verbaux' +'section.subject': 'Sujet' +'section.considering': 'Considérant' +'section.considerations': 'Considérations' +'section.actions': 'Actions' +'section.approvals': 'Approbations' +'section.dates': 'Dates' +'section.referenceDocs': 'Documents de référence' +'section.adoptedAt': 'Adoptée à' +'section.adoptedDecisions': 'Résolutions' +'section.sourceDocs': 'Documents sources' +'section.declarations': 'Déclarations' +'section.committee': 'Comité' +'section.hosts': 'Hôtes' +'section.note': 'Note' +'section.categories': 'Catégories' +'section.related': 'Décisions liées' +'section.overview': 'Aperçu' +'section.identifiers': 'Identifiants' +'page.home.stats.span': 'Années couvertes' +'label.scheduled': 'Prévu' +'label.occurred': 'Tenu' +'decisions.empty': 'Aucune décision.' +'meetings.empty': 'Aucune réunion.' +'search.empty': 'Aucun résultat trouvé.' +'search.placeholder': 'Rechercher…' +'search.allBodies': 'Tous les organes' +'search.allKinds': 'Tous les types' +'search.ariaLabel': 'Rechercher dans les décisions' +'search.ariaLabelMeetings': 'Rechercher dans les réunions' +'search.dateFrom': 'De' +'search.dateTo': 'À' +'search.showMore': 'Afficher plus' +'search.groupYear': 'Année' +'search.groupLocation': 'Lieu' +'search.groupType': 'Type' +'search.groupActions': 'Actions' +'search.groupBody': 'Organe' +'meeting.virtual': 'Virtuel' +'meeting.type.plenary': 'Plénière' +'meeting.type.working_group': 'Groupe de travail' +'meeting.type.task_group': 'Groupe de travail ad hoc' +'meeting.type.ad_hoc': 'Ad hoc' +'meeting.type.joint': 'Conjointe' +'meeting.type.general_assembly': 'Assemblée générale' +'meeting.type.committee': 'Comité' +'meeting.type.subcommittee': 'Sous-comité' +'meeting.type.conference': 'Conférence' +'meeting.type.workshop': 'Atelier' +'meeting.type.seminar': 'Séminaire' +'meeting.type.webinar': 'Webinaire' +'meeting.type.hearing': 'Audience' +'meeting.type.markup': 'Markup' +'meeting.type.board_meeting': 'Réunion du conseil' +'meeting.type.annual_general_meeting': 'Assemblée générale annuelle' +'meeting.type.other': 'Autre' +'nav.prev': '← Précédent' +'nav.next': 'Suivant →' +'decade.browseLabel': 'Parcourir par décennie' +'about.title': 'À propos' +'about.subtitle': 'Une archive numérique de {count} {records} — {committee}, de {year} à aujourd''hui.' +'about.format': 'Format Edoxen' +'about.formatBody': 'Ce site affiche les données de réunions et de décisions selon le modèle d''information Edoxen — un schéma YAML pour les actes officiels des organismes de normalisation.' +'about.formatBody2': 'Chaque fichier comporte un bloc de métadonnées (titre, date, source, lieu) suivi d''un tableau de décisions. Chaque décision possède un identifiant structuré (préfixe + numéro), un genre (résolution, ordre, décision), des localisations par langue, ainsi que les considérations, actions et approbations qui composent son dossier.' +'about.actionTypesTitle': 'Types d''actions' +'about.actionTypesBody': 'Chaque résolution enregistre des actions typées — ce que le comité a décidé de faire. Ce vocabulaire rend les archives filtrables et analysables sur des décennies.' +'about.using': 'Utilisation du site' +'about.usingDecisions': 'Décisions — consulter l''archive des résolutions.' +'about.usingMeetings': 'Réunions — voir les réunions et leurs ordres du jour, procès-verbaux et décisions adoptées.' +'about.usingUrns': 'URN — chaque entité possède une URN stable pour la citation.' +'about.stats.decisions': 'Décisions' +'about.stats.meetings': 'Réunions' diff --git a/packages/browser/src/i18n/strings/rus.yaml b/packages/browser/src/i18n/strings/rus.yaml new file mode 100644 index 0000000..51411ec --- /dev/null +++ b/packages/browser/src/i18n/strings/rus.yaml @@ -0,0 +1,71 @@ +'nav.home': 'Главная' +'nav.decisions': 'Резолюции' +'nav.meetings': 'Заседания' +'nav.about': 'О сайте' +'page.home.heroLabel': 'Архив резолюций' +'page.home.stats.decisions': 'Решений в реестре' +'page.home.stats.meetings': 'Заседаний задокументировано' +'page.home.stats.recent': 'Последние' +'page.home.latestDecisions': 'Последние решения' +'page.home.viewAll': 'Показать все' +'page.home.recentMeetings': 'Недавние заседания' +'label.body': 'Орган' +'label.kind': 'Тип' +'label.date': 'Дата' +'label.urn': 'URN' +'label.venue': 'Место' +'label.adopted': 'Принято' +'label.effective': 'Вступило в силу' +'label.meeting': 'Заседание' +'label.acclamation': 'Акламация' +'section.when': 'Когда' +'section.venue': 'Место' +'section.officers': 'Должностные лица' +'section.schedule': 'Программа' +'section.agenda': 'Повестка' +'section.deadlines': 'Сроки' +'section.minutes': 'Протоколы' +'section.subject': 'Предмет' +'section.considering': 'Принимая во внимание' +'section.considerations': 'Соображения' +'section.actions': 'Действия' +'section.approvals': 'Утверждения' +'section.dates': 'Даты' +'section.referenceDocs': 'Справочные документы' +'section.adoptedAt': 'Принято на' +'section.adoptedDecisions': 'Резолюции' +'section.sourceDocs': 'Исходные документы' +'section.declarations': 'Декларации' +'section.committee': 'Комитет' +'section.hosts': 'Организаторы' +'section.note': 'Примечание' +'section.categories': 'Категории' +'section.related': 'Связанные решения' +'section.overview': 'Обзор' +'section.identifiers': 'Идентификаторы' +'page.home.stats.span': 'Охват лет' +'label.scheduled': 'Запланировано' +'label.occurred': 'Состоялось' +'decisions.empty': 'Нет решений.' +'meetings.empty': 'Нет заседаний.' +'search.empty': 'Результаты не найдены.' +'search.placeholder': 'Поиск…' +'search.allBodies': 'Все органы' +'search.allKinds': 'Все типы' +'search.ariaLabel': 'Поиск решений' +'search.ariaLabelMeetings': 'Поиск заседаний' +'search.dateFrom': 'С' +'search.dateTo': 'По' +'search.showMore': 'Показать ещё' +'nav.prev': '← Предыдущее' +'nav.next': 'Следующее →' +'decade.browseLabel': 'Просмотр по десятилетиям' +'about.title': 'О сайте' +'about.format': 'Формат Edoxen' +'about.formatBody': 'Этот сайт отображает данные заседаний и решений с использованием информационной модели Edoxen — схемы YAML для официальных документов органов по стандартизации.' +'about.using': 'Использование сайта' +'about.usingDecisions': 'Решения — просмотр архива резолюций.' +'about.usingMeetings': 'Заседания — просмотр заседаний, их повесток, протоколов и принятых решений.' +'about.usingUrns': 'URN — каждый объект имеет стабильный URN для цитирования.' +'about.stats.decisions': 'Решения' +'about.stats.meetings': 'Заседания' diff --git a/packages/browser/src/i18n/strings/spa.yaml b/packages/browser/src/i18n/strings/spa.yaml new file mode 100644 index 0000000..37ba165 --- /dev/null +++ b/packages/browser/src/i18n/strings/spa.yaml @@ -0,0 +1,71 @@ +'nav.home': 'Inicio' +'nav.decisions': 'Resoluciones' +'nav.meetings': 'Reuniones' +'nav.about': 'Acerca de' +'page.home.heroLabel': 'Archivo de resoluciones' +'page.home.stats.decisions': 'Decisiones registradas' +'page.home.stats.meetings': 'Reuniones documentadas' +'page.home.stats.recent': 'Más recientes' +'page.home.latestDecisions': 'Últimas decisiones' +'page.home.viewAll': 'Ver todo' +'page.home.recentMeetings': 'Reuniones recientes' +'label.body': 'Órgano' +'label.kind': 'Tipo' +'label.date': 'Fecha' +'label.urn': 'URN' +'label.venue': 'Lugar' +'label.adopted': 'Adoptada' +'label.effective': 'Vigente' +'label.meeting': 'Reunión' +'label.acclamation': 'Aclamación' +'section.when': 'Cuándo' +'section.venue': 'Lugar' +'section.officers': 'Oficiales' +'section.schedule': 'Programa' +'section.agenda': 'Orden del día' +'section.deadlines': 'Plazos' +'section.minutes': 'Actas' +'section.subject': 'Asunto' +'section.considering': 'Considerando' +'section.considerations': 'Consideraciones' +'section.actions': 'Acciones' +'section.approvals': 'Aprobaciones' +'section.dates': 'Fechas' +'section.referenceDocs': 'Documentos de referencia' +'section.adoptedAt': 'Adoptada en' +'section.adoptedDecisions': 'Resoluciones' +'section.sourceDocs': 'Documentos fuente' +'section.declarations': 'Declaraciones' +'section.committee': 'Comité' +'section.hosts': 'Anfitriones' +'section.note': 'Nota' +'section.categories': 'Categorías' +'section.related': 'Decisiones relacionadas' +'section.overview': 'Resumen' +'section.identifiers': 'Identificadores' +'page.home.stats.span': 'Años cubiertos' +'label.scheduled': 'Programada' +'label.occurred': 'Celebrada' +'decisions.empty': 'Sin decisiones.' +'meetings.empty': 'Sin reuniones.' +'search.empty': 'No se encontraron resultados.' +'search.placeholder': 'Buscar…' +'search.allBodies': 'Todos los órganos' +'search.allKinds': 'Todos los tipos' +'search.ariaLabel': 'Buscar decisiones' +'search.ariaLabelMeetings': 'Buscar reuniones' +'search.dateFrom': 'Desde' +'search.dateTo': 'Hasta' +'search.showMore': 'Mostrar más' +'nav.prev': '← Anterior' +'nav.next': 'Siguiente →' +'decade.browseLabel': 'Explorar por década' +'about.title': 'Acerca de' +'about.format': 'Formato Edoxen' +'about.formatBody': 'Este sitio presenta los datos de reuniones y decisiones utilizando el modelo de información Edoxen — un esquema YAML para las actas oficiales de los organismos de normalización.' +'about.using': 'Uso del sitio' +'about.usingDecisions': 'Decisiones — explorar el archivo de resoluciones.' +'about.usingMeetings': 'Reuniones — ver reuniones y sus órdenes del día, actas y decisiones adoptadas.' +'about.usingUrns': 'URN — cada entidad tiene una URN estable para citación.' +'about.stats.decisions': 'Decisiones' +'about.stats.meetings': 'Reuniones' diff --git a/packages/browser/src/i18n/strings/zho.yaml b/packages/browser/src/i18n/strings/zho.yaml new file mode 100644 index 0000000..53ecd28 --- /dev/null +++ b/packages/browser/src/i18n/strings/zho.yaml @@ -0,0 +1,71 @@ +'nav.home': '首页' +'nav.decisions': '决议' +'nav.meetings': '会议' +'nav.about': '关于' +'page.home.heroLabel': '决议档案' +'page.home.stats.decisions': '在册决定' +'page.home.stats.meetings': '已记录会议' +'page.home.stats.recent': '最新' +'page.home.latestDecisions': '最新决定' +'page.home.viewAll': '查看全部' +'page.home.recentMeetings': '近期会议' +'label.body': '机构' +'label.kind': '类型' +'label.date': '日期' +'label.urn': 'URN' +'label.venue': '地点' +'label.adopted': '通过' +'label.effective': '生效' +'label.meeting': '会议' +'label.acclamation': '鼓掌通过' +'section.when': '时间' +'section.venue': '地点' +'section.officers': '主席团' +'section.schedule': '日程' +'section.agenda': '议程' +'section.deadlines': '截止日期' +'section.minutes': '会议纪要' +'section.subject': '主题' +'section.considering': '考虑到' +'section.considerations': '审议事项' +'section.actions': '行动' +'section.approvals': '表决' +'section.dates': '日期' +'section.referenceDocs': '参考文件' +'section.adoptedAt': '通过地点' +'section.adoptedDecisions': '决议' +'section.sourceDocs': '来源文件' +'section.declarations': '声明' +'section.committee': '委员会' +'section.hosts': '主办方' +'section.note': '备注' +'section.categories': '类别' +'section.related': '相关决议' +'section.overview': '概览' +'section.identifiers': '标识符' +'page.home.stats.span': '年份跨度' +'label.scheduled': '计划' +'label.occurred': '实际' +'decisions.empty': '暂无决定。' +'meetings.empty': '暂无会议。' +'search.empty': '未找到结果。' +'search.placeholder': '搜索…' +'search.allBodies': '全部机构' +'search.allKinds': '全部类型' +'search.ariaLabel': '搜索决定' +'search.ariaLabelMeetings': '搜索会议' +'search.dateFrom': '从' +'search.dateTo': '至' +'search.showMore': '显示更多' +'nav.prev': '← 上一条' +'nav.next': '下一条 →' +'decade.browseLabel': '按年代浏览' +'about.title': '关于' +'about.format': 'Edoxen 格式' +'about.formatBody': '本网站使用 Edoxen 信息模型呈现会议和决定数据——一个用于标准化机构正式记录的 YAML 架构。' +'about.using': '使用本站' +'about.usingDecisions': '决定 — 浏览决议档案。' +'about.usingMeetings': '会议 — 查看会议及其议程、纪要和通过的决议。' +'about.usingUrns': 'URN — 每个实体都有一个稳定的 URN 用于引用。' +'about.stats.decisions': '决定' +'about.stats.meetings': '会议' diff --git a/packages/browser/src/i18n/ui.ts b/packages/browser/src/i18n/ui.ts index 040936a..0444b34 100644 --- a/packages/browser/src/i18n/ui.ts +++ b/packages/browser/src/i18n/ui.ts @@ -1,4 +1,7 @@ import type { Terminology } from '../config/schema.js' +import { BUILTIN_STRINGS, loadYamlTranslations } from './load-translations.js' + +export { loadYamlTranslations } from './load-translations.js' export type UiLocale = 'eng' | 'fra' | 'zho' | 'spa' | 'ara' | 'rus' @@ -15,564 +18,10 @@ export const LOCALE_LABELS: Readonly> = { export const RTL_LOCALES: readonly string[] = ['ara'] -const STRINGS: Readonly>>> = { - eng: { - 'nav.home': 'Home', - 'nav.decisions': 'Resolutions', - 'nav.meetings': 'Meetings', - 'nav.about': 'About', - - 'page.home.heroLabel': 'Resolutions Archive', - 'page.home.stats.decisions': 'Decisions on record', - 'page.home.stats.meetings': 'Meetings documented', - 'page.home.stats.recent': 'Most recent', - 'page.home.latestDecisions': 'Latest Decisions', - 'page.home.viewAll': 'View all', - 'page.home.recentMeetings': 'Recent Meetings', - - 'label.body': 'Body', - 'label.kind': 'Kind', - 'label.date': 'Date', - 'label.urn': 'URN', - 'label.venue': 'Venue', - 'label.adopted': 'Adopted', - 'label.effective': 'Effective', - 'label.meeting': 'Meeting', - 'label.acclamation': 'Acclamation', - - 'section.when': 'When', - 'section.venue': 'Venue', - 'section.officers': 'Officers', - 'section.schedule': 'Schedule', - 'section.agenda': 'Agenda', - 'section.deadlines': 'Deadlines', - 'section.minutes': 'Minutes', - 'section.subject': 'Subject', - 'section.considering': 'Considering', - 'section.considerations': 'Considerations', - 'section.actions': 'Actions', - 'section.approvals': 'Approvals', - 'section.dates': 'Dates', - 'section.referenceDocs': 'Reference documents', - 'section.adoptedAt': 'Adopted at', - 'section.adoptedDecisions': 'Resolutions', - 'section.sourceDocs': 'Source documents', - 'section.declarations': 'Declarations', - 'section.committee': 'Committee', - 'section.hosts': 'Hosts', - 'section.note': 'Note', - 'section.categories': 'Categories', - 'section.related': 'Related decisions', - 'section.overview': 'Overview', - 'section.identifiers': 'Identifiers', - 'page.home.stats.span': 'Year span', - - 'label.scheduled': 'Scheduled', - 'label.occurred': 'Occurred', - - 'decisions.empty': 'No decisions.', - 'meetings.empty': 'No meetings.', - 'search.empty': 'No matches found.', - 'search.placeholder': 'Search…', - 'search.allBodies': 'All bodies', - 'search.allKinds': 'All kinds', - 'search.ariaLabel': 'Search decisions', - 'search.ariaLabelMeetings': 'Search meetings', - 'search.dateFrom': 'From', - 'search.dateTo': 'To', - 'search.showMore': 'Show more', - 'search.groupYear': 'Year', - 'search.groupLocation': 'Location', - 'search.groupType': 'Type', - 'search.groupActions': 'Actions', - 'search.groupBody': 'Body', - 'meeting.virtual': 'Virtual', - - 'meeting.type.plenary': 'Plenary', - 'meeting.type.working_group': 'Working Group', - 'meeting.type.task_group': 'Task Group', - 'meeting.type.ad_hoc': 'Ad Hoc', - 'meeting.type.joint': 'Joint', - 'meeting.type.general_assembly': 'General Assembly', - 'meeting.type.committee': 'Committee', - 'meeting.type.subcommittee': 'Subcommittee', - 'meeting.type.conference': 'Conference', - 'meeting.type.workshop': 'Workshop', - 'meeting.type.seminar': 'Seminar', - 'meeting.type.webinar': 'Webinar', - 'meeting.type.hearing': 'Hearing', - 'meeting.type.markup': 'Markup', - 'meeting.type.board_meeting': 'Board Meeting', - 'meeting.type.annual_general_meeting': 'Annual General Meeting', - 'meeting.type.other': 'Other', - - 'nav.prev': '← Previous', - 'nav.next': 'Next →', - - 'decade.browseLabel': 'Browse by decade', - 'about.title': 'About', - 'about.subtitle': 'A digital record of {count} {records} from {committee}, spanning {year} to today.', - 'about.format': 'Edoxen format', - 'about.formatBody': 'This site renders meeting and decision data using the Edoxen information model — a YAML-based schema for formal proceedings of standards bodies.', - 'about.formatBody2': 'Each file carries a metadata block (title, date, source, location) followed by a decisions array. Every decision has a structured identifier (prefix + number), a kind (resolution, order, ruling), per-language localizations, and the considerations, actions and approvals that form its record.', - 'about.actionTypesTitle': 'Action Types', - 'about.actionTypesBody': 'Every resolution records typed actions — what the committee resolved to do. The type vocabulary makes the archive filterable and analyzable across decades.', - 'about.using': 'Using this site', - 'about.usingDecisions': 'Decisions — browse the resolutions archive.', - 'about.usingMeetings': 'Meetings — see meetings and their agendas, minutes, and adopted decisions.', - 'about.usingUrns': 'URNs — every entity has a stable URN for citation.', - 'about.stats.decisions': 'Decisions', - 'about.stats.meetings': 'Meetings', - 'about.urnTitle': 'URN Identifiers', - 'about.urnBody': 'Resources in this archive are assigned Uniform Resource Names (URNs) to provide persistent, location-independent identifiers.', - 'about.urnDecisionUrns': 'Decision URNs', - 'about.urnMeetingUrns': 'Meeting URNs', - 'about.urnExample': 'Example', - 'about.urnNote': 'Note that per RFC 5141, "documents at or below the Technical Committee level" are not covered by the standard urn:iso:std: namespace. Section 2.6 delegates URN management for TC resources to the Technical Committees themselves.', - 'about.lifecycleTitle': 'Resolution Lifecycle', - 'about.lifecycleIntro': 'Each decision in the Edoxen model follows a structured lifecycle, captured through three interconnected sections:', - 'about.lifecycleConsiderations': 'Considerations', - 'about.lifecycleConsiderationsDesc': 'The context and background that led to the decision. Each consideration has a type (e.g., noting, recalling, recognising) and a message explaining what the committee observed or referenced.', - 'about.lifecycleActions': 'Actions', - 'about.lifecycleActionsDesc': 'The decisions themselves — what the committee resolved to do. Each action carries a semantic type (e.g., requests, approves, appoints) that categorizes the nature of the decision, along with the detailed message.', - 'about.lifecycleApprovals': 'Approvals', - 'about.lifecycleApprovalsDesc': 'How the decision was formally adopted, including the degree of consensus (e.g., unanimous, consensus) and any relevant notes about the approval process.', - 'about.lifecycleOutro': 'A single decision may contain multiple considerations, actions, and approvals — together forming a complete record of the committee\u2019s decision-making process.', - }, - - fra: { - 'nav.home': 'Accueil', - 'nav.decisions': 'Résolutions', - 'nav.meetings': 'Réunions', - 'nav.about': 'À propos', - - 'page.home.heroLabel': 'Archive des résolutions', - 'page.home.stats.decisions': 'Décisions enregistrées', - 'page.home.stats.meetings': 'Réunions documentées', - 'page.home.stats.recent': 'Plus récentes', - 'page.home.latestDecisions': 'Dernières décisions', - 'page.home.viewAll': 'Voir tout', - 'page.home.recentMeetings': 'Réunions récentes', - - 'label.body': 'Organe', - 'label.kind': 'Type', - 'label.date': 'Date', - 'label.urn': 'URN', - 'label.venue': 'Lieu', - 'label.adopted': 'Adoptée', - 'label.effective': 'En vigueur', - 'label.meeting': 'Réunion', - 'label.acclamation': 'Acclamation', - - 'section.when': 'Quand', - 'section.venue': 'Lieu', - 'section.officers': 'Bureau', - 'section.schedule': 'Programme', - 'section.agenda': 'Ordre du jour', - 'section.deadlines': 'Échéances', - 'section.minutes': 'Procès-verbaux', - 'section.subject': 'Sujet', - 'section.considering': 'Considérant', - 'section.considerations': 'Considérations', - 'section.actions': 'Actions', - 'section.approvals': 'Approbations', - 'section.dates': 'Dates', - 'section.referenceDocs': 'Documents de référence', - 'section.adoptedAt': 'Adoptée à', - 'section.adoptedDecisions': 'Résolutions', - 'section.sourceDocs': 'Documents sources', - 'section.declarations': 'Déclarations', - 'section.committee': 'Comité', - 'section.hosts': 'Hôtes', - 'section.note': 'Note', - 'section.categories': 'Catégories', - 'section.related': 'Décisions liées', - 'section.overview': 'Aperçu', - 'section.identifiers': 'Identifiants', - 'page.home.stats.span': 'Années couvertes', - - 'label.scheduled': 'Prévu', - 'label.occurred': 'Tenu', - - 'decisions.empty': 'Aucune décision.', - 'meetings.empty': 'Aucune réunion.', - 'search.empty': 'Aucun résultat trouvé.', - 'search.placeholder': 'Rechercher…', - 'search.allBodies': 'Tous les organes', - 'search.allKinds': 'Tous les types', - 'search.ariaLabel': 'Rechercher dans les décisions', - 'search.ariaLabelMeetings': 'Rechercher dans les réunions', - 'search.dateFrom': 'De', - 'search.dateTo': 'À', - 'search.showMore': 'Afficher plus', - 'search.groupYear': 'Année', - 'search.groupLocation': 'Lieu', - 'search.groupType': 'Type', - 'search.groupActions': 'Actions', - 'search.groupBody': 'Organe', - 'meeting.virtual': 'Virtuel', - - 'meeting.type.plenary': 'Plénière', - 'meeting.type.working_group': 'Groupe de travail', - 'meeting.type.task_group': 'Groupe de travail ad hoc', - 'meeting.type.ad_hoc': 'Ad hoc', - 'meeting.type.joint': 'Conjointe', - 'meeting.type.general_assembly': 'Assemblée générale', - 'meeting.type.committee': 'Comité', - 'meeting.type.subcommittee': 'Sous-comité', - 'meeting.type.conference': 'Conférence', - 'meeting.type.workshop': 'Atelier', - 'meeting.type.seminar': 'Séminaire', - 'meeting.type.webinar': 'Webinaire', - 'meeting.type.hearing': 'Audience', - 'meeting.type.markup': 'Markup', - 'meeting.type.board_meeting': 'Réunion du conseil', - 'meeting.type.annual_general_meeting': 'Assemblée générale annuelle', - 'meeting.type.other': 'Autre', - - 'nav.prev': '← Précédent', - 'nav.next': 'Suivant →', - - 'decade.browseLabel': 'Parcourir par décennie', - 'about.title': 'À propos', - 'about.subtitle': 'Une archive numérique de {count} {records} — {committee}, de {year} à aujourd\'hui.', - 'about.format': 'Format Edoxen', - 'about.formatBody': 'Ce site affiche les données de réunions et de décisions selon le modèle d\'information Edoxen — un schéma YAML pour les actes officiels des organismes de normalisation.', - 'about.formatBody2': 'Chaque fichier comporte un bloc de métadonnées (titre, date, source, lieu) suivi d\'un tableau de décisions. Chaque décision possède un identifiant structuré (préfixe + numéro), un genre (résolution, ordre, décision), des localisations par langue, ainsi que les considérations, actions et approbations qui composent son dossier.', - 'about.actionTypesTitle': 'Types d\'actions', - 'about.actionTypesBody': 'Chaque résolution enregistre des actions typées — ce que le comité a décidé de faire. Ce vocabulaire rend les archives filtrables et analysables sur des décennies.', - 'about.using': 'Utilisation du site', - 'about.usingDecisions': 'Décisions — consulter l\'archive des résolutions.', - 'about.usingMeetings': 'Réunions — voir les réunions et leurs ordres du jour, procès-verbaux et décisions adoptées.', - 'about.usingUrns': 'URN — chaque entité possède une URN stable pour la citation.', - 'about.stats.decisions': 'Décisions', - 'about.stats.meetings': 'Réunions', - }, - - zho: { - 'nav.home': '首页', - 'nav.decisions': '决议', - 'nav.meetings': '会议', - 'nav.about': '关于', - - 'page.home.heroLabel': '决议档案', - 'page.home.stats.decisions': '在册决定', - 'page.home.stats.meetings': '已记录会议', - 'page.home.stats.recent': '最新', - 'page.home.latestDecisions': '最新决定', - 'page.home.viewAll': '查看全部', - 'page.home.recentMeetings': '近期会议', - - 'label.body': '机构', - 'label.kind': '类型', - 'label.date': '日期', - 'label.urn': 'URN', - 'label.venue': '地点', - 'label.adopted': '通过', - 'label.effective': '生效', - 'label.meeting': '会议', - 'label.acclamation': '鼓掌通过', - - 'section.when': '时间', - 'section.venue': '地点', - 'section.officers': '主席团', - 'section.schedule': '日程', - 'section.agenda': '议程', - 'section.deadlines': '截止日期', - 'section.minutes': '会议纪要', - 'section.subject': '主题', - 'section.considering': '考虑到', - 'section.considerations': '审议事项', - 'section.actions': '行动', - 'section.approvals': '表决', - 'section.dates': '日期', - 'section.referenceDocs': '参考文件', - 'section.adoptedAt': '通过地点', - 'section.adoptedDecisions': '决议', - 'section.sourceDocs': '来源文件', - 'section.declarations': '声明', - 'section.committee': '委员会', - 'section.hosts': '主办方', - 'section.note': '备注', - 'section.categories': '类别', - 'section.related': '相关决议', - 'section.overview': '概览', - 'section.identifiers': '标识符', - 'page.home.stats.span': '年份跨度', - - 'label.scheduled': '计划', - 'label.occurred': '实际', - - 'decisions.empty': '暂无决定。', - 'meetings.empty': '暂无会议。', - 'search.empty': '未找到结果。', - 'search.placeholder': '搜索…', - 'search.allBodies': '全部机构', - 'search.allKinds': '全部类型', - 'search.ariaLabel': '搜索决定', - 'search.ariaLabelMeetings': '搜索会议', - 'search.dateFrom': '从', - 'search.dateTo': '至', - 'search.showMore': '显示更多', - - 'nav.prev': '← 上一条', - 'nav.next': '下一条 →', +// Built-in locale strings live in src/i18n/strings/.yaml. +// See load-translations.ts for the YAML loader. +const STRINGS = BUILTIN_STRINGS - 'decade.browseLabel': '按年代浏览', - 'about.title': '关于', - 'about.format': 'Edoxen 格式', - 'about.formatBody': '本网站使用 Edoxen 信息模型呈现会议和决定数据——一个用于标准化机构正式记录的 YAML 架构。', - 'about.using': '使用本站', - 'about.usingDecisions': '决定 — 浏览决议档案。', - 'about.usingMeetings': '会议 — 查看会议及其议程、纪要和通过的决议。', - 'about.usingUrns': 'URN — 每个实体都有一个稳定的 URN 用于引用。', - 'about.stats.decisions': '决定', - 'about.stats.meetings': '会议', - }, - - spa: { - 'nav.home': 'Inicio', - 'nav.decisions': 'Resoluciones', - 'nav.meetings': 'Reuniones', - 'nav.about': 'Acerca de', - - 'page.home.heroLabel': 'Archivo de resoluciones', - 'page.home.stats.decisions': 'Decisiones registradas', - 'page.home.stats.meetings': 'Reuniones documentadas', - 'page.home.stats.recent': 'Más recientes', - 'page.home.latestDecisions': 'Últimas decisiones', - 'page.home.viewAll': 'Ver todo', - 'page.home.recentMeetings': 'Reuniones recientes', - - 'label.body': 'Órgano', - 'label.kind': 'Tipo', - 'label.date': 'Fecha', - 'label.urn': 'URN', - 'label.venue': 'Lugar', - 'label.adopted': 'Adoptada', - 'label.effective': 'Vigente', - 'label.meeting': 'Reunión', - 'label.acclamation': 'Aclamación', - - 'section.when': 'Cuándo', - 'section.venue': 'Lugar', - 'section.officers': 'Oficiales', - 'section.schedule': 'Programa', - 'section.agenda': 'Orden del día', - 'section.deadlines': 'Plazos', - 'section.minutes': 'Actas', - 'section.subject': 'Asunto', - 'section.considering': 'Considerando', - 'section.considerations': 'Consideraciones', - 'section.actions': 'Acciones', - 'section.approvals': 'Aprobaciones', - 'section.dates': 'Fechas', - 'section.referenceDocs': 'Documentos de referencia', - 'section.adoptedAt': 'Adoptada en', - 'section.adoptedDecisions': 'Resoluciones', - 'section.sourceDocs': 'Documentos fuente', - 'section.declarations': 'Declaraciones', - 'section.committee': 'Comité', - 'section.hosts': 'Anfitriones', - 'section.note': 'Nota', - 'section.categories': 'Categorías', - 'section.related': 'Decisiones relacionadas', - 'section.overview': 'Resumen', - 'section.identifiers': 'Identificadores', - 'page.home.stats.span': 'Años cubiertos', - - 'label.scheduled': 'Programada', - 'label.occurred': 'Celebrada', - - 'decisions.empty': 'Sin decisiones.', - 'meetings.empty': 'Sin reuniones.', - 'search.empty': 'No se encontraron resultados.', - 'search.placeholder': 'Buscar…', - 'search.allBodies': 'Todos los órganos', - 'search.allKinds': 'Todos los tipos', - 'search.ariaLabel': 'Buscar decisiones', - 'search.ariaLabelMeetings': 'Buscar reuniones', - 'search.dateFrom': 'Desde', - 'search.dateTo': 'Hasta', - 'search.showMore': 'Mostrar más', - - 'nav.prev': '← Anterior', - 'nav.next': 'Siguiente →', - - 'decade.browseLabel': 'Explorar por década', - 'about.title': 'Acerca de', - 'about.format': 'Formato Edoxen', - 'about.formatBody': 'Este sitio presenta los datos de reuniones y decisiones utilizando el modelo de información Edoxen — un esquema YAML para las actas oficiales de los organismos de normalización.', - 'about.using': 'Uso del sitio', - 'about.usingDecisions': 'Decisiones — explorar el archivo de resoluciones.', - 'about.usingMeetings': 'Reuniones — ver reuniones y sus órdenes del día, actas y decisiones adoptadas.', - 'about.usingUrns': 'URN — cada entidad tiene una URN estable para citación.', - 'about.stats.decisions': 'Decisiones', - 'about.stats.meetings': 'Reuniones', - }, - - ara: { - 'nav.home': 'الرئيسية', - 'nav.decisions': 'القرارات', - 'nav.meetings': 'الاجتماعات', - 'nav.about': 'حول', - - 'page.home.heroLabel': 'أرشيف القرارات', - 'page.home.stats.decisions': 'القرارات المسجلة', - 'page.home.stats.meetings': 'الاجتماعات الموثقة', - 'page.home.stats.recent': 'الأحدث', - 'page.home.latestDecisions': 'أحدث القرارات', - 'page.home.viewAll': 'عرض الكل', - 'page.home.recentMeetings': 'الاجتماعات الأخيرة', - - 'label.body': 'الهيئة', - 'label.kind': 'النوع', - 'label.date': 'التاريخ', - 'label.urn': 'URN', - 'label.venue': 'المكان', - 'label.adopted': 'اعتُمدت', - 'label.effective': 'سارٍ', - 'label.meeting': 'الاجتماع', - 'label.acclamation': 'بالتصفيق', - - 'section.when': 'متى', - 'section.venue': 'المكان', - 'section.officers': 'المسؤولون', - 'section.schedule': 'الجدول', - 'section.agenda': 'جدول الأعمال', - 'section.deadlines': 'المواعيد النهائية', - 'section.minutes': 'محاضر الاجتماع', - 'section.subject': 'الموضوع', - 'section.considering': 'إذ يراعي', - 'section.considerations': 'الاعتبارات', - 'section.actions': 'الإجراءات', - 'section.approvals': 'الموافقات', - 'section.dates': 'التواريخ', - 'section.referenceDocs': 'الوثائق المرجعية', - 'section.adoptedAt': 'اعتُمد في', - 'section.adoptedDecisions': 'القرارات', - 'section.sourceDocs': 'الوثائق المصدر', - 'section.declarations': 'الإعلانات', - 'section.committee': 'اللجنة', - 'section.hosts': 'المضيفون', - 'section.note': 'ملاحظة', - 'section.categories': 'الفئات', - 'section.related': 'قرارات ذات صلة', - 'section.overview': 'نظرة عامة', - 'section.identifiers': 'المعرفات', - 'page.home.stats.span': 'نطاق السنوات', - - 'label.scheduled': 'مجدولة', - 'label.occurred': 'عُقدت', - - 'decisions.empty': 'لا توجد قرارات.', - 'meetings.empty': 'لا توجد اجتماعات.', - 'search.empty': 'لم يتم العثور على نتائج.', - 'search.placeholder': 'بحث…', - 'search.allBodies': 'جميع الهيئات', - 'search.allKinds': 'جميع الأنواع', - 'search.ariaLabel': 'البحث في القرارات', - 'search.ariaLabelMeetings': 'البحث في الاجتماعات', - 'search.dateFrom': 'من', - 'search.dateTo': 'إلى', - 'search.showMore': 'عرض المزيد', - - 'nav.prev': '→ السابق', - 'nav.next': 'التالي ←', - - 'decade.browseLabel': 'تصفح حسب العقد', - 'about.title': 'حول', - 'about.format': 'صيغة Edoxen', - 'about.formatBody': 'يعرض هذا الموقع بيانات الاجتماعات والقرارات باستخدام نموذج معلومات Edoxen — مخطط YAML للسجلات الرسمية لهيئات المعايير.', - 'about.using': 'استخدام الموقع', - 'about.usingDecisions': 'القرارات — تصفح أرشيف القرارات.', - 'about.usingMeetings': 'الاجتماعات — شاهد الاجتماعات وجداول أعمالها ومحاضرها وقراراتها المعتمدة.', - 'about.usingUrns': 'URN — لكل كيان URN مستقر للاستشهاد.', - 'about.stats.decisions': 'القرارات', - 'about.stats.meetings': 'الاجتماعات', - }, - - rus: { - 'nav.home': 'Главная', - 'nav.decisions': 'Резолюции', - 'nav.meetings': 'Заседания', - 'nav.about': 'О сайте', - - 'page.home.heroLabel': 'Архив резолюций', - 'page.home.stats.decisions': 'Решений в реестре', - 'page.home.stats.meetings': 'Заседаний задокументировано', - 'page.home.stats.recent': 'Последние', - 'page.home.latestDecisions': 'Последние решения', - 'page.home.viewAll': 'Показать все', - 'page.home.recentMeetings': 'Недавние заседания', - - 'label.body': 'Орган', - 'label.kind': 'Тип', - 'label.date': 'Дата', - 'label.urn': 'URN', - 'label.venue': 'Место', - 'label.adopted': 'Принято', - 'label.effective': 'Вступило в силу', - 'label.meeting': 'Заседание', - 'label.acclamation': 'Акламация', - - 'section.when': 'Когда', - 'section.venue': 'Место', - 'section.officers': 'Должностные лица', - 'section.schedule': 'Программа', - 'section.agenda': 'Повестка', - 'section.deadlines': 'Сроки', - 'section.minutes': 'Протоколы', - 'section.subject': 'Предмет', - 'section.considering': 'Принимая во внимание', - 'section.considerations': 'Соображения', - 'section.actions': 'Действия', - 'section.approvals': 'Утверждения', - 'section.dates': 'Даты', - 'section.referenceDocs': 'Справочные документы', - 'section.adoptedAt': 'Принято на', - 'section.adoptedDecisions': 'Резолюции', - 'section.sourceDocs': 'Исходные документы', - 'section.declarations': 'Декларации', - 'section.committee': 'Комитет', - 'section.hosts': 'Организаторы', - 'section.note': 'Примечание', - 'section.categories': 'Категории', - 'section.related': 'Связанные решения', - 'section.overview': 'Обзор', - 'section.identifiers': 'Идентификаторы', - 'page.home.stats.span': 'Охват лет', - - 'label.scheduled': 'Запланировано', - 'label.occurred': 'Состоялось', - - 'decisions.empty': 'Нет решений.', - 'meetings.empty': 'Нет заседаний.', - 'search.empty': 'Результаты не найдены.', - 'search.placeholder': 'Поиск…', - 'search.allBodies': 'Все органы', - 'search.allKinds': 'Все типы', - 'search.ariaLabel': 'Поиск решений', - 'search.ariaLabelMeetings': 'Поиск заседаний', - 'search.dateFrom': 'С', - 'search.dateTo': 'По', - 'search.showMore': 'Показать ещё', - - 'nav.prev': '← Предыдущее', - 'nav.next': 'Следующее →', - - 'decade.browseLabel': 'Просмотр по десятилетиям', - 'about.title': 'О сайте', - 'about.format': 'Формат Edoxen', - 'about.formatBody': 'Этот сайт отображает данные заседаний и решений с использованием информационной модели Edoxen — схемы YAML для официальных документов органов по стандартизации.', - 'about.using': 'Использование сайта', - 'about.usingDecisions': 'Решения — просмотр архива резолюций.', - 'about.usingMeetings': 'Заседания — просмотр заседаний, их повесток, протоколов и принятых решений.', - 'about.usingUrns': 'URN — каждый объект имеет стабильный URN для цитирования.', - 'about.stats.decisions': 'Решения', - 'about.stats.meetings': 'Заседания', - }, -} export type UiStrings = Readonly> export type CustomUiStrings = Readonly> diff --git a/packages/browser/src/virtual.d.ts b/packages/browser/src/virtual.d.ts index 9079b0f..8079609 100644 --- a/packages/browser/src/virtual.d.ts +++ b/packages/browser/src/virtual.d.ts @@ -11,3 +11,13 @@ declare module 'virtual:edoxen-payloads' { } declare module 'virtual:edoxen-custom-css' + +// Vite `?raw` imports — the file's contents as a string. +declare module '*.yaml?raw' { + const contents: string + export default contents +} +declare module '*.yml?raw' { + const contents: string + export default contents +}