⚡️
Qwik Speak
Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps
Live example on StackBlitz
Speak context
stateDiagram-v2
State1: SpeakState
State2: SpeakLocale
State3: Translation
State4: SpeakConfig
State5: TranslateFn
State1 --> State2
State1 --> State3
State1 --> State4
State1 --> State5
note right of State2
Creates subscriptions
and rerenders components with translations
end note
note right of State3: Immutable
note right of State4: Immutable
note right of State5
Immutable
QRL functions to be serializable
end note
Usage
Getting started
npm install qwik-speak --save-dev
Getting the translation
import { $translate as t, plural as p } from 'qwik-speak';
export default component$(() => {
return (
<>
<p>{t('app.title', { name: 'Qwik Speak' })}</p> {/* I'm Qwik Speak */}
<p>{p(1, 'app.devs')}</p> {/* 1 software developer */}
</>
);
});
Getting dates, relative time & numbers
import { formatDate as fd, relativeTime as rt, formatNumber as fn } from 'qwik-speak';
export default component$(() => {
return (
<>
<p>{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}</p> {/* Wednesday, July 20, 2022 at 7:09 AM */}
<p>{rt(-1, 'day')}</p> {/* 1 day ago */}
<p>{fn(1000000, { style: 'currency' })}</p> {/* $1,000,000.00 */}
</>
);
});
Configuration
import { SpeakConfig } from 'qwik-speak';
export const config: SpeakConfig = {
defaultLocale: { lang: 'en-US', currency: 'USD', timeZone: 'America/Los_Angeles' },
supportedLocales: [
{ lang: 'it-IT', currency: 'EUR', timeZone: 'Europe/Rome' },
{ lang: 'en-US', currency: 'USD', timeZone: 'America/Los_Angeles' }
],
assets: [
'app', // Shared
]
};
Assets will be loaded through the implementation of loadTranslation$
function below. You can load json files or call an endpoint to return a Translation
object for each language:
{
"app": {
"title": "I'm {{name}}",
"devs": {
"one": "{{value}} software developer",
"other": "{{value}} software developers"
}
}
}
Custom APIs
import { $ } from '@builder.io/qwik';
export const loadTranslation$: LoadTranslationFn = $((lang: string, asset: string, url?: URL) => {
/* Must contain the logic to load translation data */
// E.g. Fetch translation data from json files in public dir or i18n/[lang]/[asset].json endpoint
let endpoint = '';
// Absolute urls on server
if (isServer && url) {
endpoint = url.origin;
}
endpoint += `/i18n/${lang}/${asset}.json`;
const data = await fetch(endpoint);
return data.json();
});
export const resolveLocale$: ResolveLocaleFn = $((url?: URL) => {
/* Must contain the logic to resolve which locale to use during SSR */
});
export const storeLocale$: StoreLocaleFn = $((locale: SpeakLocale, url?: URL) => {
/* Must contain the logic to store the locale on Client when changes */
});
export const handleMissingTranslation$: HandleMissingTranslationFn = $((key: string, value?: string, params?: any, ctx?: SpeakState) => {
/* Must contain the logic to handle missing values: by default returns the key */
});
export const translateFn: TranslateFn = {
loadTranslation$: loadTranslation$,
/* other functions */
};
Add the QwikSpeak
component in root.tsx
:
import { QwikSpeak } from 'qwik-speak';
export default component$(() => {
return (
/**
* Init Qwik Speak (only available in child components)
*/
<QwikSpeak config={config} translateFn={translateFn}>
<QwikCity>
<Head />
<body>
<RouterOutlet />
</body>
</QwikCity>
</QwikSpeak>
);
});
Lazy loading of translation data
Use the Speak
component to add translation data to the context:
import { Speak } from 'qwik-speak';
export default component$(() => {
return (
/**
* Add Home translation (only available in child components)
*/
<Speak assets={['home']}>
<Home />
</Speak>
);
});
Additional languages
import { Speak } from 'qwik-speak';
export default component$(() => {
return (
<Speak assets={['home']} langs={['en-US']}>
<Home />
</Speak>
);
});
The translation data of the additional languages are preloaded along with the current language. They can be used as a fallback for missing values by implementing handleMissingTranslation$
below, or for multilingual pages.
Production
Using a server
Translation happens at runtime: translations are downloaded during SSR or on client, and the lookup also happens at runtime.
Static Site Generation (SSG)
Using SSG offered by Qwik City, translations can be inlined at build time.
What you need:
- A
lang
parameter in the root, like:routes │ └───[...lang] │ index.html │ └───page index.html
- Handle the localized routing in
resolveLocale$
andstoreLocale$
- Qwik City Static Site Generation config and dynamic routes
See the sample app
Speak config
-
defaultLocale
The default locale -
supportedLocales
Supported locales -
assets
An array of strings: each asset is passed to theloadTranslation$
function to obtain data according to the language -
keySeparator
Separator of nested keys. Default is.
-
keyValueSeparator
Key-value separator. Default is@@
The default value of a key can be passed directly into the string:
t("app.title@@I'm {{name}}")
The SpeakLocale
object contains the lang
, in the format language[-script][-region]
, where:
language
: ISO 639 two-letter or three-letter codescript
: ISO 15924 four-letter script coderegion
ISO 3166 two-letter, uppercase code
and optionally contains:
extension
Language with Intl extensions, in the formatlanguage[-script][-region][-extensions]
likeen-US-u-ca-gregory-nu-latn
to format dates and numberscurrency
ISO 4217 three-letter codetimezone
From the IANA time zone databaseunits
Key value pairs of unit identifiers
APIs
Functions
-
$translate(keys: string | string[], params?: any, ctx?: SpeakState, lang?: string)
Translates a key or an array of keys. The syntax of the string iskey@@[default value]
-
plural(value: number | string, prefix?: string, options?: Intl.PluralRulesOptions, ctx?: SpeakState, lang?: string)
Gets the plural by a number -
formatDate(value: Date | number | string, options?: Intl.DateTimeFormatOptions, locale?: SpeakLocale, lang?: string, timeZone?: string)
Formats a date -
relativeTime(value: number | string, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions, locale?: SpeakLocale, lang?: string)
Format a relative time -
formatNumber(value: number | string, options?: Intl.NumberFormatOptions, locale?: SpeakLocale, lang?: string, currency?: string)
Formats a number -
changeLocale(newLocale: SpeakLocale, ctx: SpeakState, url?: URL)
Changes locale at runtime: loads translation data and rerenders components that uses translations
Speak context
-
useSpeakContext()
Returns the Speak context -
useSpeakLocale()
Returns the locale in Speak context -
useTranslation()
Returns the translation data in Speak context -
useSpeakConfig()
Returns the configuration in Speak context
Development Builds
Build the library
npm run build
Test the library
npm test
npm run test.e2e
Run the sample app
npm start
Build the sample app
npm run build.app
Express server
npm run serve
Static Site Generation (SSG)
With an Express server running to provide http requests, execute in another Terminal:
npm run ssg
Since the sample app implements a localized routing, the command will download the translations at compile-time and generate a single app with the localized paths and inlined translations:
dist
│ index.html
│
└───page
│ index.html
│
└───it-IT
│ index.html
│
└───page
index.html
Translations are also serialized and made available at runtime.
npm run serve.ssg
What's new
Released v0.0.13
- Added
relativeTime
function - Added default value to strings:
key@@[default value]
License
MIT