Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions docs/usage/i18n.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
# Internationalization (i18n)

Swagger UI ships with a built-in internationalization system based on a lightweight key/message lookup. No additional third-party i18n libraries are required.

---

## Quick start

### Display the UI in a specific language

Pass a `locale` option when initialising Swagger UI. Swagger UI ships built-in translations for several languages — when a matching locale is detected or configured, those translations are loaded automatically with no extra setup:

```js
const ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: "#swagger-ui",
locale: "fr", // French translations are loaded automatically
})
```

### Auto-detect the browser language

Set `locale` to `null` (the default) and Swagger UI will read `navigator.languages[0]` / `navigator.language` and normalise it to a BCP 47 base language code (e.g. `"fr-CA"` → `"fr"`). If a built-in or user-registered locale matches, it is used; otherwise the UI falls back to English.

```js
SwaggerUIBundle({
url: "...",
dom_id: "#swagger-ui",
locale: null, // default — auto-detect from browser
})
```

---

## Built-in locales

The following locales are bundled with Swagger UI and loaded automatically when the detected or configured locale matches:

| Code | Language |
|------|----------|
| `ca` | Catalan (Català) |
| `de` | German (Deutsch) |
| `en` | English *(default / fallback)* |
| `es` | Spanish (Español) |
| `fr` | French (Français) |
| `is` | Icelandic (Íslenska) |
| `it` | Italian (Italiano) |
| `ja` | Japanese (日本語) |
| `ka` | Georgian (ქართული) |
| `ko` | Korean (한국어) |
| `pl` | Polish (Polski) |
| `pt` | Portuguese / Brazilian Portuguese (Português do Brasil) |
| `ru` | Russian (Русский) |
| `tr` | Turkish (Türkçe) |
| `zh` | Chinese Simplified (简体中文) |

All locale files are located in `src/core/plugins/i18n/locales/`.

### Adding or extending translations

Call `loadMessages` at any time to register additional keys or override existing ones. Partial overrides are supported — any key absent from the registered messages falls through to the English fallback:

```js
const ui = SwaggerUIBundle({ url: "...", dom_id: "#swagger-ui", locale: "fr" })

// Override a subset of French translations
ui.i18nActions.loadMessages("fr", {
"button.execute": "Lancer",
"button.try_it_out": "Tester",
})

// Switch locale at runtime
ui.i18nActions.setLocale("de")
```

---

## Configuration options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `locale` | `string \| null` | `null` | BCP 47 language tag (e.g. `"en"`, `"fr"`, `"de"`). `null` means auto-detect from the browser. |

---

## Key catalog

All translatable strings are identified by dot-namespaced keys. The full catalog is defined in [`src/core/plugins/i18n/locales/en.js`](../../src/core/plugins/i18n/locales/en.js).

### Buttons / actions

| Key | Default (English) |
|-----|-------------------|
| `button.authorize` | Authorize |
| `button.cancel` | Cancel |
| `button.clear` | Clear |
| `button.close` | Close |
| `button.copy_to_clipboard` | Copy to clipboard |
| `button.download_file` | Download file |
| `button.edit` | Edit |
| `button.execute` | Execute |
| `button.explore` | Explore |
| `button.hide` | Hide |
| `button.logout` | Logout |
| `button.reset` | Reset |
| `button.show` | Show |
| `button.try_it_out` | Try it out |

### Section / column labels

| Key | Default (English) |
|-----|-------------------|
| `label.callbacks` | Callbacks |
| `label.code` | Code |
| `label.description` | Description |
| `label.details` | Details |
| `label.examples` | Examples |
| `label.headers` | Headers: |
| `label.links` | Links |
| `label.media_type` | Media type |
| `label.models` | Models |
| `label.name` | Name |
| `label.no_links` | No links |
| `label.no_parameters` | No parameters |
| `label.parameter_content_type` | Parameter content type |
| `label.parameters` | Parameters |
| `label.request_body` | Request body |
| `label.request_duration` | Request duration |
| `label.request_url` | Request URL |
| `label.response_body` | Response body |
| `label.response_content_type` | Response content type |
| `label.response_headers` | Response headers |
| `label.responses` | Responses |
| `label.schemas` | Schemas |
| `label.server_response` | Server response |
| `label.snippets` | Snippets |
| `label.type` | Type |
| `label.undocumented` | Undocumented |

### Authentication

| Key | Default (English) |
|-----|-------------------|
| `auth.api_key_in` | In: |
| `auth.api_key_name` | Name: |
| `auth.api_key_value` | Value: |
| `auth.application` | Application: |
| `auth.authorization_header` | Authorization header |
| `auth.authorization_url` | Authorization URL: |
| `auth.authorized` | Authorized |
| `auth.basic_authorization_title` | Basic authorization |
| `auth.client_credentials_location` | Client credentials location: |
| `auth.flow` | Flow: |
| `auth.openid_connect_url` | OpenID Connect URL: |
| `auth.password` | password: |
| `auth.password_cap` | Password: |
| `auth.request_body_option` | Request body |
| `auth.scopes_description` | Scopes are used to grant … |
| `auth.scopes_required` | API requires the following scopes … |
| `auth.scopes_title` | Scopes: |
| `auth.select_all` | select all |
| `auth.select_none` | select none |
| `auth.token_url` | Token URL: |
| `auth.username` | username: |
| `auth.username_cap` | Username: |

### Accessibility (aria-labels / titles)

| Key | Default (English) |
|-----|-------------------|
| `aria.apply_credentials` | Apply credentials |
| `aria.apply_oauth2_credentials` | Apply given OAuth2 credentials |
| `aria.authorization_button_locked` | authorization button locked |
| `aria.authorization_button_unlocked` | authorization button unlocked |
| `aria.collapse_operation` | Collapse operation |
| `aria.expand_operation` | Expand operation |
| `aria.remove_authorization` | Remove authorization |
| `aria.request_content_type` | Request content type |
| `aria.response_content_type` | Response content type |

### Errors

| Key | Default (English) |
|-----|-------------------|
| `errors.jump_to_line` | Jump to line {{line}} |
| `errors.title` | Errors |

### Placeholders

| Key | Default (English) |
|-----|-------------------|
| `placeholder.filter_by_tag` | Filter by tag |

### Response

| Key | Default (English) |
|-----|-------------------|
| `response.controls_accept_header_prefix` | Controls |
| `response.controls_accept_header_suffix` | header. |
| `response.json_parse_error` | Can't parse JSON. Raw result:\n\n |
| `response.no_blob_support` | Download headers detected but your browser does not support downloading binary via XHR (Blob). |
| `response.unrecognized_type_display_as_text` | Unrecognized response type; displaying content as text. |
| `response.unrecognized_type_unable_to_display` | Unrecognized response type; unable to display. |

### Topbar

| Key | Default (English) |
|-----|-------------------|
| `topbar.select_definition` | Select a definition |

---

## Variable interpolation

Some messages contain `{{varName}}` placeholders that are replaced at runtime:

| Key | Variable |
|-----|----------|
| `errors.jump_to_line` | `line` — the line number |

Example override with interpolation preserved:

```js
ui.i18nActions.loadMessages("de", {
"errors.jump_to_line": "Gehe zu Zeile {{line}}"
})
```

---

## Programmatic API

The i18n system is exposed through the Redux system just like any other plugin.

### Dispatch actions

```js
// Change the active locale at runtime
getSystem().i18nActions.setLocale("de")

// Register or extend a locale's messages
getSystem().i18nActions.loadMessages("de", {
"button.execute": "Ausführen"
})
```

### Call the translation function directly

```js
const t = getSystem().t
console.log(t("button.execute")) // "Execute" (or translated equivalent)
console.log(t("errors.jump_to_line", { line: 42 })) // "Jump to line 42"
```

---

## Architecture notes

- The i18n system is implemented as a Redux **plugin** (`I18nPlugin`) registered early in the base preset, making it available to all other plugins and components.
- The `t(key, vars?)` function is injected into `rootInjects`, so every connected component receives it as a direct prop alongside `getComponent`, `getConfigs`, etc.
- The **English locale** (`locales/en.js`) is the canonical catalog and ultimate fallback. If a key is absent from the active locale messages, the English string is used. If the key is absent from English too, the raw key string is returned.
- Locale is normalised to the BCP 47 base language code (`"fr-CA"` → `"fr"`).
- All existing config options and behaviour remain unchanged when no `locale` is supplied.
18 changes: 12 additions & 6 deletions src/core/components/auth/api-key-auth.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react"
import PropTypes from "prop-types"
import { fallbackT } from "core/plugins/i18n/fn"

export default class ApiKeyAuth extends React.Component {
static propTypes = {
Expand All @@ -9,7 +10,12 @@ export default class ApiKeyAuth extends React.Component {
schema: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
authSelectors: PropTypes.object.isRequired
authSelectors: PropTypes.object.isRequired,
t: PropTypes.func,
}

static defaultProps = {
t: fallbackT,
}

constructor(props, context) {
Expand Down Expand Up @@ -40,7 +46,7 @@ export default class ApiKeyAuth extends React.Component {
}

render() {
let { schema, getComponent, errSelectors, name, authSelectors } = this.props
let { schema, getComponent, errSelectors, name, authSelectors, t } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
Expand All @@ -57,18 +63,18 @@ export default class ApiKeyAuth extends React.Component {
<code>{ name || schema.get("name") }</code>&nbsp;(apiKey)
<JumpToPath path={path} />
</h4>
{ value && <h6>Authorized</h6>}
{ value && <h6>{t("auth.authorized")}</h6>}
<Row>
<Markdown source={ schema.get("description") } />
</Row>
<Row>
<p>Name: <code>{ schema.get("name") }</code></p>
<p>{t("auth.api_key_name")} <code>{ schema.get("name") }</code></p>
</Row>
<Row>
<p>In: <code>{ schema.get("in") }</code></p>
<p>{t("auth.api_key_in")} <code>{ schema.get("in") }</code></p>
</Row>
<Row>
<label htmlFor="api_key_value">Value:</label>
<label htmlFor="api_key_value">{t("auth.api_key_value")}</label>
{
value ? <code> ****** </code>
: <Col>
Expand Down
12 changes: 9 additions & 3 deletions src/core/components/auth/authorize-btn.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import React from "react"
import PropTypes from "prop-types"
import { fallbackT } from "core/plugins/i18n/fn"

export default class AuthorizeBtn extends React.Component {
static propTypes = {
onClick: PropTypes.func,
isAuthorized: PropTypes.bool,
showPopup: PropTypes.bool,
getComponent: PropTypes.func.isRequired
getComponent: PropTypes.func.isRequired,
t: PropTypes.func,
}

static defaultProps = {
t: fallbackT,
}

render() {
let { isAuthorized, showPopup, onClick, getComponent } = this.props
let { isAuthorized, showPopup, onClick, getComponent, t } = this.props

//must be moved out of button component
const AuthorizationPopup = getComponent("authorizationPopup", true)
Expand All @@ -20,7 +26,7 @@ export default class AuthorizeBtn extends React.Component {
return (
<div className="auth-wrapper">
<button className={isAuthorized ? "btn authorize locked" : "btn authorize unlocked"} onClick={onClick}>
<span>Authorize</span>
<span>{t("button.authorize")}</span>
{isAuthorized ? <LockAuthIcon /> : <UnlockAuthIcon />}
</button>
{ showPopup && <AuthorizationPopup /> }
Expand Down
12 changes: 9 additions & 3 deletions src/core/components/auth/authorize-operation-btn.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React from "react"
import PropTypes from "prop-types"
import { fallbackT } from "core/plugins/i18n/fn"

export default class AuthorizeOperationBtn extends React.Component {
static propTypes = {
isAuthorized: PropTypes.bool.isRequired,
onClick: PropTypes.func,
getComponent: PropTypes.func.isRequired
getComponent: PropTypes.func.isRequired,
t: PropTypes.func,
}

static defaultProps = {
t: fallbackT,
}

onClick =(e) => {
Expand All @@ -18,14 +24,14 @@ export default class AuthorizeOperationBtn extends React.Component {
}

render() {
let { isAuthorized, getComponent } = this.props
let { isAuthorized, getComponent, t } = this.props

const LockAuthOperationIcon = getComponent("LockAuthOperationIcon", true)
const UnlockAuthOperationIcon = getComponent("UnlockAuthOperationIcon", true)

return (
<button className="authorization__btn"
aria-label={isAuthorized ? "authorization button locked" : "authorization button unlocked"}
aria-label={isAuthorized ? t("aria.authorization_button_locked") : t("aria.authorization_button_unlocked")}
onClick={this.onClick}>
{isAuthorized ? <LockAuthOperationIcon className="locked" /> : <UnlockAuthOperationIcon className="unlocked"/>}
</button>
Expand Down
Loading