Developer Guide

Internationalization

Add interface translations and localize built-in OpenStory content

OpenStory uses Paraglide JS for interface messages. English (en) is the base locale and Simplified Chinese (zh-CN) is also supported.

Locale Selection

The language menu is available on the sign-in screen and in the app sidebar. Paraglide resolves the active locale in this order:

  1. The PARAGLIDE_LOCALE cookie set by the language menu
  2. The browser's preferred language (Accept-Language on the first server request)
  3. The English base locale

The server middleware and browser runtime use the same locale, and the root layout renders it in <html lang>. API, R2 media, and Cloudflare system routes are excluded from locale routing because their URLs and response contracts must remain stable.

Interface Messages

Source catalogs live in:

  • messages/en.json
  • messages/zh-CN.json

Use semantic message keys and keep the key set and placeholders identical in every locale. Import generated messages from @/paraglide/messages.js:

import { m } from '@/paraglide/messages.js';

export function SaveButton() {
  return <button>{m.common_save()}</button>;
}

Run bun i18n:compile after changing catalogs. bun typecheck also compiles them before checking the project. Files under src/paraglide/ are generated output and must not be edited by hand.

Built-In Styles, Talent, and Locations

Built-in content needs a stable identity that is separate from the text shown to users. Each system template therefore has an immutable templateKey, such as style.product_ad, while its canonical English name remains the source value used by URLs, media paths, APIs, seed synchronization, and AI prompts.

Simplified Chinese display names and descriptions live in src/content/system/zh-CN.ts. Presentation code resolves them through src/lib/i18n/system-content.ts:

  • localizeSystemContent() returns localized display text
  • matchesLocalizedSystemContent() searches canonical and translated text
  • compareLocalizedSystemContent() applies locale-aware sorting

User-created content has no system templateKey, so it naturally falls back to the name and description entered by the user.

When adding a system template:

  1. Assign a new, permanent templateKey; never reuse or rename an existing key.
  2. Keep its canonical name and prompt-facing configuration in the template definition.
  3. Add its localized display name and description to every supported system-content catalog.
  4. Render, search, and sort it through the localization helpers rather than overwriting canonical database fields.
  5. Add or update tests in src/lib/i18n/system-content.test.ts and the system-template seed tests.

The seed synchronizer first matches system records by templateKey. It only uses the canonical English name to adopt older records that predate stable keys, then restores the canonical source values. Clients cannot set or duplicate server-managed template keys.

Documentation Content

Long-form guides use localized Markdown files instead of Paraglide message catalogs. English source documents live under docs/, while translated documents mirror the same path under docs/<locale>/. For example:

docs/user-guide/scenes.md
docs/zh-CN/user-guide/scenes.md

Every document has an immutable docId, a locale, and a stable section key in its frontmatter. Documents with the same docId are translations of each other. Keep the slug and relative path consistent across locales so links remain predictable and switching languages can preserve the current page.

---
docId: user-guide.scenes
locale: zh-CN
title: 使用场景
description: 查看、编辑并重新生成片段中的各个场景
section: user-guide
order: 2
---

Use Paraglide only for the surrounding interface, such as navigation labels, empty states, and buttons. Keep document titles, descriptions, body content, examples, tables, and images in the localized Markdown file so each translation can be reviewed as a complete article.

When changing documentation:

  1. Treat docId as permanent; do not derive identity from a translated title.
  2. Add or update the complete document in every supported locale. Do not mix fallback paragraphs from another language into a translated article.
  3. Keep code, API names, configuration keys, and canonical identifiers unchanged unless the example itself is intentionally localized.
  4. Prefer relative links between documents. The renderer rewrites them to preserve the current locale.
  5. Review localized legal or compliance text with an appropriate subject-matter expert.
  6. Run the documentation validation and build checks to catch duplicate ids, missing translations, invalid links, and route-generation errors.

Public documentation URLs include the locale, for example /docs/en/user-guide/scenes and /docs/zh-CN/user-guide/scenes. Locale-specific URLs are shareable, cacheable, and suitable for localized metadata, canonical links, hreflang, sitemaps, and language-specific llms.md output.

Simplified Chinese Product Terminology

Use the following terms consistently in the Simplified Chinese interface and documentation. Keep code identifiers, API paths, database fields, and other protocol-level names in canonical English.

Product conceptEnglish sourceSimplified Chinese
Sequencesequence片段
Scenescene场景
Shotshot镜头
Locationlocation地点
Render segmentsegment视频片段
Talent / casttalent演员
Stylestyle风格

Reserve 作品 for ordinary references to a creative work, not the sequence product entity. Use 分镜 for the storyboard or animatic concept, not as an interchangeable label for an individual shot.

Adding a Locale

To add another locale:

  1. Add it to project.inlang/settings.json.
  2. Create a complete messages/<locale>.json catalog.
  3. Add the language label to messages/*.json and src/components/i18n/language-switcher.tsx.
  4. Add a system-content catalog when built-in names and descriptions should be translated.
  5. Add a complete docs/<locale>/ document tree with matching docId values.
  6. Extend the system-content and documentation locale resolvers.
  7. Compile, type-check, test, and build the app.

At minimum, validate with:

bun i18n:compile
bun typecheck
bun lint
bun format:check
bun run test
bun run build