MeridiansMeridians

Copy and localisation

From the Meridians Wiki · Public · Maintained · joint

Meridians has two language axes. Keep them separate.

  • Interface locale is a per-person presentation preference. It changes fixed controls, guidance, public page copy, metadata, and formatting. It lives in the browser/cookie, never in a Domain record.
  • Domain content language is the language in which an expert reads, reasons, and writes durable content. It is canonical domain configuration and must be explicit to every relevant model call. A French interface must not silently make a shared English expert produce French evidence or prose.

The copy boundary

All stable, front-facing copy authored by Meridians belongs in a locale pack under src/lib/core/i18n/messages/. en-AU.ts is the root source contract and composes focused surface modules. A focused module may export both a nested feature pack and root-level objects when preserving an established copy.* path; the root imports those objects instead of duplicating or relocating the public contract. A later locale pack must satisfy the resulting TypeScript shape, so omissions are compilation failures rather than accidental English fallbacks.

Packs are organised by product surface (access, gateway, plans, studio, Stories), never by component line or by one global list of incidental strings. Keys describe intent (emptySelection, deleteTitle, messageCount) rather than extraction order (s1, s2). Keep whole grammatical units in formatter functions when word order, number formatting, or pluralisation may vary by locale; components must not reassemble a sentence from translated fragments. LocaleCopy is the translation contract: it preserves the full source-locale shape and formatter signatures while widening text values, so a translated pack can say something other than the English literal and still fail compilation when a key or formatter is missing.

Name the live practice surface Scenario in interface copy: “Start Scenario”, “Scenarios”, and “End Scenario”. Reserve game for implementation identifiers and for Game Theory, the distinct analytical method. Do not let an internal game key or module leak into a person-facing label.

Format values through the selected interface locale too. React surfaces use useLocale().format for numbers, currency, dates, times, and date-times; stable copy receives the already-formatted value as an argument. Do not introduce browser-default toLocaleString() calls or hard-code an English locale in a view, because either bypasses the person's chosen interface language.

Keep composition modules small. en-AU.ts is the one typed locale contract; it composes feature packs and domain packs such as base, studio, gateway, inspector, Scenario, and Readings. A broad surface may use its own small composition module to group narrower editing packs. The White Paper follows the same rule: its frame, product argument, methods, instrument, and practice sections are separate editorial packs, so a translation or copy edit does not require paging through the entire paper. Split when the next editor would otherwise have to understand an unrelated surface to change a sentence; do not split into one file per component.

Components render locale-pack values through useLocale(); server metadata uses englishCopy until locale-specific routes and metadata are deliberately introduced. Runtime-generated domain content stays in the canonical record. Markdown remains authored English until it is intentionally introduced as a localised document surface.

House voice before translation

Every locale pack carries the written register of Meridians' one shared voice. The canonical source for that identity is MERIDIANS_SOUL; its print sibling, EDITORIAL_VOICE, defines how the same expert writes a complete artifact rather than holds a conversation. Reusable application guidance lives in Brand copy's house voice.

Required route for copy and editorial work

Do not edit public copy, stable guidance, Stories prose, newsletter prompts, or another published register from a prose summary of the voice. Read the live sources in this order:

  1. Positioning for what the product may truthfully claim.
  2. MERIDIANS_SOUL in full for the enduring character, judgment, agency, vulnerability, social intuition, World Spirit, and sovereign encounter.
  3. EDITORIAL_VOICE for the same person in print, including what deliberately does not transfer from conversation.
  4. The owning surface: src/lib/core/i18n/messages/ for stable authored copy, or prompts/program for Story, article, attachment, and newsletter task discipline.

The prompt code wins over a descriptive doc. When MERIDIANS_SOUL changes materially, audit EDITORIAL_VOICE, this convention, the house-voice leaf, and the voice seam tests in src/__tests__/{chat-soul,editorial-voice}.test.ts in the same change. When EDITORIAL_VOICE changes, verify that both the Story editorial and newsletter system prompts still compose it; neither surface gets a local personality patch.

Apply that voice to stable authored copy as follows:

  • Lead with the load-bearing point. Say what changed, what matters, or what the person can do before explanation; cut the runway, the generic welcome, and the second headline hiding inside the first.
  • Write with calm, natural conviction. Be warm and direct without personifying controls, manufacturing drama, or asking the reader to admire the product.
  • Separate evidence, inference, and commitment. Name the condition or exposure that would change a read; do not launder uncertainty into vague neutrality or certainty into hype.
  • Make the exposed edge useful. Where a person is deciding, surface the material limit, missing evidence, or reversibility of the choice plainly. This is not performed vulnerability: no apologising for an earned judgment, no fishing for reassurance, and no burden placed on the reader.
  • Give strategic guidance a visible reason. When copy points toward a next step, make the decision it unlocks or the constraint it protects legible. Be tactful about real human stakes without mind-reading, diagnosing, or steering through hidden pressure.
  • Keep commitments attributable. The machine watches, maintains, builds the case, and may press a forceful interpretation; the person configures, corrects, and makes consequential calls. Make the objective, inherited frame, constraints, reversibility, and available lever clearer without pretending that a machine's authority itself constitutes the person's decision.
  • Carry the shared world stance into reasoning, not slogans. Respect situated information, location, permissions, and tools; trace outcomes across plural Will and material conditions; and keep synthetic- world conclusions bounded to their declared model unless external evidence validates them.
  • Preserve the public truth boundary. Positioning owns what is shipped, being built, or directional; copy must not turn a future verification surface into a present capability.

This is a written-copy contract, not a reason to put chat mechanics into an interface: controls do not speak in first person, public pages do not end with a question, and the Soul still does not apply to in-character Fate, System, World, or entity personas. Interface locale also never changes a Domain's canonical content language or its model prompts.

Director-controlled records and future learning use

The canonical record belongs to its Director. Stable copy may describe local ownership, version history, and a Director's control over their own Domain, but it must not imply that private Domain content or Scenario traces are collected for model training. They are not an ambient training feed.

If a future product permits a Director to contribute a record, branch, or Scenario trace to evaluation or training, the choice must be explicit, granular, revocable where technically possible, and clear about what leaves the Director-controlled record, who can access it, how it is used, and how other participants' data is handled. That policy belongs in a dedicated consent surface and its supporting documentation—not as an implication hidden in onboarding, Scenario copy, or a locale string.

Spoken language follows its source

Speech has three deliberate authorities. Dictation preserves the operator's words: it may clean transport-level whitespace and offer a transcription-language hint, but it does not rewrite, translate, or auto-submit what was said. Narration preserves resolved Domain prose: the selected TTS voice changes delivery, never the Domain's language, claims, register, or uncertainty. The Soul is therefore not injected into narration; a domain's authorial voice remains intact. Meridians-authored speech controls—recording, review, permissions, and failure guidance—belong to locale packs and use the house voice as concise operational guidance. This makes spoken output multilingual-ready without letting an individual viewer's interface locale rewrite shared canonical content.

The React white paper is interface copy, not a Markdown exemption. Its migration is complete: each long-form paragraph lives whole in whitePaper locale resources, using the safe inline markers **bold**, _emphasis_, and `code`. Rich in white-paper/_shared.tsx renders those markers as React elements; it never accepts locale-pack HTML.

node scripts/i18n/copy-inventory.mjs is the migration inventory. It parses JSX rather than grepping: it reports rendered literal text and literal presentational props, while excluding paths, styles, IDs, prompts, tests, and canonical domain data. Every candidate is classified during migration; author attribution is the only intentional rendered-text exemption and remains guarded separately. Once a surface is migrated, its inventory must be zero. The repository gate is a hard zero floor over that inventory, held in scripts/dev/copy-ratchet.mjs rather than read out of the generated ledger: any candidate at all fails npm run check:copy, and regenerating knowledge-base/generated/COPY.md cannot concede one. While the migration ran, a raised per-file budget was a legitimate concession that landed in the diff; that escape hatch is retired now that the count reached zero, because it is how the count drifted back up once already (two spatial views, repaired afterwards). COPY.md is consequently a report of a finished migration, and the ratchet also fails if the committed ledger still claims debt the tree no longer holds.

Three commands, smallest first:

CommandScopeUse
npm run copy:file <path>one file or directorysee exactly which strings a surface still types inline
npm run copy:changedthe files this work touchedinner loop; runs inside npm run check:changed
npm run check:copyall four surface rootsthe pre-push gate

Adding copy to a surface therefore starts in the locale pack, not in the JSX. A client component reads useLocale().copy; code outside React uses copyFor(locale). A presentational component that cannot call a hook — a leaf rendered by a parent that owns the locale — takes the formatters it needs as a copy prop (SpatialWorldView and SpatialSkeletonView are the worked examples), so the string still resolves in one place. A count or unit rendered beside a number is copy too: {count} inside belongs in the pack as insideCount(count), because the word order and pluralisation are language, not layout.

The same inventory exposes message-pack structure: --message-keys lists opaque sN keys still awaiting semantic names, and --root-sections lists sections still embedded directly in en-AU.ts. Contract tests ratchet opaque keys per focused file and pin migrated root sections outside the monolith. These are migration instruments, not targets: rename keys while moving whole grammar into the pack, and move a root section only when one product-surface module clearly owns it.

The migration order is deliberate:

  1. Public, access, pricing, and onboarding copy — the language that explains the product to an audience.
  2. Shared controls, empty states, errors, and settings — grouped by feature, not in one monolithic file.
  3. Studio and Stories vocabulary — only after each term has a settled glossary entry in knowledge-base/knowledge/vocabulary/index.md.
  4. Locale-aware dates, numbers, currency, and pluralisation.
  5. Explicit Domain content language, prompt policy, and multilingual retrieval/evaluation.

Do not translate LLM prompts by interface locale. That would let whichever viewer opens a shared Domain change its expert. The Domain-language step needs its own reducer action, migration, prompt inventory, and tests before it is enabled.