eze-lang
v1.0.0
Published
eze-lang
Downloads
64
Maintainers
Readme
Eze-Lang
Eze-Lang is a high-performance localization package designed to simplify translation management. Write your translations in simple YAML files, and let Eze-Lang generate language-specific JSON files along with type-safe TypeScript definitions. With smart dynamic message composition—including placeholders, conditions, variants, and nested key lookups (parrotHolders)—Eze-Lang helps you build robust, internationalized applications with ease.
Why Eze-Lang?
Organized Translations:
Store translations in separate YAML files (e.g.,actions.yml,errors.yml,greetings.yml,counts.yml) instead of one huge file. The file name defines the translation group, keeping your project clean and scalable.Automatic TypeScript Definitions:
Eze-Lang automatically generates aParrot.Types.tsfile at your project root for type safety and IDE autocompletion, reducing potential runtime errors.Dynamic & Static Translations:
- Static Keys: Return fixed strings.
- Dynamic Keys: Use runtime placeholders, conditions, and variants to generate context-aware messages.
- ParrotHolders: Reuse or nest translations dynamically without redundancy.
Variants & Conditions:
Easily handle multiple message variations based on parameters like gender or numeric conditions.Insight: Variants allow the translation system to automatically select the correct message variant based on the first placeholder (e.g.,
genderin greetings). Multi-conditions add flexibility by handling special cases (e.g., specific names).Default Parameters & Fallbacks:
Set fallback values viaDefaultPlaceholdersandDefaultVariantsto gracefully handle missing values, making debugging easier.Performance-Optimized:
With precompiled interpolation and smart merging, Eze-Lang delivers minimal runtime overhead.Developer Experience:
Integrated with Vite for live reloading and automatic updates, it works seamlessly in React and any JavaScript/TypeScript project.Missing-Key Detection:
The build tooling automatically generates amissing-keys.ymlreport per language, listing untranslated keys with their source-language values.
Installation
Install with npm, Yarn, or pnpm:
npm install eze-lang
# or
yarn add eze-lang
# or
pnpm add eze-langEze-Lang requires Node.js 18 or newer.
Note: When you install and start your project, the parrot will auto-generate if no configuration is present. All YAML files in your project are processed as described below.
Quick Start
- Add the Vite plugin to your Vite configuration:
import { defineConfig } from "vite";
import parrotPlugin from "eze-lang/dist/vite-plugin-parrot.js";
export default defineConfig({
plugins: [parrotPlugin()],
});Auto-Inject: The plugin auto-injects the generated
parrot/index.tsscript into your HTML viatransformIndexHtml. This registers the config at startup — no manual import needed.
- Run your project:
npm run dev
# or
yarn devNote: The Vite plugin automatically generates language-specific JSON files and TypeScript definitions on startup.
- Switch language in your application:
import { setLanguage } from "eze-lang";
import Parrot from "eze-lang";
// Switch to Arabic (config is already registered by auto-inject)
setLanguage("ar");
// Flat access (all keys in one namespace)
console.log(Parrot.upload); // "تحميل الملف"
console.log(Parrot.search({ query: "projects" })); // "البحث عن 'projects'..."
// Grouped access (namespaced by YAML file)
import { ParrotGroups } from "eze-lang";
console.log(ParrotGroups.actions.upload);Generated output structure:
parrot/
en/
actions.json
messages.json
ui.json
ar/
actions.json
messages.json
ui.json
index.ts # Loads and merges per-group files
Parrot.Types.ts # Generated at the project rootComponents
Eze-Lang provides three components for rendering translations in your application:
- Static Component Usage:
// Renders a static key directly
import { ParrotStaticComponent } from "eze-lang";
return <ParrotStaticComponent k="upload" />;- Mixed Component Usage:
// Renders a mixed key, requiring some parameters for interpolation
import { ParrotMixedComponent } from "eze-lang";
return <div>
<ParrotMixedComponent k="greeting" gender="female" name="Emma" />
<ParrotMixedComponent k="itemCount" count={5} />
<ParrotMixedComponent k="upload" />
</div>;- Dynamic Component Usage:
// Renders a dynamic key with placeholders and parrotHolders
import { ParrotDynamicComponent } from "eze-lang";
return <ParrotDynamicComponent k="customMessage" startParrotAction="becauseOf" endParrotAction="tryAgain" anyText="Your text here" user="John" />;Note: The
ParrotDynamicComponentcomponent is used for dynamic keys with placeholders and parrotHolders. Thekprop and generated required placeholder props are type-checked.
Configuration
The parrot.config.json file is generated in your project root. All YAML files must reside in a yml folder inside the specified outputDir.
{
"$schema": "./node_modules/eze-lang/dist/parrot.config.schema.json",
"languages": ["en", "ar"],
"ymlValuesLanguage": "en",
"defaultLanguage": "en",
"outputDir": "./parrot"
}| Field | Required | Description |
| ------------------- | -------- | ----------------------------------------------------------------------- |
| languages | ✅ | All supported language codes |
| defaultLanguage | ✅ | Startup language fallback |
| ymlValuesLanguage | ❌ | Language the YAML values are written in (defaults to defaultLanguage) |
| outputDir | ✅ | Directory for generated files |
Note: Organize your translations into different files as needed. The file name defines the translation group.
YAML Blueprint Examples
Below are several example YAML files with real-world scenarios, inline usage examples, and expected outputs.
1. Actions (actions.yml)
upload:
desc: "Action for uploading a file"
value: "Upload File"
download:
desc: "Action for downloading a file"
value: "Download File"
search:
desc: "Search action with a query"
placeholders: query
value: "Searching for '{query}'..."Usage & Expected Output:
console.log(Parrot.upload); // "Upload File"
console.log(Parrot.download); // "Download File"
console.log(Parrot.search({ query: "docs" })); // "Searching for 'docs'..."2. Errors (errors.yml)
serverError:
desc: "Generic server error message"
value: "A server error occurred. Please try again later."
errorWhile:
desc: "Error message with a dynamic action"
placeholders: action
value: "An error occurred while {action}"
errorWithDetail:
desc: "Nested key lookup error message using parrotHolders"
parrotHolders: action
value: "Failed to perform {action}"Usage & Expected Output:
console.log(Parrot.serverError); // "A server error occurred. Please try again later."
console.log(Parrot.errorWhile({ action: "uploading" })); // "An error occurred while uploading"
console.log(Parrot.errorWithDetail({ action: "download" })); // "Failed to perform Download File"3. Greetings & Variants (greetings.yml)
greeting:
desc: "Basic greeting with variants based on gender"
placeholders: gender, name
value: "Hello {name}"
variants:
male: "Hello Mr. {name}"
female: "Hello Ms. {name}"
default: "Hello {name}"
genderGreeting:
desc: "Greeting with multi-conditions based on gender and name"
placeholders: gender, name
value: "Hi {name}"
conditions:
"{gender} === 'female' && {name} === 'Alice'": "Welcome, Queen Alice!"
"{gender} === 'male'": "Greetings, Sir {name}"
"{gender} === 'female'": "Greetings, Lady {name}"Usage & Expected Output:
console.log(Parrot.greeting({ gender: "male", name: "John" })); // "Hello Mr. John"
console.log(Parrot.greeting({ gender: "female", name: "Sarah" })); // "Hello Ms. Sarah"
console.log(Parrot.greeting({ gender: "default", name: "Alex" })); // "Hello Alex"
console.log(Parrot.genderGreeting({ gender: "female", name: "Alice" })); // "Welcome, Queen Alice!"
console.log(Parrot.genderGreeting({ gender: "male", name: "Robert" })); // "Greetings, Sir Robert"
console.log(Parrot.genderGreeting({ gender: "female", name: "Emma" })); // "Greetings, Lady Emma"4. Item Counts with Conditions (counts.yml)
itemCount:
desc: "Displays item count with context-aware messages"
placeholders: count
value: "{count} items available"
conditions:
"{count} < 0": "Invalid count"
"{count} === 0": "No items available"
"{count} === 1": "One item available"
"{count} > 1 && {count} < 10": "A few items available"
"{count} >= 10 && {count} < 50": "Several items available"
"{count} >= 50": "Many items available"Note: Conditions are evaluated using a CSP-safe parser (no
evalornew Function). Supported operators:===,!==,>,<,>=,<=,&&,||. The||operator has lower precedence than&&, matching JavaScript semantics.
Usage & Expected Output:
console.log(Parrot.itemCount({ count: -1 })); // "Invalid count"
console.log(Parrot.itemCount({ count: 0 })); // "No items available"
console.log(Parrot.itemCount({ count: 1 })); // "One item available"
console.log(Parrot.itemCount({ count: 5 })); // "A few items available"
console.log(Parrot.itemCount({ count: 20 })); // "Several items available"
console.log(Parrot.itemCount({ count: 75 })); // "Many items available"5. Pluralization (items.yml)
Eze-Lang supports CLDR plural categories for proper Arabic (and other language) pluralization.
Eze-Lang uses Intl.PluralRules for automatic CLDR plural support across all locales.
Override any language with custom rules via the exported pluralRules map.
itemCount:
desc: "Item count with proper Arabic plurals"
placeholders: count
value: "{count} items"
plurals:
zero: "لا عناصر"
one: "عنصر واحد"
two: "عنصران"
few: "{count} عناصر"
many: "{count} عنصراً"
other: "{count} عنصر"Usage & Expected Output (Arabic):
import { setLanguage } from "eze-lang";
setLanguage("ar");
console.log(Parrot.itemCount({ count: 0 })); // "لا عناصر"
console.log(Parrot.itemCount({ count: 1 })); // "عنصر واحد"
console.log(Parrot.itemCount({ count: 2 })); // "عنصران"
console.log(Parrot.itemCount({ count: 5 })); // "5 عناصر"
console.log(Parrot.itemCount({ count: 15 })); // "15 عنصراً"
console.log(Parrot.itemCount({ count: 100 })); // "100 عنصر"Adding custom plural rules (override):
import { pluralRules } from "eze-lang";
// Override for any language — takes priority over Intl.PluralRules
pluralRules.custom = (n) => (n === 1 ? "one" : "other");6. Composite Custom Messages (custom.yml)
failed:
desc: "Custom message combining dynamic elements"
placeholders: detail, action
value: "Failed to {action} {detail}"
becauseOf:
desc: "Provides a reason message"
placeholders: user
value: "because of {user}"
customMessage:
desc: "Custom message combining dynamic elements from various sources"
placeholders: anyText
parrotHolders: startParrotAction,endParrotAction
value: "{startParrotAction} {anyText} {endParrotAction}"Usage & Expected Output:
// Using Static keys
console.log(
Parrot.customMessage({
startParrotAction: "upload",
endParrotAction: "download",
anyText: "and",
}),
); // "Upload File and Download File"
// Using Dynamic keys
console.log(
Parrot.customMessage({
startParrotAction: "becauseOf",
endParrotAction: "tryAgain",
anyText: "SOME CUSTOM TEXT",
user: "John",
}),
); // "because of John SOME CUSTOM TEXT Please try again"
console.log(
Parrot.customMessage({
anyText: ",",
startParrotAction: "becauseOf",
user: "John",
endParrotAction: "failed",
action: "download",
detail: "Image URL",
}),
); // "because of John , Failed to download Image URL"Automatic Language Detection
The generated parrot/index.ts auto-detects the startup language using this priority:
- URL parameter —
?lang=arin the URL (great for testing and shareable links) - localStorage — user's stored preference (
langkey) - Browser languages — iterates
navigator.languagesarray with prefix matching (e.g.,en-US→en) - Cookie — reads
langcookie fromdocument.cookie - Config default —
defaultLanguagefromparrot.config.json
No manual detection code is needed — the Vite plugin handles it automatically.
Each source is guarded independently, so blocked localStorage does not prevent a
valid URL parameter, browser preference, cookie, or configured default from
being used. Calling setLanguage() persists the selection for the next reload.
Additional API
Beyond Parrot and setLanguage, these utilities are exported:
| Export | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
| getLanguage() | Returns the current language code |
| detectLanguage() | Resolves URL, stored, browser, cookie, and default language sources safely |
| initLanguage(lang?) | Reads stored language from localStorage, calls setLanguage() |
| onLanguageChange(cb) | Register a listener that fires after every setLanguage() or patchKeys() call |
| patchKeys(config) | Additive key merge — overwrites matching keys, keeps the rest. See PATCHKEYS.md |
| DefaultPlaceholders | Global fallback values for placeholders (.replace(), .update(), .set(), .delete(), .get(), .clear()) |
| DefaultVariants | Global fallback values for variant selection (.replace(), .update(), .set(), .delete(), .get(), .clear()) |
| pluralRules | User override map for plural rules. Intl.PluralRules is the default for all CLDR locales |
| ParrotGroups | Grouped access to translations by YAML file name |
Agent Guidance
Published packages include self-contained guidance and relevant source code.
Agents reviewing an installed dependency should start at
node_modules/eze-lang/AGENTS.md, which routes to
node_modules/eze-lang/.agent/_index.md and the focused runtime/tooling docs.
Final Thoughts
Eze-Lang empowers developers by:
- Effortless Organization: Managing translations in simple YAML files.
- Type Safety & Autocompletion: With generated TypeScript definitions (
Parrot.Types.ts). - Dynamic Message Composition: Using placeholders, conditions, variants, and parrotHolders for flexible translations.
- Seamless Integration: Leveraging the Vite plugin for real-time localization.
- Optimized Performance: Enjoying minimal runtime overhead with precompiled interpolation.
Eze-Lang is crafted to make localization intuitive, efficient, and enjoyable. Dive in and experience a smoother workflow for managing translations in your projects!
