Skip to content

Commit 9abc282

Browse files
committed
chore: update README
1 parent ee53572 commit 9abc282

File tree

5 files changed

+173
-5
lines changed

5 files changed

+173
-5
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"cSpell.words": [
77
"deno",
88
"denostack",
9-
"intlit"
9+
"intlit",
10+
"Jaeum",
11+
"jongseong",
12+
"josa"
1013
]
1114
}

README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ TypeScript support.
2424
- Includes pluralization support (plugins can be optionally added for more
2525
features)
2626
- Easy to integrate with existing projects
27+
- Inline `/* ... */` comments let you differentiate identical source strings
28+
without affecting the fallback text
29+
- Bundled Korean tagged template adds automatic particle selection when `locale`
30+
is set to `ko`
2731

2832
## Installation
2933

@@ -49,6 +53,36 @@ const text = formatter.format("Hello World");
4953
console.log(text); // Output: Hello World
5054
```
5155

56+
You can pass values in the second argument and interpolate them inside the
57+
template. When no translation is registered, the original text is used as the
58+
fallback.
59+
60+
```typescript
61+
const formatter = new Formatter({ locale: "en" });
62+
63+
formatter.format("Hello, {name}!", { name: "Kelly" });
64+
// → "Hello, Kelly!"
65+
```
66+
67+
### Supplying Localized Messages
68+
69+
Provide a message catalog to translate strings. The `Formatter` generics keep
70+
the message keys type-safe.
71+
72+
```typescript
73+
const messages = {
74+
"Hello, {name}!": "안녕하세요, {name}!",
75+
};
76+
77+
const formatter = new Formatter<typeof messages>({
78+
locale: "ko",
79+
messages,
80+
});
81+
82+
formatter.format("Hello, {name}!", { name: "Kelly" });
83+
// → "안녕하세요, Kelly!"
84+
```
85+
5286
### Handling Plurals
5387

5488
Intlit provides support for handling plural forms in different languages, making
@@ -127,3 +161,122 @@ console.log(
127161
formatter.format("You have {count} file{count.other:}s{/}.", { count: 100 }),
128162
); // Output: لديك 100 ملفات.
129163
```
164+
165+
### Gender and Conditional Segments
166+
167+
Hooks in Intlit behave like chained methods. The default hooks include helpers
168+
for gendered output and `else` fallbacks.
169+
170+
```typescript
171+
const formatter = new Formatter({ locale: "en" });
172+
173+
formatter.format(
174+
"{user} added {photoCount.one:}a new photo{.other:}{_} new photos{/} to {gender.male:}his{.female:}her{.other:}their{/} stream.",
175+
{ user: "Alex", photoCount: 2, gender: "female" },
176+
);
177+
// → "Alex added 2 new photos to her stream."
178+
```
179+
180+
Inside nested segments the current value is available through the special `_`
181+
parameter, so you can reuse the original number (as seen above when printing the
182+
plural count).
183+
184+
### Adding Context with Inline Comments
185+
186+
Inline block comments are stripped from the rendered output but kept in the key.
187+
They are perfect for differentiating identical phrases that need distinct
188+
translations.
189+
190+
```typescript
191+
const messages = {
192+
"/* 환영 */안녕하세요.": "Welcome!",
193+
"/* 일반 */안녕하세요.": "Hello!",
194+
};
195+
196+
const formatter = new Formatter({
197+
locale: "en",
198+
messages,
199+
});
200+
201+
formatter.format("/* 환영 */안녕하세요.");
202+
// → "Welcome!"
203+
204+
formatter.format("/* 일반 */안녕하세요.");
205+
// → "Hello!"
206+
207+
// Because the comments are ignored at runtime the fallback remains clean.
208+
new Formatter({ locale: "ko" }).format("/* 환영 */안녕하세요.");
209+
// → "안녕하세요."
210+
```
211+
212+
### Integrating a Tagged Template Handler
213+
214+
Need smarter whitespace management or language-specific post-processing? Intlit
215+
ships with a particle-aware template handler that is applied automatically when
216+
`locale` is set to `ko`.
217+
218+
```typescript
219+
const formatter = new Formatter({ locale: "ko" });
220+
221+
formatter.format(
222+
`새 소식이 있습니다:\n{count.one:}새 메시지가 하나 생겼어요{.other:}{_}개의 새 메시지가 있어요{/}
223+
`,
224+
{ count: 3 },
225+
);
226+
// → "새 소식이 있습니다:\n3개의 새 메시지가 있어요"
227+
```
228+
229+
For other locales you can still provide your own `taggedTemplate` function to
230+
massage the final string (e.g. trim indentation, collapse whitespace, or run
231+
additional transforms).
232+
233+
### Custom Hooks
234+
235+
Extend Intlit by adding application-specific hooks. A hook receives a
236+
`HookContext` object describing the current placeholder and returns the next
237+
value that should flow through the chain.
238+
239+
```typescript
240+
import { Formatter, type Hook } from "intlit";
241+
242+
const upper: Hook = (ctx) => String(ctx.current ?? "").toUpperCase();
243+
244+
const fallback: Hook = (ctx) =>
245+
ctx.current == null || ctx.current === "" ? "(none)" : ctx.current;
246+
247+
const formatter = new Formatter({
248+
locale: "en",
249+
hooks: { upper, fallback },
250+
});
251+
252+
formatter.format("Hello, {name.upper.fallback}!", { name: "alex" });
253+
// → "Hello, ALEX!"
254+
```
255+
256+
Hooks receive:
257+
258+
- `ctx.original`: The value that came directly from `Formatter#format`.
259+
- `ctx.current`: The value produced so far in the chain. Whatever the hook
260+
returns becomes the new `ctx.current`.
261+
- `ctx.locale`: Locale that was supplied when constructing the formatter.
262+
- `args`: Arguments passed from the template method such as the `currency` part
263+
in `{price.number:currency}`. Arguments can include nested templates.
264+
265+
To share state across hook invocations, keep data in a
266+
`WeakMap<HookContext, …>`. The built-in gender and plural hooks follow this
267+
pattern to remember which branch has already matched.
268+
269+
Combine custom hooks with the built-in plural helpers to cover complex cases
270+
without branching in your code.
271+
272+
## Formatter Options
273+
274+
`new Formatter(options)` accepts the following configuration:
275+
276+
- `locale`: Locale passed to plural rules. Defaults to `en`.
277+
- `messages`: A record of source text → translated text pairs.
278+
- `hooks`: Extra hook implementations that augment or replace the defaults.
279+
- `taggedTemplate`: Custom renderer for the template literal output.
280+
281+
Instantiate different `Formatter` objects per locale, or swap the `messages`
282+
object dynamically when building multi-locale applications.

deno.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
},
1313
"imports": {
1414
"@deno/dnt": "jsr:@deno/dnt@^0.41.1",
15-
"@kokr/text": "npm:@kokr/text@^0.4.1",
1615
"@std/assert": "jsr:@std/assert@^0.222.1",
1716
"@std/fmt": "jsr:@std/fmt@^0.222.1",
1817
"@std/testing": "jsr:@std/testing@^0.222.1"

formatter.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { text } from "@kokr/text";
21
import { assertEquals } from "@std/assert/assert-equals";
32
import { Formatter } from "./formatter.ts";
43

@@ -37,7 +36,6 @@ Deno.test("formatter, plural", () => {
3736
const formatter = new Formatter({
3837
locale: "ko",
3938
messages: messages.ko,
40-
taggedTemplate: text,
4139
});
4240

4341
assertEquals(
@@ -91,3 +89,16 @@ Deno.test("formatter, plural", () => {
9189
);
9290
}
9391
});
92+
93+
Deno.test("formatter, ko josa", () => {
94+
const formatter = new Formatter({ locale: "ko" });
95+
96+
assertEquals(
97+
formatter.format("{name}는 코딩합니다.", { name: "완두" }),
98+
"완두는 코딩합니다.",
99+
);
100+
assertEquals(
101+
formatter.format("{name}는 코딩합니다.", { name: "완삼" }),
102+
"완삼은 코딩합니다.",
103+
);
104+
});

formatter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defaultHooks } from "./default_hooks.ts";
22
import { Interpreter } from "./interpreter.ts";
3+
import { template } from "./langs/ko/template.ts";
34
import type {
45
FormatParameters,
56
Hook,
@@ -25,7 +26,8 @@ export class Formatter<Messages extends Record<string, string>> {
2526
...defaultHooks,
2627
...options.hooks,
2728
},
28-
taggedTemplate: options.taggedTemplate,
29+
taggedTemplate: options.taggedTemplate ??
30+
(options.locale === "ko" ? template : undefined),
2931
},
3032
);
3133
this.messages = messages ?? {} as Messages;

0 commit comments

Comments
 (0)