Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ jobs:
# bundling regressions that make the published bin unrunnable.
- run: node dist/cli.js --help
- run: bun dist/cli.js --help
- run: bun test
- run: bun run test
- run: bunx tsc --noEmit
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun test
- run: bun run test
- run: bun run build

- uses: actions/setup-node@v4
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ logs
*.tsbuildinfo
.idea
.DS_Store
tests/.tmp-fixtures
107 changes: 102 additions & 5 deletions bun.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"test": "bun test",
"test": "bun test --conditions=browser",
"prepublishOnly": "bun run build"
},
"keywords": [
Expand Down Expand Up @@ -80,17 +80,20 @@
}
},
"dependencies": {
"@babel/parser": "^7.26.0",
"glob": "^11.0.0",
"zod": "^3.23.0"
},
"devDependencies": {
"@ai-sdk/anthropic": "^1.2.12",
"@ai-sdk/google": "^1.2.22",
"@ai-sdk/openai": "^1.3.24",
"@babel/core": "^7.26.0",
"@happy-dom/global-registrator": "^16.0.0",
"@semantic-release/git": "^10.0.1",
"@types/bun": "latest",
"ai": "^4.0.0",
"@semantic-release/git": "^10.0.1",
"babel-preset-solid": "^1.9.12",
"semantic-release": "25",
"solid-js": "^1.8.0",
"tsup": "^8.0.0",
Expand Down
11 changes: 10 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {
import { resolve, join, dirname, relative, basename } from "node:path";
import { translateBatch, translateMarkdown } from "./translate.js";
import { hashContent } from "./hash.js";
import { extractStringsFromSource } from "./extract.js";
import {
extractStringsFromSource,
type ExtractWarning,
} from "./extract.js";
import { syncLocaleFiles, formatSyncFailures } from "./lock.js";
import type { CLIConfig, LockFile } from "./types.js";

Expand Down Expand Up @@ -188,6 +191,7 @@ async function runExtract() {

const { glob } = await import("glob");
const strings: Record<string, string> = {};
const warnings: ExtractWarning[] = [];
let total = 0;

for (const pattern of patterns) {
Expand All @@ -198,6 +202,7 @@ async function runExtract() {
const extracted = extractStringsFromSource(
code,
relative(root, file),
warnings,
);
for (const entry of extracted) {
strings[entry.key] = entry.source;
Expand All @@ -209,6 +214,10 @@ async function runExtract() {
}
}

for (const w of warnings) {
console.warn(`warning: ${w.file}:${w.line} — ${w.message}`);
}

// Merge with existing source file
const sourceFilePath = join(localesDir, `${sourceLocale}.json`);
let existing: Record<string, string> = {};
Expand Down
15 changes: 13 additions & 2 deletions src/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,15 @@ export interface PluralProps {
/**
* Renders the appropriate plural form based on CLDR plural rules for the current locale.
*
* String forms are translated through the translation dictionary (the source
* string is the key — matching extraction) and support an `{n}` placeholder
* interpolated with the count. Non-string forms render as-is, untranslated.
*
* ```tsx
* <Plural n={count()}
* zero="No items"
* one="1 item"
* other={`${count()} items`}
* other="{n} items"
* />
* ```
*/
Expand All @@ -169,7 +173,14 @@ export function Plural(props: PluralProps): JSX.Element {
other: props.other,
};

return forms[category] ?? props.other;
const form = forms[category] ?? props.other;

// Translate string forms through the dictionary, keyed by source string
if (ctx && typeof form === "string") {
return ctx.t(form, { n: props.n });
}

return form;
}) as unknown as JSX.Element;
}

Expand Down
Loading
Loading