Localization
Translate the chart's text, format numbers and dates per locale, and lay the chart out right to left.
Vela renders in English by default. Set a locale and the chart formats its numbers and dates for that locale, reads its interface text from the locale packs you register, and can lay out its chrome right to left. Vela publishes packs for the languages listed below, and you can add your own. With no localization options set, a chart looks and behaves exactly as it always has.
Localization works the same on a bare chart (new Vela(...)), a
workspace and the deprecated widget: every one of them takes the options
below, exposes i18n and setLocale(), and emits locale:changed.
Set a locale
Pass a BCP 47 tag as locale:
import { Vela } from '@luxalgo/vela';
const chart = new Vela('#chart', { data: bars, locale: 'de' });Change it at runtime with setLocale(), which takes a tag or any of the localization
options. The chart re-renders its axes, chips and readouts in place, without re-running
indicators, and then emits locale:changed:
chart.on('locale:changed', ({ locale, direction }) => {
document.documentElement.lang = locale; // keep your own chrome in step
});
chart.setLocale('fr');
chart.setLocale({ locale: 'ar', direction: 'auto' });Fields you pass to setLocale() replace their current value; fields you leave out keep
theirs. Pass a field as undefined to return it to its default.
A workspace applies one localization to every cell and to its shared chrome:
import { VelaWorkspace } from '@luxalgo/vela/workspace';
const ws = new VelaWorkspace('#app', { layout: '4', locale: 'ja' });
ws.setLocale('ko'); // every cell, the shared panels, and cells a later layout createsThe options
| Option | Default | What it does |
|---|---|---|
locale | 'en' | The language and region of interface text, numbers and dates. |
direction | 'ltr' | The direction of the chrome: 'ltr', 'rtl', or 'auto' to follow the locale's script. The plot never mirrors. See Right to left. |
messages | — | Your own text for any message, on top of the registered packs. See Override any string. |
numberingSystem | 'latn' | The digits of every chart number. See Numbers and digits. |
hourCycle | 'h23' | The clock of chart times: 'h23' (14:30), 'h12' (2:30 PM), or 'locale'. |
weekStart | the locale's | The first day of the week in date pickers: 0 Sunday, 1 Monday, 6 Saturday. |
formatters | — | Your own formatting for prices, percents, volumes, times and durations. See Replace a formatter. |
What follows the locale
- Numbers and dates on the chart: the price axis (price, percent, indexed and volume scales), the last-price label and its countdown, the time axis, the crosshair's price and time chips, the values in the legend, the data window and the status line, the numbers drawing tools show, and the bottom bar's clock. Alert times in the alerts list follow the chart's clock once you set a locale.
- Calendars: the date picker in an indicator's settings names months and weekdays in the locale, starts its weeks on the locale's first day, and orders month and year the way the locale writes them.
- Interface text: the words Vela's own chrome shows or announces are messages: the top and bottom bars, menus, dialogs and panels, the built-in indicators with their inputs and plots, the drawing tools with their settings and on-chart labels, the chart settings, time zone names, key names in the shortcuts panel, toasts, and the names screen readers announce. Each message reads in English until a pack translates it, and switching the locale relabels what is on screen at once.
- What stays as it is: symbols, venues and the descriptions your data provider gives
them, script names, the names you type (a drawing's text, a group you named), and the
values a chart saves. A saved layout keeps an
indicator's option as
'Week'whatever language its menu showed it in, so a layout opens the same in every language. - The page: the chart's root element carries
langonce you set a locale, anddironce you set a direction. Without them the attributes stay off and your page decides.
Use a shipped language
Vela publishes a locale pack for each of these 19 languages, each imported as
@luxalgo/vela/locales/<tag>:
| Language | Native name | Tag |
|---|---|---|
| Arabic | العربية | ar |
| Chinese (Simplified) | 简体中文 | zh-Hans |
| Chinese (Traditional) | 繁體中文 | zh-Hant |
| French | Français | fr |
| German | Deutsch | de |
| Hebrew | עברית | he |
| Hindi | हिन्दी | hi |
| Indonesian | Bahasa Indonesia | id |
| Italian | Italiano | it |
| Japanese | 日本語 | ja |
| Korean | 한국어 | ko |
| Malay | Bahasa Melayu | ms |
| Polish | Polski | pl |
| Portuguese (Brazil) | Português (Brasil) | pt-BR |
| Russian | Русский | ru |
| Spanish | Español | es |
| Thai | ภาษาไทย | th |
| Turkish | Türkçe | tr |
| Vietnamese | Tiếng Việt | vi |
Each pack covers the whole interface: the chrome, the built-in indicators, the drawing tools, the chart settings and the time zone names. Names stay as written in every language: Vela, LuxAlgo, PineTS, Pine Script®, tickers, and the indicators the trading literature knows by their English name. Numbers keep Western digits.
Import a pack, register it, and switch to its locale:
import { registerLocale } from '@luxalgo/vela/plugin';
import es from '@luxalgo/vela/locales/es';
registerLocale(es);
chart.setLocale('es');Importing a pack never registers it. Each pack is its own entry point, so a page loads only the languages it imports. To load a language when the user picks it:
const { default: pack } = await import('@luxalgo/vela/locales/ru');
registerLocale(pack);
ws.setLocale(pack.locale);The Arabic and Hebrew packs carry direction: 'rtl'. Pass direction: 'auto' with their
locale and the chrome mirrors, while the plot keeps reading left to right; see
Right to left:
import ar from '@luxalgo/vela/locales/ar';
registerLocale(ar);
chart.setLocale({ locale: 'ar', direction: 'auto' });You can pass the browser's own language tag (navigator.language) as it is: a regional tag
reads the closest pack you registered. zh-CN and zh-SG read Simplified Chinese, zh-TW,
zh-HK and zh-MO Traditional Chinese, es-MX and es-419 Spanish, pt and pt-PT
Brazilian Portuguese, ar-EG Arabic, and the legacy tags iw and in read Hebrew and
Indonesian. Numbers and dates still follow the tag itself, so pt-PT keeps Portugal's
formats.
With a pack active, the indicator picker finds an indicator by its translated name, by its English name and by its short name, so a search for "Relative Strength" or "RSI" works in every language.
The translations are machine-drafted, then checked in an independent review pass. If a
word reads wrong in your language, corrections are welcome: open a pull request against the
language's file in src/locales/ (for example src/locales/es.json). To reword a message
in your own app instead, use messages, or register your own pack
for the same locale after Vela's: for the same message, the pack registered last wins.
Add your own language
A locale pack is one plain object: a locale tag and its messages, keyed by message id. Register it before you switch to its locale:
import { registerLocale } from '@luxalgo/vela/plugin';
const off = registerLocale({
locale: 'nl',
messages: {
'widget.shortcuts.title': 'Sneltoetsen',
'widget.shortcuts.mouse': 'Muis',
'ui.datePicker.nextMonth': 'Volgende maand',
},
});
chart.setLocale('nl');
// Later, to remove the pack again:
off();Registration is always a call: importing a pack never registers it. A pack only needs the
messages it translates. A message missing from a pack falls back through the locale's
parents to English: pt-BR reads pt-BR, then pt, then English; zh-TW reads zh-TW,
then zh-Hant, then zh. After the bare language comes the region it usually means, when
that region writes the same script: pt-PT reads pt-PT, pt, then pt-BR; zh reads
zh-Hans, then zh-CN. Legacy tags read as their current ones (iw as he, in as id).
Several packs can share a locale (Vela's, a plugin's, your own); for the same message, the
pack registered last wins.
Start a translation from the English reference pack, which lists every message id with its English text:
import en from '@luxalgo/vela/locales/en';
console.log(en.messages); // { 'ui.datePicker.chooseMonth': 'Choose month', … }Message syntax
Messages use a subset of ICU message syntax:
| Form | Example |
|---|---|
| A value | Delta {value} |
| A number | {count, number}, {count, number, integer}, {share, number, percent} |
| Plurals | {count, plural, one {# bar} other {# bars}}, with =0 {…} exact matches and offset:1 |
| Ordinals | {n, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} |
| A choice | {side, select, buy {Bought} sell {Sold} other {Traded}} |
Plurals follow each language's own categories: Arabic has six (zero, one, two,
few, many, other), Polish and Russian four. An apostrophe is plain text
(l'indicateur); to show a literal brace, quote it: '{'. A translation that fails to
parse falls back to the English text instead of breaking the chart.
Override any string
The messages option replaces any message for the current locale, on top of the registered
packs. It works in English too, to reword Vela's own text:
const chart = new Vela('#chart', {
messages: { 'widget.shortcuts.title': 'Shortcuts' },
});Message ids are typed: a misspelled id is a compile error. A plugin or host that declares its own messages adds their ids to the catalog so they type-check too:
declare module '@luxalgo/vela/plugin' {
interface VelaMessageCatalog {
'acme.panel.title': string;
}
}Right to left
Set direction: 'rtl', or 'auto' to follow the locale: Arabic, Hebrew, Persian, Urdu and
the other right-to-left scripts resolve to 'rtl'. The direction lands as dir on the
chart's root element and on the workspace root, and the chrome under them follows it.
The plot never mirrors, whatever the direction. Time runs left to right with the newest bar on the right, the price axis stays on the right, and the chart's arrow keys still move toward older bars on the left. Prices, percents and times read left to right.
Some chrome still positions itself for left-to-right pages and does not mirror yet. Check your layout in the right-to-left locales you ship.
Numbers and digits
Chart numbers use Western digits in every language, Arabic included, so prices read the
same for everyone. Set numberingSystem to show other digits:
import { createTranslator } from '@luxalgo/vela/plugin';
// The same options a chart takes; `chart.i18n.format` gives the same results.
const price = (options) => createTranslator(options).format.price(58732.5, { decimals: 2, grouping: true });
price({ locale: 'ar-SA' }); // 58,732.50
price({ locale: 'ar-SA', numberingSystem: 'locale' }); // ٥٨٬٧٣٢٫٥٠
price({ locale: 'hi', numberingSystem: 'deva' }); // ५८,७३२.५०'locale' uses the locale's own digits, and any numbering system id ('arab', 'deva',
'thai', …) picks one. A tag with a -u-nu- extension ('ar-u-nu-arab') picks its digits
when you leave numberingSystem unset.
Other conventions hold in every locale:
- Separators follow the locale. German reads
58.732,50, Indian English groups in lakhs (1,23,45,678.90). The price axis leaves out group separators so its labels fit the scale. - Rounding is the same everywhere. A price shows the same digits in every language; only the separators change.
- The calendar is Gregorian. Thai dates count Gregorian years, not Buddhist ones.
- The clock is 24-hour unless you set
hourCycle. - Volumes abbreviate the locale's way:
1.2Min English,120万in Japanese,1,2 Mio.in German.
Replace a formatter
Supply any of price, percent, compact, timeTick, timeStamp, duration and
clock in formatters. Each receives the value and a context: the locale, the display
timeZone, the surface the value appears on ('priceAxis', 'crosshair', 'lastPrice',
'timeAxis', 'legend', 'dataWindow', …), the formatter's own fields, and fallback(),
which returns Vela's built-in formatting. On a daily or coarser chart, timeStamp receives
timeZone: 'UTC': those bars are dates stamped at 00:00 UTC, and read the same in every zone.
new Vela('#chart', {
locale: 'de',
formatters: {
price: (value, ctx) => (ctx.surface === 'dataWindow' ? `${ctx.fallback()} €` : ctx.fallback()),
},
});A formatter that throws, or returns something other than a string, falls back to the built-in formatting.
Use the translator
chart.i18n (and ws.i18n) is the chart's translator. It holds the resolved settings
(locale, direction, numberingSystem, hourCycle, weekStart), translates messages
with t(), and formats values with format:
import { msg } from '@luxalgo/vela/plugin';
const SAVED = msg('acme.layout.saved', '{count, plural, one {# layout saved} other {# layouts saved}}');
chart.i18n.t(SAVED, { count: 3 }); // "3 layouts saved"
chart.i18n.t('Plain text'); // plain strings come back unchanged
chart.i18n.format.price(58732.5, { decimals: 2, grouping: true });
chart.i18n.format.timeStamp(Date.now(), { timeZone: 'Asia/Tokyo' });The translator is the same object for the chart's lifetime and reads the new settings after
setLocale(), so you can keep it. Contributed actions and panels get the shell's translator
as ctx.i18n, and renderer layers as args.i18n. For UI you build outside a chart,
createTranslator(options) makes a standalone one.
Label what you contribute
The labels you hand Vela accept a message as well as a plain string, and Vela shows them in
the shell's language: a contributed action's label, a side panel's title, a legend
action's tooltip, a legend callout's text, a registered layout's label, a key binding's
label and category, an indicator input's display (its title, tooltip, group and the
names of its options), and a drawing type's name. Messages go in only: what you read back
is text, in the language of the translator you pass (English without one), such as
buildToolbar(option, chart.i18n), layouts(chart.i18n), layoutDefinition(id, chart.i18n),
drawingTypes(chart.i18n), keymap.bindings(chart.i18n) and
chart.availableNativeIndicators(). Text you keep goes stale when the language changes, so
read it again on locale:changed.
import { msg, registerWidgetAction } from '@luxalgo/vela/plugin';
registerWidgetAction({
id: 'acme.export',
target: 'topbar',
label: msg('acme.export.label', 'Export trades'),
run: (ctx) => exportTrades(ctx),
});Keyboards and scripts
Shortcuts name Latin keys, and they keep working on keyboard layouts of other scripts:
Ctrl+Z undoes on a Russian layout, where that key types я. A Latin layout keeps its own
letters wherever they sit. Typing a letter of any script on the chart opens symbol search,
and while an input method composes a character (Japanese, Chinese, Korean), its keystrokes
stay with it. Text drawn on the chart wraps between words in scripts written without spaces,
such as Thai, Chinese and Japanese.
For contributors
Declare new interface text with msg('area.component.key', 'English text') and resolve it
through a translator. Never decide anything from display text: keep identities (ids, option
values, saved names) in English and show them through a message. Then run:
npm run i18n:extractIt collects every msg() call into src/locales/en.json, the English catalog translations
start from, and into src/locales/catalog.d.ts, the type that checks message ids. The tests
fail when either file is out of date, or when a pack under src/locales/ names an unknown
message or changes a message's arguments.
The shipped packs live beside it, one <tag>.json of messages and one <tag>.ts entry point
per language; the build publishes each as @luxalgo/vela/locales/<tag>. You don't need to
translate the text you add: a message a pack lacks reads in English until a translation
lands, and npm run i18n:check lists how many messages each pack translates. The pack tests
fail when a pack names an unknown message, when one of its messages doesn't parse, takes
other values than the English, lacks a plural form its language needs, drops a product name
or writes other digits than 0 to 9, and when a workspace in a shipped language shows English
that comes from no message at all.