Welcome to i18n-keyless
Install i18n-keyless in less than 5 minutes.
Get an API key by visiting https://i18n-keyless.com/#get-api-key
Three ways to hand your assistant the whole of i18n-keyless:
- /llms.txt — the whole documentation as one Markdown file. Paste it into Claude / ChatGPT / Cursor / etc.
SKILL.md— an Agent Skill for Claude Code. Copyskills/i18n-keyless/into your project's.claude/skills/. It also ships insidei18n-keyless-reactandi18n-keyless-node, so an agent finds it innode_modulesonce you install.- Context7 — add
use context7to your prompt if you run the Context7 MCP server.
Getting Started
Choose your platform below to see the setup instructions:
- React
- React Native
- Vue
- Angular
- HTML / JS
- Node.js
- Laravel
- Rails
- Flutter
- Python
- Go
- Kotlin
- Swift
Storage is required to cache translations.
- window.localStorage
- IndexedDB (idb-keyval)
npm install i18n-keyless-react
import * as I18nKeyless from "i18n-keyless-react";
I18nKeyless.init({
API_KEY: "YOUR_API_KEY",
storage: window.localStorage,
languages: {
primary: "fr",
supported: ["en", "fr"],
},
});
npm install i18n-keyless-react idb-keyval
import * as I18nKeyless from "i18n-keyless-react";
import { get, set, del } from 'idb-keyval';
const indexedDBStorage = {
getItem: async (key) => {
const value = await get(key);
return value || null;
},
setItem: async (key, value) => {
await set(key, value);
},
removeItem: async (key) => {
await del(key);
}
};
I18nKeyless.init({
API_KEY: "YOUR_API_KEY",
storage: indexedDBStorage,
languages: {
primary: "fr",
supported: ["en", "fr"],
},
});
You can check here for the methods that your storage item needs to have
Storage is required to cache translations.
- MMKV
- AsyncStorage
npx expo install i18n-keyless-react react-native-mmkv
npx expo prebuild
import * as I18nKeyless from "i18n-keyless-react";
import { MMKV } from 'react-native-mmkv'
const storage = new MMKV()
I18nKeyless.init({
API_KEY: "YOUR_API_KEY",
storage: storage,
languages: {
primary: "fr",
supported: ["en", "fr"],
},
});
npx expo install i18n-keyless-react @react-native-async-storage/async-storage
npx expo prebuild
import * as I18nKeyless from "i18n-keyless-react";
import AsyncStorage from "@react-native-async-storage/async-storage";
I18nKeyless.init({
API_KEY: "YOUR_API_KEY",
storage: AsyncStorage,
languages: {
primary: "fr",
supported: ["en", "fr"],
},
});
npm install i18n-keyless-vue
import { createApp } from "vue";
import { init, I18nKeyless } from "i18n-keyless-vue";
init({
API_KEY: "YOUR_API_KEY",
storage: window.localStorage,
languages: {
primary: "fr",
supported: ["en", "fr"],
},
});
createApp(App).use(I18nKeyless).mount("#app");
npm install i18n-keyless-angular
// app.config.ts
import { provideI18nKeyless } from "i18n-keyless-angular";
export const appConfig = {
providers: [
provideI18nKeyless({
API_KEY: "YOUR_API_KEY",
languages: {
primary: "fr",
supported: ["en", "fr"],
},
}),
],
};
No build step required. Add one script tag:
<script async type="module" src="https://esm.sh/i18n-keyless-browser/auto"
data-api-key="YOUR_API_KEY"
data-primary="fr"
data-supported="en,fr"></script>
Or install with npm:
npm install i18n-keyless-browser
import { init, translateDom, defineI18nT } from "i18n-keyless-browser";
init({
API_KEY: "YOUR_API_KEY",
storage: window.localStorage,
languages: {
primary: "fr",
supported: ["en", "fr"],
},
});
defineI18nT(); // registers the <i18n-t> web component
translateDom(); // translates every [data-i18n] element
npm install i18n-keyless-node
import * as I18nKeyless from "i18n-keyless-node";
I18nKeyless.init({
API_KEY: "YOUR_API_KEY",
languages: {
primary: "fr",
supported: ["en", "fr"],
},
});
composer require i18n-keyless/laravel
Add to your .env:
I18N_KEYLESS_API_KEY=YOUR_API_KEY
I18N_KEYLESS_LANGUAGES=en,fr
Your existing __() calls resolve through the i18n-keyless API with no code changes.
bundle add i18n-keyless-rails
Add to your .env:
I18N_KEYLESS_API_KEY=YOUR_API_KEY
I18N_KEYLESS_LANGUAGES=en,fr
Write the source string where a key would go: t('Bonjour le monde') resolves through the
i18n-keyless API. Your YAML keys (t('users.index.title')) keep working and win.
flutter pub add i18n_keyless
final i18n = I18nKeylessClient();
await i18n.init(I18nKeylessConfig(
apiKey: 'YOUR_API_KEY',
languages: LanguagesConfig(
primary: Lang.fr,
supported: [Lang.en, Lang.fr],
),
storage: SharedPreferencesStorage(),
));
runApp(I18nKeylessScope(client: i18n, child: const MyApp()));
pip install i18n-keyless # or: uv add i18n-keyless
import i18n_keyless as i18n
i18n.init(api_key="YOUR_API_KEY", primary="fr", supported=["en", "fr"]) # once, at process start
Python >= 3.9, no dependency. Django, Flask and FastAPI call t() with the request language.
go get github.com/arnaudambro/i18n-keyless/ports/go/v3
import i18nkeyless "github.com/arnaudambro/i18n-keyless/ports/go/v3"
client, err := i18nkeyless.Init(ctx, i18nkeyless.Config{
APIKey: "YOUR_API_KEY",
Languages: i18nkeyless.Languages{Primary: "fr", Supported: []string{"en", "fr"}},
})
Standard library only, Go >= 1.21. One client per process; T() in a handler or a template func map.
// build.gradle.kts
implementation("io.github.arnaudambro:i18n-keyless-kotlin:3.6.1")
I18nKeyless.initBlocking(I18nKeylessConfig(
apiKey = "YOUR_API_KEY",
languages = LanguagesConfig(primary = Lang.FR, supported = listOf(Lang.FR, Lang.EN)),
storage = FileStorage(context.filesDir.resolve("i18n-keyless")),
))
Pure JVM, zero dependencies: Android, Compose, desktop, and servers with server = true.
// Package.swift, or Xcode > File > Add Package Dependencies
.package(url: "https://github.com/arnaudambro/i18n-keyless-swift.git", from: "3.6.1")
import I18nKeyless
try I18nKeyless.configure(.init(apiKey: "YOUR_API_KEY",
languages: .init(primary: .fr, supported: [.fr, .en])))
Foundation only, iOS 15 / macOS 12. SwiftUI re-renders when a translation lands; server: true for Vapor.
Usage
Once you've initialized i18n-keyless for your platform, you can start translating text immediately:
- React
- React Native
- Vue
- Angular
- HTML / JS
- Node.js
- Laravel
- Rails
- Flutter
- Python
- Go
- Kotlin
- Swift
import { I18nKeylessText, useTranslation, getTranslation } from "i18n-keyless-react";
import { TabBar, Tab } from "whatever-tab-bar-system";
// Three ways to get text: `I18nKeylessText` component / `useTranslation` hook / `getTranslation` function
// `I18nKeylessText` component
function Header() {
return (
<h1><I18nKeylessText>Bonjour le monde</I18nKeylessText></h1>
);
}
// `useTranslation` hook — inside a component, for a prop that needs a plain string
function MyTabs() {
const first = useTranslation("Premier onglet");
const second = useTranslation("Deuxième onglet");
return (
<TabBar>
<Tab label={first} />
<Tab label={second} />
</TabBar>
);
}
// `getTranslation` function — outside a component: a route loader, a utility
export const loader = () => ({ title: getTranslation("Premier onglet") });
import { I18nKeylessText, useTranslation, getTranslation } from "i18n-keyless-react";
import { TabBar, Tab } from "whatever-tab-bar-system";
import { Text } from "react-native";
// Three ways to get text: `I18nKeylessText` component / `useTranslation` hook / `getTranslation` function
// `I18nKeylessText` component
function Header() {
return (
<Text><I18nKeylessText>Bonjour le monde</I18nKeylessText></Text>
);
}
// `useTranslation` hook — inside a component, for a prop that needs a plain string
function MyTabs() {
const first = useTranslation("Premier onglet");
const second = useTranslation("Deuxième onglet");
return (
<TabBar>
<Tab label={first} />
<Tab label={second} />
</TabBar>
);
}
// `getTranslation` function — outside a component: a route loader, a utility
export const loader = () => ({ title: getTranslation("Premier onglet") });
<script setup lang="ts">
import { useI18nKeyless, useTranslation } from "i18n-keyless-vue";
// The t() composable — for template expressions and attributes
const { t, setCurrentLanguage } = useI18nKeyless();
// useTranslation() — a computed string
const placeholder = useTranslation("Votre email");
</script>
<template>
<!-- The <T> component — the default -->
<h1><T>Bonjour le monde</T></h1>
<input :placeholder="t('Votre email')" />
</template>
import { Component } from "@angular/core";
import { I18nKeylessTextComponent, I18nKeylessTranslatePipe } from "i18n-keyless-angular";
@Component({
standalone: true,
imports: [I18nKeylessTextComponent, I18nKeylessTranslatePipe],
template: `
<h1><i18n-t>Bonjour le monde</i18n-t></h1>
<input [placeholder]="'Votre email' | t" />
`,
})
export class HelloComponent {}
<h1 data-i18n>Bonjour le monde</h1>
<p><i18n-t>Bienvenue sur le site</i18n-t></p>
<button onclick="i18nKeyless.setCurrentLanguage('en')">English</button>
With the JS API:
import { getTranslation, setCurrentLanguage } from "i18n-keyless-browser";
const label = getTranslation("Votre email");
await setCurrentLanguage("en");
awaitForTranslationOrFallbackToOriginal NEED to be awaited, to prevent 429 to happen.
import { awaitForTranslationOrFallbackToOriginal, type Lang } from 'i18n-keyless-node';
async function sendNotificationsToUsers() {
const users = await fetchUsers()
for (const user of users) {
const notification = {
title: await awaitForTranslationOrFallbackToOriginal("Viens voir l'application", user.lang as Lang);
body: await awaitForTranslationOrFallbackToOriginal("Il y a des supers choses !", user.lang as Lang);
}
}
}
Pick by call site: in a request handler — a route, a server component, an email or
notification send — use
awaitForTranslationOrFallbackToOriginal
(it never rejects). In a script or a build step, use
awaitForTranslationOrThrow (an ignored rejection
crashes the process on purpose, so a broken run fails loudly).
// Blade templates — your existing __() calls just work
<h1>{{ __('Bonjour le monde') }}</h1>
<p>{{ __('Bienvenue :name', ['name' => $user->name]) }}</p>
// For context (one string, two meanings)
{{ i18nk('8 heures', context: 'duration') }}
// In PHP code
__('Your order has been shipped');
i18nk('8 heures', context: 'durée');
# ERB views — the source string where a key would go
<h1><%= t('Bonjour le monde') %></h1>
<p><%= t('Bienvenue %{name}', name: current_user.name) %></p>
# For context (one string, two meanings)
<%= i18nk('8 heures', context: 'durée') %>
# In Ruby code (controllers, mailers, jobs)
t('Votre commande a été expédiée')
I18nKeyless.t('8 heures', context: 'durée', locale: :en)
// The T widget — the default
T('Bonjour le monde');
// For props that need a plain string
TextField(
decoration: InputDecoration(hintText: context.t('Votre email')),
);
// Switch language
I18nKeyless.of(context).setCurrentLanguage(Lang.en);
# In a view, a task or a script: the source string where a key would go
i18n.t("Bonjour le monde", lang)
# For context (one string, two meanings)
i18n.t("8 heures", lang, context="durée")
# Placeholders, replaced after the translation
i18n.t("Bienvenue {name}", lang, replace={"{name}": user.name})
# Switch language: pass another code
i18n.t("Bonjour le monde", "en")
// In a handler or a template: the source string where a key would go
client.T(ctx, "Bonjour le monde", lang)
// For context (one string, two meanings)
client.T(ctx, "8 heures", lang, i18nkeyless.WithContext("durée"))
// Placeholders, replaced after the translation
client.T(ctx, "Bienvenue {name}", lang, i18nkeyless.WithReplace(map[string]string{"{name}": name}))
// Switch language: pass another code
client.T(ctx, "Bonjour le monde", "en")
// The source string where a key would go
I18nKeyless.t("Bonjour le monde")
// For context (one string, two meanings)
I18nKeyless.t("8 heures", context = "durée")
// Placeholders, replaced after the translation
I18nKeyless.t("Bienvenue {name}", replace = mapOf("{name}" to user.name))
// Switch language
I18nKeyless.setLanguage(Lang.EN)
// SwiftUI: the source string where a key would go
I18nKeylessText("Bonjour le monde")
// A String, for a label, a title, UIKit
let title = I18nKeyless.t("Votre profil")
// For context (one string, two meanings)
I18nKeyless.t("8 heures", context: "durée")
// Placeholders, replaced after the translation
I18nKeyless.t("Bienvenue {name}", replace: ["{name}": user.name])
// Switch language
await I18nKeyless.setLanguage(.en)