Blazing-fast, dependency-free i18n library for Vue 3, React and modern JS/TS apps.
Uses native Intl APIs and modern features for blazing performance, dynamic locale loading, and type-safe keys.
- Higher speed — no unnecessary dependencies, works on pure
Intl API. - Minimal size — lightweight and compact code.
- TypeScript support —
type-safetranslation keys and autocomplete. - Dynamic loading and caching — convenient for working with large projects.
- Easy integration —
Reactplugin andVue 3plugin with provide/inject and hooks - Full support — For
pluralandformatting—numbers,dates,currencies,units.
- Native Intl APIs:
Intl.NumberFormat,Intl.DateTimeFormat,Intl.PluralRules,Intl.RelativeTimeFormat - Language detection (
localStorage,cookie,browser language) - Dynamic locale loading via
ESMdynamic import - Vue 3 plugin with
provide/injectand global$tfunction - TypeScript-friendly with
type-safetranslation keys and autocompletion
npm install swift-i18nCreate a locales folder in your src directory:
src/
├─ locales/
│ ├─ en.json
│ └─ uk.json{
"common": {
"hello": "Hello!",
"items_one": "{count} item",
"items_other": "{count} items"
},
"home": {
"title": "Welcome",
"description": "This is the home page"
}
}import { createApp } from 'vue';
import App from './App.vue';
import { createSwiftI18n } from 'swift-i18n/vue-plugin';
const app = createApp(App);
const i18n = await createSwiftI18n({
defaultLang: 'en',
supportedLangs: ['en', 'uk'],
loader: async (lang) => {
const module = await import(`./locales/${lang}.json`)
return module.default
}
});
app.use(i18n);
app.mount('#app');<script setup lang="ts">
import { useI18n } from 'swift-i18n/vue-plugin';
const { t, plural, changeLanguage, lang } = useI18n();
</script>
<template>
<h1>Current language: {{ lang }}</h1>
<div>{{ t('common.hello') }}</div>
<div>{{ plural('common.items', 5) }}</div>
<button @click="changeLanguage('uk')">UK</button>
<button @click="changeLanguage('en')">EN</button>
</template>import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import { createSwiftI18n } from 'swift-i18n/react-plugin';
async function bootstrap() {
const I18nProvider = await createSwiftI18n({
defaultLang: 'en',
supportedLangs: ['en', 'uk'],
loader: async (lang) => {
const module = await import(`./locales/${lang}.json`)
return module.default
}
});
createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<I18nProvider>
<App />
</I18nProvider>
</React.StrictMode>
)
}
bootstrap()import React from 'react';
import { useI18n } from 'swift-i18n/react-plugin';
export default function App() {
const { t, lang, changeLanguage, plural } = useI18n();
return (
<>
<div>{t('common.hello')}</div>
<div>{plural('common.items', 3)}</div>
<button onClick={() => changeLanguage('uk')}>🇺🇦</button>
<button onClick={() => changeLanguage('en')}>🇬🇧</button>
<p>Current lang: {lang}</p>
</>
);
}import { formatCurrency, formatDate, formatRelativeTime, formatNumber, formatUnit } from 'swift-i18n';
formatNumber(1234567.89, 'en-US'); // "1,234,567.89"
formatCurrency(1234.5, 'USD', 'en-US'); // "$1,234.50"
formatUnit(10, 'kilometer-per-hour', 'en-US'); // "10 km/h"
formatDate(new Date(), 'en-US'); // "8/11/2025"
formatRelativeTime(-2, 'day', 'en-US'); // "2 days ago"The plural(baseKey: string, count: number, vars?: Record<string, any>) method returns the correct plural form translation:
{
"common": {
"items_one": "{count} item",
"items_few": "{count} items",
"items_many": "{count} items",
"items_other": "{count} items"
}
}plural('common.items', 1); // "1 item"
plural('common.items', 3); // "3 items"Pass variables into translations via the vars object:
{
"greeting": "Hello, {name}!"
}t('greeting', { name: 'Alice' }); // "Hello, Alice!"
plural('common.items', 5, { name: 'Alice' });fallbackLang: 'en' to choose which language to use when your preferred language lacks a translation.
Sometimes some items will not be translated into some languages. In this example, the item hello is available in English but not Japanese:
{
"en": {
"hello": "Hello, world!"
},
"ja": {
}
}If you want to use (say) en items when an item is not available in your desired locale, set the fallbackLang option in the createSwiftI18n:
const i18n = await createSwiftI18n({
defaultLang: 'ja',
fallbackLang: 'en',
loader: async (lang) => {
const module = await import(`./locales/${lang}.json`)
return module.default
}
});If there’s a locale messages key that will always have the same concrete text as another one you can just link to it.
To link to another locale messages key, all you have to do is to prefix its contents with an @:key sign followed by the full name of the locale messages key including the namespace you want to link to.
{
"en": {
"message": {
"the_world": "the world",
"dio": "DIO:",
"linked": "@:message.dio @:message.the_world !!!!"
}
}
}It’s en locale that has hierarchical structure in the object.
The message.the_world has the_world and message.dio. The message.linked has @:message.dio @:message.dio @:message.the_world !!!!, and it’s linked to the locale messages key with message.dio and message.the_world.
<p>{{ $t('message.linked') }}</p>The first argument is message.linked as the locale messages key as a parameter to t.
<p>DIO: the world !!!!</p>To help mitigate XSS risks when using HTML messages, Vue I18n provides escape parameter options. When enabled, this option escapes interpolation parameters and sanitizes the final translated HTML.
// enable `escapeParameter` globally
const i18n = createI18n({
locale: 'en',
escapeParameter: true,
})
// or enable it per translation
t('message.welcome', { name }, { escapeParameter: true })When the escape parameter option is enabled:
- HTML special characters (
<,>,",',&,/,=) in interpolation parameters are escaped - The final translated HTML is sanitized to prevent XSS attacks:
- Dangerous characters in HTML attribute values are escaped
- Event handler attributes (
onclick,onerror, etc.) are neutralized - JavaScript URLs in href, src, action, formaction, and style attributes are disabled
const input = '<img src=x onerror=alert(1)>'
// Without escape parameter (DANGEROUS):
$t('message.hello', { name: input })
// Result: Hello <strong><img src=x onerror=alert(1)></strong>!
// With escape parameter (SAFE):
$t('message.hello', { name: input }, { escapeParameter: true })
// Result: Hello <strong><img src=x onerror=alert(1)></strong>!- Translations are dynamically loaded via ESM
import() - Automatic loading when calling
changeLanguage().
Add type definitions for autocompletion:
- Create
src/types/swift-i18n.d.ts:
[Manual schema definition]
import 'swift-i18n';
declare module 'swift-i18n' {
// Merge interface — put YOUR key scheme here
interface Translations {
common: {
hello: string;
items_one: string;
items_other: string;
};
home: {
title: string;
description: string;
};
}
}[Derive schema directly from a JSON locale file]
Alternatively, you can generate the type definition automatically from an existing locale (e.g. en.json).
This approach ensures the types always stay in sync with your translation files.
import 'swift-i18n';
import en from '../locales/en.json'
type MessageSchema = typeof en;
declare module 'swift-i18n' {
interface Translations extends MessageSchema {};
}Tip
Use the manual schema if you want strict control.
Use the derived schema if you prefer automatic synchronization.
- Add to
tsconfig.json:
{
"include": [
"src/types/**/*"
]
}Tip
Supported Versions 1.3+
If you want to use a message format like ICU Message Format, you can use a custom format by implementing the message compiler yourself.
Warning
This topic requires understanding Swift I18n message format compilation and how formats are resolved.
Caution
The feature is experimental. It may receive breaking changes or be removed in the future.
You can make a message compiler by implementing functions with the following interfaces.
The following is a TypeScript type definition:
export type MessageCompiler = (
message: string | unknown,
ctx: {
locale: string;
key: string;
onError?: (err: CompileError) => void;
}
) => MessageFunction;The following is an example of a message compiler implementation that uses intl-messageformat to support the ICU Message format.
import IntlMessageFormat from "intl-messageformat";
import type { MessageCompiler, CompileError, MessageContext } from "swift-i18n";
export const messageCompiler: MessageCompiler = (
message,
{ locale, key, onError }
) => {
if (typeof message === "string") {
/**
* You can tune your message compiler performance more with your cache strategy or also memoization at here
*/
const formatter = new IntlMessageFormat(message, locale);
return (ctx: MessageContext) => {
return formatter.format(ctx.values);
};
} else {
/**
* for AST.
* If you would like to support it,
/**
* You need to transform locale messages such as `json`, `yaml`, etc. with the bundle plugin.
*/
if (onError) {
onError(
new Error("AST format for messages is not supported") as CompileError
);
}
return () => key;
}
};After implementing message compiler, set the messageCompiler option of createSwiftI18n as follows, and you can use your message format for the messages option:
import { createSwiftI18n } from "swift-i18n";
import { messageCompiler } from "./compilation";
const i18n = createSwiftI18n({
locale: "en",
messageCompiler,
});
// the below your something to do ...
// ...Example in the translation file:
{
"common": {
"hello": "Hello {name}, you have {count, plural, one {# message} other {# messages}}"
}
}Example of use and substitution in a template:
<h1>{{ t('common.Hello', { name: "John", count: 5 }) }}</h1>Result obtained:
Hello John, you have 5 messagesNote
You can get the code for the tutorial below on examples/message-format.
swift-i18n supports the runtime key warnings mechanism, which helps find problems with translation keys during development.
This allows you to quickly detect errors in localization keys that TypeScript cannot check statically.
- Key not found in locale
- The value is not a string
- If supportedLangs is passed, then when attempting to change the language to one that is not supported
By default, warnOnMissing is enabled. To disable it, pass warnOnMissing: false to the swift-i18n configuration.
Welcome to contribute to swift-i18n!
- Fork the repository.
- Create a branch with new features or fixes.
- Write tests for new features.
- Send a pull request with a detailed description.
- Sign commits according to Conventional Commits.
Contact me if you need help or ideas.