A robust internationalization (i18n) library for Node.js & Bun β built for performance, flexibility and DX.
OILang is a dual-layer i18n engine: a persistent adapter (PostgreSQL / Drizzle ORM) acts as the source of truth, while a runtime store (Memory / Redis) provides fast access during request handling.
It ships with a first-class CLI (bunx oilang generate), built-in framework handlers (Elysia), and a fully typed API.
- Features
- Installation
- Quick Start
- CLI
- Configuration File
- API Reference
- Adapters
- Stores
- Elysia Handler
- Utils
- Troubleshooting
- Changelog
- Dual-layer architecture (persistent DB + runtime cache)
- PostgreSQL and Drizzle ORM adapters (
pg,mysql,sqlite) - CLI schema generator (SQL or Drizzle schema files)
- First-class TypeScript types
- Pluggable stores:
MemoryStore,RedisStore - Built-in Elysia handler with ready-to-use REST endpoints
bun add @voilabs/oilangOr with npm:
npm install @voilabs/oilangimport { OILang } from "@voilabs/oilang";
import { PostgreSQL } from "@voilabs/oilang/adapters";
import { MemoryStore } from "@voilabs/oilang/stores";
export const oilang = new OILang({
database: new PostgreSQL(
"postgresql://user:password@localhost:5432/dbname",
{
schemaNames: {
keys: "i18n_keys",
locales: "i18n_locales",
},
},
),
store: new MemoryStore(),
});
await oilang.init();import { OILang } from "@voilabs/oilang";
import { DrizzleAdapter } from "@voilabs/oilang/adapters";
import { RedisStore } from "@voilabs/oilang/stores";
import { db } from "./db";
export const oilang = new OILang({
database: new DrizzleAdapter(db, {
provider: "pg", // "pg" | "mysql" | "sqlite"
schemaNames: {
keys: "i18n_keys",
locales: "i18n_locales",
},
}),
store: new RedisStore(process.env.REDIS_URL!),
});
await oilang.init();OILang ships with a CLI that statically analyzes your config and generates schema files.
bunx oilang generate --path <output-file-or-directory> --config <path-to-oilang-config>Generate a Drizzle schema file:
bunx oilang generate \
--path ./src/server/drizzle/oilang-schema.ts \
--config ./src/server/lib/oilang.tsGenerate into a directory (auto-picks file name):
bunx oilang generate --path ./databaseIf --config is omitted, CLI will:
- Read from
oilang.config.tsif available. - Otherwise search the project for a file exporting
oilangand create a config for you.
OILang uses an optional oilang.config.ts at project root:
import { defineConfig } from "@voilabs/oilang";
export default defineConfig({
configPath: "./src/server/lib/oilang.ts",
outputPath: "./src/server/drizzle/oilang-schema.ts",
});Supported keys:
| Key | Description |
|---|---|
configPath |
Path to the file exporting your OILang instance |
outputPath |
Destination for generated schema file |
output |
Alias for outputPath |
path |
Alias for outputPath |
Main entry point. Orchestrates the adapter and the store.
| Method | Description |
|---|---|
init() |
Connect the adapter and hydrate the store cache |
refreshCache() |
Reload all locales/translations into the store |
getAdapter() |
Return the configured database adapter instance |
locales |
Locale management namespace |
translations |
Translation management namespace |
| Method | Description |
|---|---|
list() |
List all locales |
create({ locale, nativeName, englishName, isDefault?, translationsFromDefault? }) |
Create a locale |
update(locale, nativeName, englishName, isDefault?) |
Update locale info |
delete(locale) |
Delete a locale |
| Method | Description |
|---|---|
list(locale) |
List all translations for a locale |
create(locale, { key, value }) |
Add a translation |
update(locale, key, newValue) |
Update a translation |
delete(locale, key) |
Delete a translation |
translate(locale, key, variables?) |
Resolve a translation (with variables + fallback) |
Uses the pg library and manages tables on connect() via CREATE TABLE IF NOT EXISTS.
new PostgreSQL(connectionString, {
schemaNames: { keys: "i18n_keys", locales: "i18n_locales" },
});Works with the Drizzle ORM. Supports PostgreSQL, MySQL and SQLite providers.
new DrizzleAdapter(db, {
provider: "pg", // "pg" | "mysql" | "sqlite"
schemaNames: { keys: "i18n_keys", locales: "i18n_locales" },
schemas: { locales, translations }, // optional; auto-detected otherwise
});schemasis optional. When omitted, OILang tries to discover tables via Drizzle internals.- CLI generates a matching Drizzle schema file so you can import it back into your project.
Fast in-process cache β ideal for single-node apps and development.
new MemoryStore();Shared cache for distributed deployments.
new RedisStore(connectionString, { prefix?: "oilang:" });Mount OILang as a ready-to-use Elysia module:
import { Elysia } from "elysia";
import { elysiaHandler } from "@voilabs/oilang/handlers";
new Elysia()
.use(
elysiaHandler(oilang, {
onAuthHandle: async ({ request }) => {
// optional auth pre-handler
},
}),
)
.listen(3000);All endpoints are mounted under the /oilang prefix.
Endpoints marked π support the optional onAuthHandle hook.
| Method | Route | Description |
|---|---|---|
GET |
/oilang/locales |
List all locales |
POST |
/oilang/locales |
Create a new locale π |
PUT |
/oilang/locales/:localeCode |
Update a locale π |
DELETE |
/oilang/locales/:locale |
Delete a locale π |
POST /oilang/locales body:
{
"locale": "en-US",
"native_name": "English",
"english_name": "English",
"is_default": true,
"translations_from_default": false
}PUT /oilang/locales/:localeCode body:
{
"native_name": "English",
"english_name": "English"
}| Method | Route | Description |
|---|---|---|
GET |
/oilang/translations/:locale |
Fetch translations. ?format=true returns nested JSON |
POST |
/oilang/translations/:locale |
Add multiple translations π |
PUT |
/oilang/translations/:locale |
Update multiple translations π |
DELETE |
/oilang/translations/:locale |
Delete multiple translations π |
POST / PUT body:
{
"translations": [
{ "key": "home.title", "value": "Welcome" },
{ "key": "home.subtitle", "value": "Hello there" }
]
}DELETE body:
{
"translations": [{ "key": "home.title" }]
}| Method | Route | Description |
|---|---|---|
POST |
/oilang/set-locale |
Set the active locale on the session |
Body:
{ "locale": "en-US" }| Method | Route | Description |
|---|---|---|
POST |
/oilang/refresh |
Refresh the internal cache from DB π |
import { wrap } from "@voilabs/oilang/utils";
wrap({ "en-US": "Hello", "tr-TR": "Merhaba" });
// => "<en-US>Hello</en-US><tr-TR>Merhaba</tr-TR>"import { unwrap } from "@voilabs/oilang/utils";
unwrap("<en-US>Hello</en-US><tr-TR>Merhaba</tr-TR>", "tr-TR");
// => "Merhaba"import { unwrapObject } from "@voilabs/oilang/utils";
unwrapObject("<en-US>Hello</en-US><tr-TR>Merhaba</tr-TR>");
// => { "en-US": "Hello", "tr-TR": "Merhaba" }- If the CLI cannot resolve your config, check
configPathinsideoilang.config.ts. - When CLI runtime import fails, it falls back to static source analysis automatically.
- Missing
schemaNames? Use explicit string literals in your config so the analyzer can detect them. - On Windows, CLI internally uses proper file URLs for path compatibility.
- CLI Internal Refactor (
src/cli):- Database schema generation logic was split into dedicated modules under
src/cli/utils/databases/. generatecommand now uses modular static-analysis helpers (drizzle,postgres,shared,index).
- Database schema generation logic was split into dedicated modules under
- Adapter Contract Simplification:
- Removed
getFileInfo()requirement from the adapter interface and implementations. - Schema generation is now fully handled by CLI static analysis.
- Removed
- PostgreSQL Runtime Bootstrap (
src/adapters/PostgreSQL.ts):- Restored
CREATE TABLE IF NOT EXISTSflow insideconnect()so PostgreSQL users keep auto-init behavior.
- Restored
- Improved
schemaNamesparsing for more robustkeys/localesdetection. - PostgreSQL fallback generation now better respects custom
schemaNames.
- Resilient config loading for CLI:
importβrequireβ static source-analysis fallback. - Better schema generation reliability for
PostgreSQLanddrizzleAdapterconfigs.
- Safer config loading flow (ESM/CJS compatibility).
- Better
oilang.config.tshandling (configPath,outputPath,output,path). - CLI refactored into modular folders (
commands,utils,index).
- Added
defineConfig(...)helper and extended config keys.
- Windows config import path compatibility using proper file URL conversion.
- Drizzle adapter: optional
schemas+ automatic schema discovery.
- Standardized adapter file generation via
{ content, name }. - Improved output target resolution.
- Drizzle ORM support (
src/adapters/Drizzle.ts). - CLI generator (
bunx oilang generate).
- Initial public release (
OILang, locale/translation APIs, stores, utilities).