# Publishing extensions ## Function The BT website extension registry stores installable package metadata under `db/ext///`. This page defines the `info.json` localization model, README filenames, language-tag rules, fallback behavior, and the distinction between browser language preferences and explicit localized URLs. Registry metadata schema 2 lets the publisher choose the language of each unsuffixed default field. Additional translations use a dot followed by the website's normalized language tag, for example `summary.en` or `summary.zh-hans`. Machine-readable identifiers are never translated. ## Language-tag standard Language identifiers follow **BCP 47**. BCP 47 is the umbrella specification: [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646) defines the syntax and registry of language tags, while [RFC 4647](https://www.rfc-editor.org/rfc/rfc4647) defines language-range matching. These are complementary specifications, not alternative naming systems. The [HTML language-preference standard](https://html.spec.whatwg.org/multipage/system-state.html#language-preferences) requires `navigator.language` and every entry in `navigator.languages` to be valid BCP 47 language tags. The latter is ordered by preference. HTTP `Accept-Language` is defined by [RFC 9110 section 12.5.4](https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.4) as a preference list of language ranges with optional `q` weights. The HTML standard recommends that browsers use the same preference list for the Navigator APIs and the HTTP header, but privacy measures mean a server must not assume their byte-for-byte values are always identical. BT language tags obey these storage rules: - Tags are compared case-insensitively as required by BCP 47, then normalized to lowercase for URL segments, JSON suffixes, and filenames. - Subtags use ASCII hyphens (`-`). Underscores are invalid: use `pt-br`, not `pt_BR`. - Examples include `en`, `zh-hans`, `zh-hant`, `pt-br`, and `sr-latn-rs`. - `zh-cn` is a region-qualified Chinese tag and `zh-hans` is a script-qualified Chinese tag. They are not generally interchangeable. The current website redirects its historical `zh-cn` URL alias to `zh-hans`; this is a website compatibility rule, not a general BCP 47 equivalence. - The current public website supports the explicit URL locales `en` and `zh-hans`. Metadata for another valid tag can be prepared, but it is displayed only after that locale is added to the website route allowlist. Case normalization is mandatory because the production filesystem can be case-sensitive. `readme.zh-Hans.md`, `readme.ZH-HANS.md`, and `readme.zh-hans.md` would otherwise be different files on Linux. The registry accepts one canonical stored spelling: `readme.zh-hans.md`. ## Website language selection An explicit localized URL such as `/en/ext/sqlite` or `/zh-hans/ext/sqlite` always determines the rendered language. It is not changed by JavaScript, cookies, or request headers. Only a request to the root `/` performs language negotiation, in this order: 1. A supported `bt_locale` cookie wins. 2. Otherwise the server reads the HTTP `Accept-Language` header, honors valid `q` weights, retains header order for equal weights, and selects a supported website locale. 3. If no supported language matches, the website uses `en`. The website does not execute `navigator.language` to select the initial route. The browser normally creates `Accept-Language` from its language preferences and sends it with the HTTP request, allowing the server to redirect before HTML and JavaScript load. Current Chinese language ranges map to the available `zh-hans` site locale; English ranges and `*` map to `en`. A `q=0` range is not acceptable. Responses from explicit localized routes set the corresponding `Content-Language` value. ## Syntax ```json { "schema_version": 2, "name": "sqlite", "summary": "SQLite 文件数据库扩展", "summary.en": "SQLite file database extension", "description": "为 BT 项目提供 SQLite 访问能力。", "description.en": "Provides SQLite access for BT projects.", "author": "BT Team", "developer": { "id": "bt_official", "name": "BT 官方", "name.en": "BT Official", "homepage": "https://btlang.org" }, "repository": "https://github.com/bt-lang/bt/tree/main/extension/sqlite", "license": "MIT OR Apache-2.0", "latest": "1.0.0", "versions": [] } ``` Localized JSON property names contain a literal dot. Consumers must use bracket lookup such as `info['summary.' + locale]`; `info.summary.zh-hans` does not mean the same thing. ## `info.json` fields | Field | Type | Required | Default | Valid value | Meaning | | ------ | ------ | ------ | ------ | ------ | ------ | | `schema_version` | Int | Yes | None | `2` | Registry metadata schema. | | `name` | String | Yes | None | Lowercase extension identifier | Stable machine name; never localized. | | `summary` | String | Yes | None | Non-empty text in the publisher's chosen default language | Default short description. | | `summary.` | String | No | `summary` | Non-empty text; locale is normalized BCP 47 | Exact localized short description. | | `description` | String | Yes | None | Non-empty text in the publisher's chosen default language | Default full description. | | `description.` | String | No | `description` | Non-empty text; locale is normalized BCP 47 | Exact localized full description. | | `author` | String | Yes | None | Non-empty text | Package author fallback; not selected by locale. | | `developer` | Object | Yes | None | Fields below | Publisher identity. | | `repository` | String | Yes | None | Public HTTPS URL | Source repository for this release. | | `license` | String | Yes | None | SPDX expression | Package license. | | `latest` | String | Yes | None | Published SemVer | Latest non-yanked version. | | `versions` | Array[Object] | Yes | None | At least one published version | Version and package records. | ### `developer` fields | Field | Type | Required | Default | Valid value | Meaning | | ------ | ------ | ------ | ------ | ------ | ------ | | `id` | String | Yes | None | Stable publisher identifier | Machine identity; never localized. | | `name` | String | Yes | None | Non-empty text in the publisher's chosen default language | Default publisher display name. | | `name.` | String | No | `name` | Non-empty text; normalized BCP 47 suffix | Exact localized display name. | | `homepage` | String | Yes | None | Public HTTPS URL | Publisher homepage. | ### `versions` item fields | Field | Type | Required | Default | Valid value | Meaning | | ------ | ------ | ------ | ------ | ------ | ------ | | `version` | String | Yes | None | Three-part SemVer | Published extension version. | | `file` | String | Yes | None | `-.bts` | Download filename. | | `download_url` | String | Yes | None | Registry HTTPS API URL | Canonical download endpoint. | | `sha256` | String | Yes | None | 64 lowercase hexadecimal characters | Exact package digest. | | `size` | Int | Yes | None | Positive byte count | Exact package size. | | `bt_min_version` | String | Yes | None | Three-part SemVer | Oldest compatible BT runtime. | | `kind` | String | Yes | None | `bt` or `wasm` | Extension backend. | | `abi` | String | Yes | None | ABI supported by the selected kind | Runtime calling contract. | | `created_at` | String | Yes | None | Registry publication timestamp | Publication time. | | `yanked` | Bool | Yes | `false` | `true` or `false` | Whether new installation should be discouraged. | | `downloads` | Int | Yes | `0` | Nonnegative | Recorded installation count. | | `permissions` | Array[String] | Yes | `[]` | Manifest permission identifiers | Capabilities shown before installation. | | `exports` | Array[Object] | Yes | `[]` | `name` and `returns` per item | Public global entries; identifiers are not localized. | | `objects` | Array[Object] | Yes | `[]` | `name` and `methods` per item | Public object API summary; identifiers are not localized. | ## README localization The publisher chooses the language of `readme.md`. Localized files add a normalized dotted suffix: ```text readme.md readme.en.md readme.zh-hans.md readme.zh-hant.md readme.ja.md readme.pt-br.md ``` For `/en/ext/`, the renderer tries `readme.en.md` and then `readme.md`. For `/zh-hans/ext/`, it tries `readme.zh-hans.md` and then `readme.md`. It performs an exact normalized-tag lookup and then the publisher's default fallback; it does not silently truncate `pt-br` to `pt`. The same rule applies to `summary.`, `description.`, and `developer.name.`. `readme.md` in the website registry is separate from the uppercase `README.md` inside a `.bts` package. Production registry filenames are lowercase and exact. The legacy schema 1 fields `summary_en`, `description_en`, and `name_en` remain readable during migration, but new metadata must use schema 2 dotted keys. ## Return value and fallback The localized HTML page displays the exact localized value when present and the unsuffixed publisher-defined default value otherwise. The registry JSON API returns the complete raw metadata, including all translations, so API consumers can apply the same rule. Missing translations are not errors and do not produce empty page sections. ## Notes - Do not localize `name`, API identifiers, configuration keys, event names, versions, hashes, permissions, ABI names, filenames, or download URLs. - Translation suffixes must match the website's normalized URL locale exactly. - Language tags are case-insensitive for comparison; filenames and JSON keys use lowercase to remain deterministic on case-sensitive and case-insensitive hosts. - The root preference is only an initial redirect hint. Bookmarkable, canonical, and shared links always include an explicit language segment.