@krizaka/i18n 0.1.0-beta.0 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/react.d.ts +2 -70
- package/dist/rich.d.ts +9 -1
- package/dist/rich.js +15 -4
- package/dist/rich.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Node ≥ 20, ESM only. React (≥ 18) only for `./rich` and `./react`; TypeScrip
|
|
|
25
25
|
| Entry | What | React |
|
|
26
26
|
| :-- | :-- | :-- |
|
|
27
27
|
| `@krizaka/i18n` | `createI18n`, `format`, `placeholdersOf`, `isPluralMessage`, the types | no |
|
|
28
|
-
| `@krizaka/i18n/rich` | `<Rich text href? renderLink?>` | no hook — Server Components too |
|
|
28
|
+
| `@krizaka/i18n/rich` | `<Rich text href? renderLink? renderBold? slots?>` | no hook — Server Components too |
|
|
29
29
|
| `@krizaka/i18n/react` | `createI18nReact(i18n)` → `I18nProvider`, `useI18n` | client (`"use client"`) |
|
|
30
30
|
| `@krizaka/i18n/check` | `checkMessages`, `findUnused`, `scanHardcoded`, `main` (the CLI, programmatic) | no |
|
|
31
31
|
| `krizaka-i18n` (bin) | `check` · `scan` | no |
|
|
@@ -93,8 +93,14 @@ import { Rich } from "@krizaka/i18n/rich";
|
|
|
93
93
|
<Rich text={t("legal.read")} href={["/terms", "/privacy"]} renderLink={({ href, children }) => <Link href={href!}>{children}</Link>} />
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
```tsx
|
|
97
|
+
<Rich text={t("auth.sent")} slots={{ email: <strong className="break-all">{email}</strong> }} /> // "{email}" → the node
|
|
98
|
+
<Rich text={t("home.lead")} renderBold={(children) => <strong className="font-semibold text-fg">{children}</strong>} />
|
|
99
|
+
```
|
|
100
|
+
|
|
96
101
|
Messages are text, never HTML: an unknown or unbalanced tag is shown as written. Each `<a>` takes the next `href`; a
|
|
97
|
-
link without one renders its words only. `<b>` and `<a>` may hold each other.
|
|
102
|
+
link without one renders its words only. `<b>` and `<a>` may hold each other. `slots` puts a node in place of a
|
|
103
|
+
`{name}` placeholder (one without a slot stays as written); `renderBold` styles the `<b>`.
|
|
98
104
|
|
|
99
105
|
## The CLI
|
|
100
106
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,75 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The types of a message catalogue. `en.json` (the reference) gives the shape; every other locale has the same one.
|
|
7
|
-
* A key is the dotted path to a leaf (`"auction.phase.SOLD"`); a leaf is a message string or a set of plural forms.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** A value a message may hold: a string, a plural set, a nested branch, or a list (long structured content). */
|
|
11
|
-
type MessageValue = string | Dictionary | readonly MessageValue[];
|
|
12
|
-
/** A message catalogue, as `import en from "./messages/en.json"` types it. */
|
|
13
|
-
interface Dictionary {
|
|
14
|
-
readonly [key: string]: MessageValue;
|
|
15
|
-
}
|
|
16
|
-
/** A value a placeholder accepts. */
|
|
17
|
-
type PlaceholderValue = string | number;
|
|
18
|
-
/** The values handed to `format` / `t`: `{ count: 3, name: "Ada" }`. */
|
|
19
|
-
type Values = Readonly<Record<string, PlaceholderValue | null | undefined>>;
|
|
20
|
-
/** The plural categories a plural set may carry (`zero` is the explicit "exactly 0" form). */
|
|
21
|
-
type PluralCategory = keyof PluralForms;
|
|
22
|
-
type Join<P extends string, K extends string> = P extends "" ? K : `${P}.${K}`;
|
|
23
|
-
/** True when T is a plural set: an object whose keys are plural categories, `other` included. */
|
|
24
|
-
type IsPlural<T> = T extends {
|
|
25
|
-
other: string;
|
|
26
|
-
} ? Exclude<keyof T, PluralCategory> extends never ? true : false : false;
|
|
27
|
-
/**
|
|
28
|
-
* Every key of a catalogue: the dotted path to each string and each plural set. Lists are not keys (read them as a
|
|
29
|
-
* branch with `getDictionary`). `MessageKey<typeof en>` → `"home.title" | "auction.bids" | …`.
|
|
30
|
-
*/
|
|
31
|
-
type MessageKey<D, P extends string = ""> = D extends readonly unknown[] ? never : {
|
|
32
|
-
[K in keyof D & string]: D[K] extends string ? Join<P, K> : IsPlural<D[K]> extends true ? Join<P, K> : D[K] extends readonly unknown[] ? never : D[K] extends object ? MessageKey<D[K], Join<P, K>> : never;
|
|
33
|
-
}[keyof D & string];
|
|
34
|
-
/** The message at a key: `MessageAt<typeof en, "home.title">`. */
|
|
35
|
-
type MessageAt<D, K extends string> = K extends `${infer H}.${infer R}` ? H extends keyof D ? MessageAt<D[H], R> : never : K extends keyof D ? D[K] : never;
|
|
36
|
-
/** The `{placeholders}` of a literal message type: `Placeholders<"{count} of {total}">` → `"count" | "total"`. */
|
|
37
|
-
type Placeholders<S extends string> = S extends `${string}{${infer N}}${infer R}` ? N extends `${string}${" " | "{"}${string}` ? Placeholders<R> : N | Placeholders<R> : never;
|
|
38
|
-
/**
|
|
39
|
-
* The arguments `t` takes after the key. A message typed as a literal (a catalogue written `as const`) requires its
|
|
40
|
-
* placeholders; a plural set requires `count`; a message typed `string` (a JSON import widens literals) takes any values.
|
|
41
|
-
*/
|
|
42
|
-
type ValuesArgs<M> = IsPlural<M> extends true ? [values: Values & {
|
|
43
|
-
readonly count: number;
|
|
44
|
-
}] : M extends string ? string extends M ? [values?: Values] : [Placeholders<M>] extends [never] ? [values?: Values] : [values: Values & {
|
|
45
|
-
readonly [N in Placeholders<M>]: PlaceholderValue;
|
|
46
|
-
}] : [values?: Values];
|
|
47
|
-
/** A translator bound to one locale: `t("auction.bids", { count: 3 })`. */
|
|
48
|
-
type Translate<D> = <K extends MessageKey<D>>(key: K, ...values: ValuesArgs<MessageAt<D, K>>) => string;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Replaces the `{name}` placeholders of a message: `format("{count} repositories", { count: 23 })` → `"23 repositories"`.
|
|
52
|
-
* A placeholder without a value (missing, `null` or `undefined`) stays as written, so a gap is visible, never silent.
|
|
53
|
-
*/
|
|
54
|
-
declare function format(message: string, values?: Values): string;
|
|
55
|
-
|
|
56
|
-
interface I18n<D extends Dictionary, L extends string = string> {
|
|
57
|
-
/** The supported locales, in the order the catalogues were given. */
|
|
58
|
-
readonly locales: readonly L[];
|
|
59
|
-
readonly defaultLocale: L;
|
|
60
|
-
/** True when the value is a supported locale. */
|
|
61
|
-
isLocale(value: unknown): value is L;
|
|
62
|
-
/** Narrows any value (a route segment, a cookie) to a supported locale: the default one for anything else. */
|
|
63
|
-
asLocale(value: unknown): L;
|
|
64
|
-
/** The whole catalogue of a locale (the default one for an unknown locale) — for Server Components and branches. */
|
|
65
|
-
getDictionary(locale: unknown): D;
|
|
66
|
-
/** The translator bound to a locale: `translator("fr")("auction.bids", { count: 3 })`. */
|
|
67
|
-
translator(locale: unknown): Translate<D>;
|
|
68
|
-
/** The translator of the default locale. */
|
|
69
|
-
t: Translate<D>;
|
|
70
|
-
/** `{placeholder}` replacement, the same function as the package's `format`. */
|
|
71
|
-
format: typeof format;
|
|
72
|
-
}
|
|
3
|
+
import { Dictionary, Translate, format, I18n } from './index.js';
|
|
4
|
+
import '@krizaka/intl/plural';
|
|
73
5
|
|
|
74
6
|
/** What `useI18n()` returns. */
|
|
75
7
|
interface I18nContextValue<D extends Dictionary, L extends string> {
|
package/dist/rich.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ interface RichProps {
|
|
|
15
15
|
href?: string | readonly string[];
|
|
16
16
|
/** Renders a link (e.g. with `next/link`). Default: `<a href>`, or the plain words when the link has no href. */
|
|
17
17
|
renderLink?: (link: RichLink) => ReactNode;
|
|
18
|
+
/** Renders a `<b>…</b>` (e.g. with the app's classes). Default: `<strong>`. */
|
|
19
|
+
renderBold?: (children: ReactNode) => ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Nodes put in place of `{name}` placeholders (a link, an emphasised value): `slots={{ email: <strong>{email}</strong> }}`.
|
|
22
|
+
* A placeholder without a slot stays as written. Plain values go through `format` / `t` before, not here.
|
|
23
|
+
*/
|
|
24
|
+
slots?: Readonly<Record<string, ReactNode>>;
|
|
18
25
|
}
|
|
19
26
|
/**
|
|
20
27
|
* Renders a message with its markup — `<b>` → `<strong>`, `<a>` → a link — and everything else as text (never HTML:
|
|
@@ -24,8 +31,9 @@ interface RichProps {
|
|
|
24
31
|
* ```tsx
|
|
25
32
|
* <Rich text={t.home.lead} />
|
|
26
33
|
* <Rich text="Read <a>the docs</a>, <b>now</b>." href="/docs" renderLink={({ href, children }) => <Link href={href!}>{children}</Link>} />
|
|
34
|
+
* <Rich text="We sent a link to {email}." slots={{ email: <strong>{email}</strong> }} />
|
|
27
35
|
* ```
|
|
28
36
|
*/
|
|
29
|
-
declare function Rich({ text, href, renderLink }: RichProps): ReactNode;
|
|
37
|
+
declare function Rich({ text, href, renderLink, renderBold, slots }: RichProps): ReactNode;
|
|
30
38
|
|
|
31
39
|
export { Rich, type RichLink, type RichProps, Rich as default };
|
package/dist/rich.js
CHANGED
|
@@ -2,12 +2,23 @@
|
|
|
2
2
|
import { Fragment } from "react";
|
|
3
3
|
import { Fragment as Fragment2, jsx } from "react/jsx-runtime";
|
|
4
4
|
var TAG = /<(b|a)>([\s\S]*?)<\/\1>/g;
|
|
5
|
+
var SLOT = /\{(\w+)\}/;
|
|
5
6
|
function defaultLink({ href, children }) {
|
|
6
7
|
return href === void 0 ? children : /* @__PURE__ */ jsx("a", { href, children });
|
|
7
8
|
}
|
|
8
|
-
function
|
|
9
|
+
function defaultBold(children) {
|
|
10
|
+
return /* @__PURE__ */ jsx("strong", { children });
|
|
11
|
+
}
|
|
12
|
+
function Rich({ text, href, renderLink = defaultLink, renderBold = defaultBold, slots }) {
|
|
9
13
|
const hrefs = typeof href === "string" ? [href] : href ?? [];
|
|
10
14
|
let links = 0;
|
|
15
|
+
const plain = (source, key) => {
|
|
16
|
+
if (!slots) return /* @__PURE__ */ jsx(Fragment, { children: source }, key);
|
|
17
|
+
const parts = source.split(SLOT);
|
|
18
|
+
return /* @__PURE__ */ jsx(Fragment, { children: parts.map(
|
|
19
|
+
(part, i) => i % 2 === 0 ? part : /* @__PURE__ */ jsx(Fragment, { children: Object.hasOwn(slots, part) ? slots[part] : `{${part}}` }, i)
|
|
20
|
+
) }, key);
|
|
21
|
+
};
|
|
11
22
|
const render = (source, prefix) => {
|
|
12
23
|
const out = [];
|
|
13
24
|
let last = 0;
|
|
@@ -15,9 +26,9 @@ function Rich({ text, href, renderLink = defaultLink }) {
|
|
|
15
26
|
const [whole, tag, inner] = match;
|
|
16
27
|
const at = match.index;
|
|
17
28
|
const key = `${prefix}${out.length}`;
|
|
18
|
-
if (at > last) out.push(
|
|
29
|
+
if (at > last) out.push(plain(source.slice(last, at), `${key}t`));
|
|
19
30
|
if (tag === "b") {
|
|
20
|
-
out.push(/* @__PURE__ */ jsx(
|
|
31
|
+
out.push(/* @__PURE__ */ jsx(Fragment, { children: renderBold(render(inner, `${key}.`)) }, key));
|
|
21
32
|
} else {
|
|
22
33
|
const index = links++;
|
|
23
34
|
out.push(
|
|
@@ -26,7 +37,7 @@ function Rich({ text, href, renderLink = defaultLink }) {
|
|
|
26
37
|
}
|
|
27
38
|
last = at + whole.length;
|
|
28
39
|
}
|
|
29
|
-
if (last < source.length) out.push(
|
|
40
|
+
if (last < source.length) out.push(plain(source.slice(last), `${prefix}end`));
|
|
30
41
|
return out;
|
|
31
42
|
};
|
|
32
43
|
return /* @__PURE__ */ jsx(Fragment2, { children: render(text, "") });
|
package/dist/rich.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rich.tsx"],"sourcesContent":["import { Fragment, type ReactNode } from \"react\";\n\n/** What `renderLink` receives for each `<a>…</a>` of a message, in order of appearance. */\nexport interface RichLink {\n /** The href given for this link (`href[index]`, or `href` itself when it is a string), if any. */\n href: string | undefined;\n /** The position of the link in the message: 0 for the first `<a>`. */\n index: number;\n children: ReactNode;\n}\n\nexport interface RichProps {\n /** The message: plain text with `<b>…</b>` emphasis and `<a>…</a>` links, the only markup messages may carry. */\n text: string;\n /** The target of each `<a>`, in order (a string for a single link). The words stay in the message, the URL in code. */\n href?: string | readonly string[];\n /** Renders a link (e.g. with `next/link`). Default: `<a href>`, or the plain words when the link has no href. */\n renderLink?: (link: RichLink) => ReactNode;\n}\n\nconst TAG = /<(b|a)>([\\s\\S]*?)<\\/\\1>/g;\n\nfunction defaultLink({ href, children }: RichLink): ReactNode {\n return href === undefined ? children : <a href={href}>{children}</a>;\n}\n\n/**\n * Renders a message with its markup — `<b>` → `<strong>`, `<a>` → a link — and everything else as text (never HTML:\n * no `dangerouslySetInnerHTML`, an unknown or unbalanced tag is shown as written). `<b>` and `<a>` may hold each other.\n * No hook, no context: usable in a Server Component.\n *\n * ```tsx\n * <Rich text={t.home.lead} />\n * <Rich text=\"Read <a>the docs</a>, <b>now</b>.\" href=\"/docs\" renderLink={({ href, children }) => <Link href={href!}>{children}</Link>} />\n * ```\n */\nexport function Rich({ text, href, renderLink = defaultLink }: RichProps): ReactNode {\n const hrefs = typeof href === \"string\" ? [href] : (href ?? []);\n let links = 0;\n\n // The match is lazy: inside <b>…</b> there is no </b>, so only an <a> can match there (and the other way round).\n const render = (source: string, prefix: string): ReactNode[] => {\n const out: ReactNode[] = [];\n let last = 0;\n for (const match of source.matchAll(TAG)) {\n const [whole, tag, inner] = match as unknown as [string, \"a\" | \"b\", string];\n const at = match.index as number;\n const key = `${prefix}${out.length}`;\n if (at > last) out.push(
|
|
1
|
+
{"version":3,"sources":["../src/rich.tsx"],"sourcesContent":["import { Fragment, type ReactNode } from \"react\";\n\n/** What `renderLink` receives for each `<a>…</a>` of a message, in order of appearance. */\nexport interface RichLink {\n /** The href given for this link (`href[index]`, or `href` itself when it is a string), if any. */\n href: string | undefined;\n /** The position of the link in the message: 0 for the first `<a>`. */\n index: number;\n children: ReactNode;\n}\n\nexport interface RichProps {\n /** The message: plain text with `<b>…</b>` emphasis and `<a>…</a>` links, the only markup messages may carry. */\n text: string;\n /** The target of each `<a>`, in order (a string for a single link). The words stay in the message, the URL in code. */\n href?: string | readonly string[];\n /** Renders a link (e.g. with `next/link`). Default: `<a href>`, or the plain words when the link has no href. */\n renderLink?: (link: RichLink) => ReactNode;\n /** Renders a `<b>…</b>` (e.g. with the app's classes). Default: `<strong>`. */\n renderBold?: (children: ReactNode) => ReactNode;\n /**\n * Nodes put in place of `{name}` placeholders (a link, an emphasised value): `slots={{ email: <strong>{email}</strong> }}`.\n * A placeholder without a slot stays as written. Plain values go through `format` / `t` before, not here.\n */\n slots?: Readonly<Record<string, ReactNode>>;\n}\n\nconst TAG = /<(b|a)>([\\s\\S]*?)<\\/\\1>/g;\n\nconst SLOT = /\\{(\\w+)\\}/;\n\nfunction defaultLink({ href, children }: RichLink): ReactNode {\n return href === undefined ? children : <a href={href}>{children}</a>;\n}\n\nfunction defaultBold(children: ReactNode): ReactNode {\n return <strong>{children}</strong>;\n}\n\n/**\n * Renders a message with its markup — `<b>` → `<strong>`, `<a>` → a link — and everything else as text (never HTML:\n * no `dangerouslySetInnerHTML`, an unknown or unbalanced tag is shown as written). `<b>` and `<a>` may hold each other.\n * No hook, no context: usable in a Server Component.\n *\n * ```tsx\n * <Rich text={t.home.lead} />\n * <Rich text=\"Read <a>the docs</a>, <b>now</b>.\" href=\"/docs\" renderLink={({ href, children }) => <Link href={href!}>{children}</Link>} />\n * <Rich text=\"We sent a link to {email}.\" slots={{ email: <strong>{email}</strong> }} />\n * ```\n */\nexport function Rich({ text, href, renderLink = defaultLink, renderBold = defaultBold, slots }: RichProps): ReactNode {\n const hrefs = typeof href === \"string\" ? [href] : (href ?? []);\n let links = 0;\n\n /** Plain text, with its `{slot}` placeholders replaced by their nodes. */\n const plain = (source: string, key: string): ReactNode => {\n if (!slots) return <Fragment key={key}>{source}</Fragment>;\n const parts = source.split(SLOT);\n return (\n <Fragment key={key}>\n {parts.map((part, i) =>\n i % 2 === 0 ? part : <Fragment key={i}>{Object.hasOwn(slots, part) ? slots[part] : `{${part}}`}</Fragment>,\n )}\n </Fragment>\n );\n };\n\n // The match is lazy: inside <b>…</b> there is no </b>, so only an <a> can match there (and the other way round).\n const render = (source: string, prefix: string): ReactNode[] => {\n const out: ReactNode[] = [];\n let last = 0;\n for (const match of source.matchAll(TAG)) {\n const [whole, tag, inner] = match as unknown as [string, \"a\" | \"b\", string];\n const at = match.index as number;\n const key = `${prefix}${out.length}`;\n if (at > last) out.push(plain(source.slice(last, at), `${key}t`));\n if (tag === \"b\") {\n out.push(<Fragment key={key}>{renderBold(render(inner, `${key}.`))}</Fragment>);\n } else {\n const index = links++;\n out.push(\n <Fragment key={key}>{renderLink({ href: hrefs[index], index, children: render(inner, `${key}.`) })}</Fragment>,\n );\n }\n last = at + whole.length;\n }\n if (last < source.length) out.push(plain(source.slice(last), `${prefix}end`));\n return out;\n };\n\n return <>{render(text, \"\")}</>;\n}\n\nexport default Rich;\n"],"mappings":";AAAA,SAAS,gBAAgC;AAgCA,SA0DhC,YAAAA,WA1DgC;AALzC,IAAM,MAAM;AAEZ,IAAM,OAAO;AAEb,SAAS,YAAY,EAAE,MAAM,SAAS,GAAwB;AAC5D,SAAO,SAAS,SAAY,WAAW,oBAAC,OAAE,MAAa,UAAS;AAClE;AAEA,SAAS,YAAY,UAAgC;AACnD,SAAO,oBAAC,YAAQ,UAAS;AAC3B;AAaO,SAAS,KAAK,EAAE,MAAM,MAAM,aAAa,aAAa,aAAa,aAAa,MAAM,GAAyB;AACpH,QAAM,QAAQ,OAAO,SAAS,WAAW,CAAC,IAAI,IAAK,QAAQ,CAAC;AAC5D,MAAI,QAAQ;AAGZ,QAAM,QAAQ,CAAC,QAAgB,QAA2B;AACxD,QAAI,CAAC,MAAO,QAAO,oBAAC,YAAoB,oBAAN,GAAa;AAC/C,UAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,WACE,oBAAC,YACE,gBAAM;AAAA,MAAI,CAAC,MAAM,MAChB,IAAI,MAAM,IAAI,OAAO,oBAAC,YAAkB,iBAAO,OAAO,OAAO,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,OAAvD,CAA2D;AAAA,IACjG,KAHa,GAIf;AAAA,EAEJ;AAGA,QAAM,SAAS,CAAC,QAAgB,WAAgC;AAC9D,UAAM,MAAmB,CAAC;AAC1B,QAAI,OAAO;AACX,eAAW,SAAS,OAAO,SAAS,GAAG,GAAG;AACxC,YAAM,CAAC,OAAO,KAAK,KAAK,IAAI;AAC5B,YAAM,KAAK,MAAM;AACjB,YAAM,MAAM,GAAG,MAAM,GAAG,IAAI,MAAM;AAClC,UAAI,KAAK,KAAM,KAAI,KAAK,MAAM,OAAO,MAAM,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,CAAC;AAChE,UAAI,QAAQ,KAAK;AACf,YAAI,KAAK,oBAAC,YAAoB,qBAAW,OAAO,OAAO,GAAG,GAAG,GAAG,CAAC,KAAzC,GAA2C,CAAW;AAAA,MAChF,OAAO;AACL,cAAM,QAAQ;AACd,YAAI;AAAA,UACF,oBAAC,YAAoB,qBAAW,EAAE,MAAM,MAAM,KAAK,GAAG,OAAO,UAAU,OAAO,OAAO,GAAG,GAAG,GAAG,EAAE,CAAC,KAAlF,GAAoF;AAAA,QACrG;AAAA,MACF;AACA,aAAO,KAAK,MAAM;AAAA,IACpB;AACA,QAAI,OAAO,OAAO,OAAQ,KAAI,KAAK,MAAM,OAAO,MAAM,IAAI,GAAG,GAAG,MAAM,KAAK,CAAC;AAC5E,WAAO;AAAA,EACT;AAEA,SAAO,oBAAAA,WAAA,EAAG,iBAAO,MAAM,EAAE,GAAE;AAC7B;AAEA,IAAO,eAAQ;","names":["Fragment"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krizaka/i18n",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
4
|
"description": "The i18n engine of every Krizaka app: typed message keys and placeholders from en.json, format(), plurals through @krizaka/intl, <Rich> (<b>, <a>), a React provider, and the krizaka-i18n CLI that checks catalogues and finds hard-coded strings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"krizaka",
|
|
@@ -123,6 +123,6 @@
|
|
|
123
123
|
"typecheck": "tsc --noEmit",
|
|
124
124
|
"test": "vitest run --coverage",
|
|
125
125
|
"size": "size-limit",
|
|
126
|
-
"publint": "publint && attw --pack . --profile esm-only"
|
|
126
|
+
"publint": "publint && attw --pack . --profile esm-only && tsc -p test/consumer/tsconfig.json"
|
|
127
127
|
}
|
|
128
128
|
}
|