OILang is a robust internationalization (i18n) handling library designed for performance and flexibility. It employs a dual-layer architecture, utilizing a persistent database as the source of truth and a high-performance in-memory or Redis-based store for fast runtime access. This ensures that your application remains responsive while maintaining data integrity and persistence.
- Dual-Layer Architecture: Combines persistent storage with fast caching.
- Flexible Storage: Choose between in-memory storage for simple use cases or Redis for distributed systems.
- Customizable Schemas: Configurable database schema names to fit existing database structures.
- Type-Safe: Built with TypeScript for reliable development.
To use OILang, you need to install the package and its peer dependencies.
bun add @voilabs/oilangHere is a basic example of how to initialize and use OILang within your application.
import { OILang } from "@voilabs/oilang";
import { PostgreSQL } from "@voilabs/oilang/adapters";
import { MemoryStore } from "@voilabs/oilang/stores";
// Initialize the library
const oilang = new OILang({
database: new PostgreSQL(
"postgresql://user:password@localhost:5432/dbname",
{
schemaNames: {
keys: "i18n_keys",
locales: "i18n_locales",
},
},
),
store: new MemoryStore(),
});
async function main() {
// Connect to database and load data into store
await oilang.init();
// Create a new locale
await oilang.locales.create({
locale: "en-US",
nativeName: "English",
englishName: "English",
isDefault: true,
});
// Add a translation key
await oilang.translations.create("en-US", {
key: "greeting",
value: "Hello, World!",
});
// Retrieve translations
// Example: Translations are stored in memory or Redis as a key-value pair.
const translations = await oilang.translations.list("en-US");
console.log(translations);
// Create a new locale (reference translations from default locale)
await oilang.locales.create({
locale: "[localeCode]",
nativeName: "[nativeName]",
englishName: "[englishName]",
translationsFromDefault: true,
});
}
main();The main entry point for the library. It orchestrates the interaction between the database adapter and the store, delegating specific operations to locales and translations namespaces.
new OILang(config: AdapterConfig)config: Configuration object containing initializeddatabaseandstoreinstances.
-
init(): Promise<void>Connects to the database and initializes the store by loading existing locales and translations. -
refreshCache(): Promise<void>Refreshes the internal cache by reloading data from the database.
oilang.locales: Manages locale operations.oilang.translations: Manages translation operations.
Access via oilang.locales.
-
list(): Promise<ActionResponse<LocaleData[]>>Retrieves all available locales from the store. -
create(config: { locale: string; nativeName: string; englishName: string; translationsFromDefault?: boolean; isDefault?: boolean }): Promise<ActionResponse<LocaleData>>Creates a new locale in the database and updates the store. -
delete(locale: string): Promise<ActionResponse<LocaleData>>Deletes a locale and its associated translations from both the database and the store. -
update(locale: string, nativeName: string, englishName: string): Promise<ActionResponse<LocaleData>>Updates locale information in the database and store.
Access via oilang.translations.
-
list(locale: string): Promise<ActionResponse<TranslationData[]>>Retrieves all translations for a specific locale from the store. Returns an array of translation objects. -
create(locale: string, config: { key: string; value: string }): Promise<ActionResponse<TranslationData>>Adds a translation key-value pair for a specific locale. -
update(locale: string, key: string, newValue: string): Promise<ActionResponse<TranslationData>>Updates an existing translation value. -
delete(locale: string, key: string): Promise<ActionResponse<TranslationData>>Deletes a translation key. -
translate(locale: string, key: string, variables?: Record<string, string | number>): Promise<string>Retrieves a single translation, optionally performing variable interpolation. Uses fallback locale if translation is missing.
Handles persistent storage of locales and translations.
Constructor
new PostgreSQL(connectionString: string | ClientConfig, config: { schemaNames: { keys: string; locales: string } })connectionString: PostgreSQL connection string or configuration object.config.schemaNames: Custom table names for keys and locales.
Stores handle the runtime access to data. They act as a cache that is synchronized with the database.
Stores data in the application's memory. Suitable for single-instance applications or development.
Constructor
new MemoryStore();Stores data in a Redis instance. Essential for distributed applications or when data persistence across restarts (without DB reload) is desired.
Constructor
new RedisStore(connectionString: string, options?: { prefix?: string })connectionString: Redis connection URL (default:redis://localhost:6379).options.prefix: specific prefix for Redis keys (default:oilang:).
OILang provides built-in handlers to easily integrate with popular web frameworks.
The elysiaHandler allows you to expose RESTful API endpoints for managing locales and translations directly from your Elysia application.
import { Elysia } from "elysia";
import { elysiaHandler } from "@voilabs/oilang/handlers";
const app = new Elysia()
.use(elysiaHandler(oilang)) // oilang instance
.get("/", ({ store }) => {
return `Current locale: ${store.locale}`;
})
.listen(3000);The handler enriches the Elysia context:
- State: Adds
localeto the global state (store.locale).
The handler exposes the following endpoints under the /oilang prefix:
Note: Endpoints marked with
*support theonAuthHandlehook for authentication.
-
POST /oilang/set-localeSets the current locale for the session/request.- Body:
{ locale: string }
- Body:
-
GET /oilang/localesList all available locales. -
POST /oilang/locales* Create a new locale.- Body:
{ locale: string; native_name: string; english_name: string; is_default?: boolean; translations_from_default?: boolean; }
- Body:
-
PUT /oilang/locales/:localeCode* Update an existing locale.- Params:
localeCode(string) - Body:
{ native_name: string; english_name: string; }
- Params:
-
DELETE /oilang/locales/:locale* Delete a locale.- Params:
locale(string)
- Params:
-
GET /oilang/translations/:localeGet translations for a specific locale.- Params:
locale(string) - Query:
format(optional, "true" for nested JSON). If not provided, returns an array of translation objects.
- Params:
-
POST /oilang/translations/:locale* Add multiple translations.- Params:
locale(string) - Body:
{ translations: { key: string; value: string; } []; }
- Params:
-
PUT /oilang/translations/:locale* Update multiple translations.- Params:
locale(string) - Body:
{ translations: { key: string; value: string; } []; }
- Params:
-
DELETE /oilang/translations/:locale* Delete multiple translations.- Params:
locale(string) - Body:
{ translations: { key: string; } []; }
- Params:
POST /oilang/refresh* Refresh the internal cache from the database.
You can pass an optional configuration object to elysiaHandler:
elysiaHandler(oilang, {
onAuthHandle: async ({ request }) => {
// Implement authentication logic here
},
});onAuthHandle: A hook to run before handling requests, useful for authentication.
The Database adapter automatically creates the necessary tables if they do not exist.
Stores information about supported languages.
code(Primary Key): The locale code (e.g., "en-US").native_name: Name of the language in its own script.english_name: Name of the language in English.created_at: Timestamp of creation.updated_at: Timestamp of last update.
Stores the translation strings.
id(Primary Key): Unique identifier.key: The translation key (e.g., "homepage.title").value: The translated string.locale_id(Foreign Key): ReferencesLocales(code).
Most methods return a result object pattern to handle errors gracefully without throwing.
type ActionResponse<T> =
| { error: Error & { code?: string }; data: null }
| { error: null; data: T };
interface TranslationData {
locale_id: string;
key: string;
value: string;
}OILang provides utility functions to help with string manipulation for i18n.
Helper functions to handle multi-language strings formats used in some legacy systems or specific storage patterns.
Wraps a dictionary of locale-value pairs into a single string format <locale>value</locale>.
import { wrap } from "@voilabs/oilang/utils";
const wrapped = wrap({
"en-US": "Hello",
"tr-TR": "Merhaba",
});
// Output: "<en-US>Hello</en-US><tr-TR>Merhaba</tr-TR>"Extracts the value for a specific locale from a wrapped string.
import { unwrap } from "@voilabs/oilang/utils";
const value = unwrap("<en-US>Hello</en-US><tr-TR>Merhaba</tr-TR>", "tr-TR");
// Output: "Merhaba"Extracts the value for a specific locale from a wrapped string.
import { unwrapObject } from "@voilabs/oilang/utils";
const value = unwrapObject("<en-US>Hello</en-US><tr-TR>Merhaba</tr-TR>");
// Output: { "en-US": "Hello", "tr-TR": "Merhaba" }-
Core Library (
src/index.ts):- Implemented
OILangclass as the main entry point for managing internationalization. - Added
LocaleandTranslationclasses to handle domain-specific operations. - Implemented
init()method to load data from the persistent database into the cache store. - Added
refreshCache()method to reload data at runtime. - Added comprehensive error handling with
ActionResponsetype.
- Implemented
-
Data Stores (
src/stores):- MemoryStore: Implemented in-memory storage for fast, local development and testing.
- RedisStore: Implemented Redis-based storage using
ioredisfor distributed caching and persistence. - Defined common interface for stores supporting
load,set,get,getAll,remove, andupdateoperations.
-
Utilities (
src/utils):- Added
unwrapfunction to extract locale-specific strings from XML-wrapped values. - Added
unwrapObjectto parse strings into key-value locale maps.
- Added
-
Types (
src/types.ts):- Defined
LocaleDatainterface for locale structure (code, native/english names, etc.). - Defined
TranslationDatainterface for translation key-value pairs.
- Defined