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:
- The
PARAGLIDE_LOCALEcookie set by the language menu - The browser's preferred language (
Accept-Languageon the first server request) - 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.jsonmessages/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 textmatchesLocalizedSystemContent()searches canonical and translated textcompareLocalizedSystemContent()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:
- Assign a new, permanent
templateKey; never reuse or rename an existing key. - Keep its canonical name and prompt-facing configuration in the template definition.
- Add its localized display name and description to every supported system-content catalog.
- Render, search, and sort it through the localization helpers rather than overwriting canonical database fields.
- Add or update tests in
src/lib/i18n/system-content.test.tsand 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.mdEvery 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:
- Treat
docIdas permanent; do not derive identity from a translated title. - Add or update the complete document in every supported locale. Do not mix fallback paragraphs from another language into a translated article.
- Keep code, API names, configuration keys, and canonical identifiers unchanged unless the example itself is intentionally localized.
- Prefer relative links between documents. The renderer rewrites them to preserve the current locale.
- Review localized legal or compliance text with an appropriate subject-matter expert.
- 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 concept | English source | Simplified Chinese |
|---|---|---|
| Sequence | sequence | 片段 |
| Scene | scene | 场景 |
| Shot | shot | 镜头 |
| Location | location | 地点 |
| Render segment | segment | 视频片段 |
| Talent / cast | talent | 演员 |
| Style | style | 风格 |
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:
- Add it to
project.inlang/settings.json. - Create a complete
messages/<locale>.jsoncatalog. - Add the language label to
messages/*.jsonandsrc/components/i18n/language-switcher.tsx. - Add a system-content catalog when built-in names and descriptions should be translated.
- Add a complete
docs/<locale>/document tree with matchingdocIdvalues. - Extend the system-content and documentation locale resolvers.
- 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