Skip to content

Commit 2f32e25

Browse files
committed
Add docs
1 parent c919a06 commit 2f32e25

13 files changed

+218
-24
lines changed

lib/hono/HonolateContext.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { LocalizedValue } from "./LocalizedValue.ts";
22

3+
/**
4+
* The context type for Honolate, extending Hono's Context with localization-related variables.
5+
*/
36
export type HonolateContext<T extends string> = {
47
Variables: {
58
language?: T;

lib/hono/InitHonolateOptions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import type { JsonPath } from "./JsonPath.ts";
22

3+
/**
4+
* Options for initializing Honolate middleware in a Hono application.
5+
*/
36
export interface InitHonolateOptions<T extends string> {
7+
/**
8+
* The default language to fall back to when no specific language is set or detected.
9+
*/
410
readonly defaultLanguage: T;
11+
/**
12+
* A mapping of supported languages to their respective JSON path files containing localization data.
13+
*/
514
readonly languages: Readonly<
615
{
716
[key in T]: JsonPath;
817
}
918
>;
19+
/**
20+
* A global pattern for files to search for localization keys.
21+
*/
1022
pattern?: string;
1123
}

lib/hono/JsonPath.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1+
/**
2+
* Path to a JSON file containing localization data.
3+
*/
14
export type JsonPath = string;

lib/hono/LazyLocalyzedString.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
import type { LocalyzedStringValue } from "./LocalyzedStringValue.ts";
22

3+
/**
4+
* A lazily evaluated localized string with a key and optional values for interpolation.
5+
*
6+
* Gets resolved later – when the locale is known – using the {@link import("t.ts").t} function.
7+
*/
38
export type LazyLocalyzedString = {
9+
/**
10+
* The localization key used to look up the localized string.
11+
*/
412
localizationKey: string;
13+
/**
14+
* An array of values to be interpolated into the localized string.
15+
*
16+
* `"{i}"` gets replaced by the i-th value in this array. `"\{i}"` can be used to escape the `"{"` character.
17+
*/
518
values: LocalyzedStringValue[];
619
};

lib/hono/LocalizedValue.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1+
/**
2+
* A localized string value.
3+
*
4+
* This is the value of a localization key in the localization JSON files.
5+
*/
16
export type LocalizedValue = string;

lib/hono/LocalyzedStringValue.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
11
import type { LazyLocalyzedString } from "./LazyLocalyzedString.ts";
22

3+
/**
4+
* A value passed into a localized string for interpolation.
5+
*
6+
* This can be a string, number, or another {@link LazyLocalyzedString} for nested localization.
7+
*
8+
* For example, given the localization entry:
9+
*
10+
* ```json
11+
* {
12+
* "greeting": "Hello, {0}!",
13+
* "welcomeMessage": "{0} Welcome to our site."
14+
* }
15+
* ```
16+
*
17+
* You could create a `LazyLocalyzedString` for `welcomeMessage` that includes another
18+
* `LazyLocalyzedString` for `greeting` as its first value:
19+
*
20+
* ```ts
21+
* const lazyGreeting: LazyLocalyzedString = {
22+
* localizationKey: "greeting",
23+
* values: ["Alice"]
24+
* };
25+
*
26+
* const lazyWelcomeMessage: LazyLocalyzedString = {
27+
* localizationKey: "welcomeMessage",
28+
* values: [lazyGreeting]
29+
* };
30+
* ```
31+
*/
332
export type LocalyzedStringValue = string | number | LazyLocalyzedString;

lib/hono/ensureLazyLocalyzedString.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import type { LazyLocalyzedString } from "./LazyLocalyzedString.ts";
22
import type { LocalyzedStringValue } from "./LocalyzedStringValue.ts";
33
import { lt } from "./lt.ts";
44

5+
/**
6+
* Ensures that the input is a LazyLocalyzedString.
7+
*
8+
* Can be applied to either a template string or an already existing LazyLocalyzedString.
9+
*
10+
* @returns the input as a LazyLocalyzedString
11+
*/
512
export function ensureLazyLocalyzedString(
613
input: TemplateStringsArray | LazyLocalyzedString,
714
values: LocalyzedStringValue[],

lib/hono/getLocalizationMap.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useRequestContext } from "@hono/hono/jsx-renderer";
22
import { neverThrow } from "../common/neverThrow.ts";
33

4+
/**
5+
* @returns the current locale's localization map, mapping localization keys to their respective translations
6+
*/
47
export function getLocalizationMap(): Record<string, string> {
58
const ctx = neverThrow(() => useRequestContext());
69
if (ctx instanceof Error) {

lib/hono/initHonolate.ts

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ import type { InitHonolateOptions } from "./InitHonolateOptions.ts";
66
import type { LocalizedValue } from "./LocalizedValue.ts";
77
import { ensureRequestLanguage } from "./ensureRequestLanguage.ts";
88

9+
/**
10+
* Initializes Honolate with the given options.
11+
*
12+
* Make sure to add the returned middleware to your Hono app before any route handlers that use localization.
13+
*
14+
* @param options the options with which Honolate will be initialized. See {@link InitHonolateOptions}
15+
* @returns the middleware required to show localized strings using Honolate
16+
*
17+
* @example
18+
* import { InitHonolateOptions, initHonolate } from '@wuespace/honolate';
19+
*
20+
* const honolateOptions: InitHonolateOptions<'en' | 'de'> = {
21+
* defaultLanguage: 'en',
22+
* languages: {
23+
* en: './locales/en.json',
24+
* de: './locales/de.json',
25+
* },
26+
* // optional: add custom language detection logic here
27+
* };
28+
*
29+
* const app = new Hono();
30+
* app.use('*', initHonolate(honolateOptions));
31+
* // ...other middlewares and route handlers
32+
*/
933
export const initHonolate: <T extends string>(
1034
options: InitHonolateOptions<T>,
1135
) => ReturnType<typeof createMiddleware<HonolateContext<T>>> = <
@@ -18,30 +42,7 @@ export const initHonolate: <T extends string>(
1842
const languages = new Map<T, Record<string, LocalizedValue>>();
1943

2044
for (const lang in options.languages) {
21-
const path = options.languages[lang as T];
22-
const module = neverThrow(() => Deno.readFileSync(normalizePath(path)));
23-
if (module instanceof Error) {
24-
console.error(
25-
`Failed to load language file for ${lang} at ${path}:`,
26-
module,
27-
);
28-
languages.set(lang as T, {});
29-
continue;
30-
}
31-
32-
const decodedModule = new TextDecoder().decode(module);
33-
const parsedModule = neverThrow(() => JSON.parse(decodedModule));
34-
35-
if (parsedModule instanceof Error) {
36-
console.error(
37-
`Failed to parse language file for ${lang} at ${path}:`,
38-
parsedModule,
39-
);
40-
languages.set(lang as T, {});
41-
continue;
42-
}
43-
44-
languages.set(lang as T, parsedModule as Record<string, LocalizedValue>);
45+
languages.set(lang, loadLanguage(options.languages[lang]));
4546
}
4647

4748
return createMiddleware<HonolateContext<T>>(async (c, next) => {
@@ -56,3 +57,39 @@ export const initHonolate: <T extends string>(
5657
return next();
5758
});
5859
};
60+
61+
/**
62+
* Loads a language file from the given path.
63+
* @param languagePath the path to the language
64+
* @returns the loaded localization map
65+
*/
66+
function loadLanguage(languagePath: string): Record<string, LocalizedValue> {
67+
// Load
68+
const module = neverThrow(() =>
69+
Deno.readFileSync(normalizePath(languagePath))
70+
);
71+
72+
if (module instanceof Error) {
73+
// Handle file read error
74+
console.error(
75+
`Failed to load language file at ${languagePath}:`,
76+
module,
77+
);
78+
return {};
79+
}
80+
81+
// Parse
82+
const decodedModule = new TextDecoder().decode(module);
83+
const parsedModule = neverThrow(() => JSON.parse(decodedModule));
84+
85+
if (parsedModule instanceof Error) {
86+
// Handle JSON parse error
87+
console.error(
88+
`Failed to parse language file at ${languagePath}:`,
89+
parsedModule,
90+
);
91+
return {};
92+
}
93+
94+
return parsedModule as Record<string, LocalizedValue>;
95+
}

lib/hono/lt.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,37 @@ import { escapeKey } from "../common/escapeKey.ts";
22
import type { LazyLocalyzedString } from "./LazyLocalyzedString.ts";
33
import type { LocalyzedStringValue } from "./LocalyzedStringValue.ts";
44

5+
/**
6+
* A template tag function to create a {@link LazyLocalyzedString}.
7+
* @returns A {@link LazyLocalyzedString} representing the localization key and its values.
8+
*
9+
* @example
10+
* ```ts
11+
* const lazyString = lt`welcomeMessage`;
12+
*
13+
* const lazyStringWithValues = lt`welcomeMessage, {0}`("Alice");
14+
*
15+
* const nestedLazyString = lt`greeting, {0}`(
16+
* lt`userName, {0}`("Alice")
17+
* );
18+
*
19+
* // [...in a Hono rendering context...]
20+
* <>
21+
* <p>{t(lazyString)}</p>
22+
* <p>{t(lazyStringWithValues)}</p>
23+
* <p>{t(nestedLazyString)}</p>
24+
* </>
25+
* ```
26+
*
27+
* @remarks
28+
* The existence of these "late" strings is both the inspiration for the library's name,
29+
* as well as a core concept of how localization is handled. By deferring the resolution of
30+
* localization keys and their values until the rendering phase, we can ensure that the
31+
* correct localized strings are used based on the current context (e.g., user language).
32+
*
33+
* This allows you to, e.g., throw an `Error` with a localized message without needing
34+
* to know the user's language at the time the error is created.
35+
*/
536
export function lt(
637
strings: TemplateStringsArray,
738
...values: LocalyzedStringValue[]

0 commit comments

Comments
 (0)