swift-i18n
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.

Why choose swift-i18n?
- Higher speed — no unnecessary dependencies, works on pure
Intl API.
- Minimal size — lightweight and compact code.
- TypeScript support —
type-safe translation keys and autocomplete.
- Dynamic loading and caching — convenient for working with large projects.
- Easy integration —
React plugin and Vue 3 plugin with provide/inject and hooks
- Full support — For
plural and formatting — numbers, dates, currencies, units.
Features
- Native Intl APIs:
Intl.NumberFormat, Intl.DateTimeFormat, Intl.PluralRules, Intl.RelativeTimeFormat
- Language detection (
localStorage, cookie, browser language)
- Dynamic locale loading via
ESM dynamic import
- Vue 3 plugin with
provide/inject and global $t function
- TypeScript-friendly with
type-safe translation keys and autocompletion
Get Started
1. Installation
npm install swift-i18n
2. Create locale files
Create a locales folder in your src directory:
src/
├─ locales/
│ ├─ en.json
│ └─ uk.json
Example en.json:
{
"common": {
"hello": "Hello!",
"items_one": "{count} item",
"items_other": "{count} items"
},
"home": {
"title": "Welcome",
"description": "This is the home page"
}
}
Vue 3 Integration with Vite
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');
Usage in components (<script setup>)
<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>
React Integration
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>
</>
);
}
Format helpers
import { formatCurrency, formatDate, formatRelativeTime, formatNumber, formatUnit } from 'swift-i18n';
formatNumber(1234567.89, 'en-US');
formatCurrency(1234.5, 'USD', 'en-US');
formatUnit(10, 'kilometer-per-hour', 'en-US');
formatDate(new Date(), 'en-US');
formatRelativeTime(-2, 'day', 'en-US');
Core Features
Pluralization
The plural(baseKey: string, count: number, vars?: Record<string, any>) method returns the correct plural form translation:
Example JSON structure:
{
"common": {
"items_one": "{count} item",
"items_few": "{count} items",
"items_many": "{count} items",
"items_other": "{count} items"
}
}
Usage:
plural('common.items', 1);
plural('common.items', 3);
Variable Interpolation
Pass variables into translations via the vars object:
{
"greeting": "Hello, {name}!"
}
t('greeting', { name: 'Alice' });
plural('common.items', 5, { name: 'Alice' });
Fallbacking
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
}
});
Advanced Usage
Linked messages
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.
Locale messages the below:
{
"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.
The following is an example of the use of $t() or t() in a template:
<p>{{ $t('message.linked') }}</p>
The first argument is message.linked as the locale messages key as a parameter to t.
As result the below:
<p>DIO: the world !!!!</p>
Using the escape parameter option
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.
For example:
const i18n = createI18n({
locale: 'en',
escapeParameter: true,
})
t('message.welcome', { name }, { escapeParameter: true })
How it works
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
Example
const input = '<img src=x onerror=alert(1)>'
$t('message.hello', { name: input })
$t('message.hello', { name: input }, { escapeParameter: true })
Dynamic loading & caching
- Translations are dynamically loaded via ESM
import()
- Automatic loading when calling
changeLanguage().
Type-safe Translations
Add type definitions for autocompletion:
- Create
src/types/swift-i18n.d.ts:
[Manual schema definition]
import 'swift-i18n';
declare module 'swift-i18n' {
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.
{
"include": [
"src/types/**/*"
]
}
Custom Message Format
[!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.
Message Compiler implementation
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") {
const formatter = new IntlMessageFormat(message, locale);
return (ctx: MessageContext) => {
return formatter.format(ctx.values);
};
} else {
if (onError) {
onError(
new Error("AST format for messages is not supported") as CompileError
);
}
return () => key;
}
};
Message compiler registration
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,
});
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 messages
Reference
[!NOTE]
You can get the code for the tutorial below on examples/message-format.
Debugging missing translations
Runtime warnings
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.
The framework will output console.warn if one of the following occurs during the call to t():
- 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.
Contribution
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.