https://github.com/stackoverprof/say-dictionary
[NPM] Simple and LLM-friendly i18n solution.
https://github.com/stackoverprof/say-dictionary
Last synced: 4 days ago
JSON representation
[NPM] Simple and LLM-friendly i18n solution.
- Host: GitHub
- URL: https://github.com/stackoverprof/say-dictionary
- Owner: stackoverprof
- Created: 2026-02-03T01:49:41.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-02-13T22:21:02.000Z (5 months ago)
- Last Synced: 2026-02-14T04:44:42.334Z (5 months ago)
- Language: JavaScript
- Homepage: https://stackoverprof.github.io/say-dictionary-site/
- Size: 58.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# say-dictionary
URL-based i18n with configurable languages and a CLI for extracting translation keys.
## Why?
The dictionary format is designed to be **LLM-friendly**. Just hand your `dictionary.json` to an AI and ask it to translate — done. No complex tooling, no translation service integrations, no manual key mapping.
## Installation
```bash
npm install say-dictionary
# or
pnpm add say-dictionary
```
## Usage
### 1. Initialize (once in your entry point)
```ts
// root.tsx or app entry
import { init } from 'say-dictionary';
import dictionary from './dictionary.json';
init(dictionary);
```
### 2. Use anywhere
```ts
import { say, getLanguage, setLanguage } from 'say-dictionary';
// Get translated text
say("Order Now"); // Returns "Order Now" or "Panta núna" based on URL
// ICU formatting (only when vars are provided)
say("You have {count, plural, one {# pizza} other {# pizzas}}", {
count: 2,
});
// Get current language from URL (null if no language prefix)
getLanguage(); // "is" or null
// Navigate to different language
setLanguage('is'); // Redirects to /is/current-path
```
## Dictionary Format
```json
{
"Order Now": { "en": "Order Now", "is": "Panta núna" },
"Welcome": { "en": "Welcome", "is": "Velkomin" },
"You have {count, plural, one {# pizza} other {# pizzas}}": {
"is": "Þú átt {count, plural, one {# pizzur} other {# pizzur}}"
}
}
```
Languages are automatically detected from the dictionary keys.
ICU message formatting is **opt-in** and only runs when you pass variables to
`say(key, vars)`. If a translation is missing for the current language, the key
itself is treated as the ICU message.
Example:
```ts
say(
"Today is the {day, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} of March",
{ day: 3 }
);
// "Today is the 3rd of March"
say("Event on {date, date, ::MMMM d}", { date: new Date("2026-03-02") });
// "Event on March 2"
```
## URL Structure
The language is detected from the first path segment:
- `/is/about` → Icelandic (`say()` returns translation)
- `/about` → No language prefix (`say()` returns the key itself)
## SSR (Next.js, Remix, etc.)
For SSR frameworks, use `ssrLang()` to prevent hydration mismatch:
```tsx
// app/[lang]/page.tsx
export default async function Page({ params }) {
const { lang } = await params;
return ;
}
// app/page.tsx
"use client";
import { ssrLang, say } from 'say-dictionary';
export default function Home({ lang }: { lang?: string }) {
ssrLang(lang ?? null);
return
{say("Hello")}
;
}
```
This is optional — only needed for SSR. Client-side apps (Vite, CRA) work without it.
## CLI
Extract translation keys from your source files:
```bash
npx say-dictionary extract -l en,is -i ./app -o ./dictionary.json
```
Options:
- `-l, --lang` - Comma-separated list of languages
- `-i, --in` - Source directory to scan
- `-o, --out` - Output dictionary file
Output `dictionary.json`:
```json
{
"Order Now": { "en": "Order Now", "is": "" },
"Welcome": { "en": "Welcome", "is": "" }
}
```
**The first language (`en`) gets the key as its value.** Hand this to an LLM to fill in the translations.
## License
MIT