Branchlet provides an elegant, extensible syntax for handling conditional text logic in i18n strings.
- Intuitive Syntax - Natural, readable conditional expressions
- Extensible - Create custom matchers for your specific use cases
- Zero Dependencies - Lightweight, fast and tree-shakeable
- Pure Functions - Predictable, testable, and composable
- Framework Agnostic - Works anywhere JavaScript runs
# Using pnpm
pnpm add @branchlet/core
# Using npm
npm install @branchlet/core
# Using yarn
yarn add @branchlet/coreimport { createBranchlet } from '@branchlet/core';
import { eqMatcher, intervalMatcher } from '@branchlet/core/matchers';
const { parse } = createBranchlet();
// Simple pluralization
parse(
'You have {{ count, =0:no items, =1:one item, many items }} in your cart.',
{ count: 0 }
);
// → "You have no items in your cart."
// Range-based conditions
parse(
'Your score is {{ score, [0-50]:bad, [51-75]:good, [76-100]:excellent }}!',
{ score: 85 }
);
// → "Your score is excellent!"Templates use double curly braces {{ }} for expressions:
branchlet.parse('Hello {{ name }}!', { name: 'World' });
// → "Hello World!"Expressions can contain conditional branches with the syntax: {{ variable, condition1:result, condition2:result, fallback }}
branchlet.parse(
'Status of the task: {{ status, =active:Active, =pending:Pending, Unknown }}',
{ status: 'active' }
);
// → "Active"Branchlet includes several ready-to-use matchers:
eqMatcher- Equality:=valueneqMatcher- Inequality:!=valuegtMatcher- Greater than:>valuegteMatcher- Greater than or equal:>=valueltMatcher- Less than:<valuelteMatcher- Less than or equal:<=valueintervalMatcher- Ranges:[min-max]
import { createBranchlet } from '@branchlet/core';
import {
eqMatcher,
gteMatcher,
ltMatcher,
intervalMatcher,
} from '@branchlet/core/matchers';
const branchlet = createBranchlet({
matchers: [eqMatcher, gteMatcher, ltMatcher, intervalMatcher],
});
// Equality
branchlet.parse('Status: {{ code, =200:OK, =404:Not Found, Error }}', { code: 200 });
// → "Status: OK"
// Comparisons
branchlet.parse('{{ age, >=18:Adult, <18:Minor }}', { age: 25 });
// → "Adult"
// Ranges
branchlet.parse('{{ temp, [-10-0]:Freezing, [1-15]:Cold, [16-25]:Mild, [26-35]:Warm, Hot }}',
{ temp: 22 }
);
// → "Mild"You can reference the variable value within branch results using single curly braces:
branchlet.parse(
'You have {{ count, =0:no items, =1:{count} item, {count} items }} in your cart.',
{ count: 5 }
);
// → "You have 5 items in your cart."Extend Branchlet with your own conditional logic:
import type { Matcher } from '@branchlet/core';
// Custom matcher to check if a string has a specific file extension
const hasExtensionMatcher: Matcher = ({ conditionText, value }) => {
const match = conditionText.match(/^hasExtension\((.*)\)$/);
if (match) {
return String(value).endsWith(match[1]);
}
return null; // Not recognized, try next matcher
};
const branchlet = createBranchlet({
matchers: [hasExtensionMatcher],
});
branchlet.parse(
'Filename: {{ filename, hasExtension(.png):Image file, hasExtension(.doc):Document file, Unknown file }}',
{ filename: 'img_12345.png' }
);
// → "Filename: Image file"Matchers are evaluated in order. Return null to pass control to the next matcher:
const customMatcher: Matcher = ({ conditionText, value }) => {
if (conditionText.startsWith('custom')) {
// Your logic here
return /* true or false */;
}
return null; // Not recognized, continue to next matcher
};const template = `
{{ itemCount, =0:Your cart is empty, =1:You have 1 item, You have {itemCount} items }}
{{ itemCount, >0:({{ total }} total), }}
`.trim();
branchlet.parse(template, { itemCount: 3, total: '$45.99' });
// → "You have 3 items ($45.99 total)"const notification = branchlet.parse(
'{{ unread, =0:No new messages, =1:1 new message, {unread} new messages }}',
{ unread: 12 }
);
// → "12 new messages"const engagement = branchlet.parse(
'You have {{ points }} points - {{ points, [0-99]:Beginner, [100-499]:Intermediate, [500-999]:Advanced, Expert }} level!',
{ points: 750 }
);
// → "You have 750 points - Advanced level!"Branchlet is built on functional programming principles:
- Pure Functions - No side effects, predictable behavior
- Dependency Injection - Matchers are injected, not hardcoded
- Composition - Small, focused functions that compose well
- Immutability - No mutations, just transformations
This makes Branchlet easy to test, extend, and reason about.
Creates a Branchlet instance with custom matchers.
Parameters:
options.matchers- Array of matcher functions (optional)
Returns:
- Object with
parse(template, variables)method
Parses a template string with optional variables.
Parameters:
template- Template string with{{ }}expressionsvariables- Object mapping variable names to values (optional)
Returns:
- Interpolated string
type Matcher = (args: {
conditionText: string;
value: string | number | boolean;
}) => boolean | null;Returns:
true- Condition matchesfalse- Condition doesn't match (but is recognized)null- Condition not recognized (try next matcher)
Contributions are welcome! Please feel free to submit a Pull Request.
This project is crafted with ❤️ by Corentin Thomasset. If you find this project helpful, please consider supporting my work.
Header logo uses Braces and Sprout icons from the Lucide icon collection.
This project is under the MIT license © Corentin Thomasset.