Skip to content

Commit 209b898

Browse files
committed
chore: Extracted Data Package Validator from dpkit
0 parents  commit 209b898

101 files changed

Lines changed: 14142 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OPENAI_API_KEY="<you-api-key>"

.gitignore

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Mac
2+
.DS_Store
3+
4+
# Node
5+
node_modules/
6+
jspm_packages/
7+
.lock-wscript
8+
build/Release
9+
.node_repl_history
10+
*.tgz
11+
.npm
12+
*.so
13+
14+
# Testing
15+
coverage
16+
htmlcov/
17+
lib-cov
18+
.tox/
19+
.coverage
20+
.coverage.*
21+
.cache
22+
.nyc_output
23+
coverage.xml
24+
*,cover
25+
.hypothesis/
26+
.eslintcache
27+
28+
# Logs
29+
logs
30+
*.log
31+
npm-debug.log*
32+
33+
# Runtime data
34+
pids
35+
*.pid
36+
*.seed
37+
*.pid.lock
38+
39+
# Translations
40+
*.mo
41+
*.pot
42+
43+
# Storybook
44+
*storybook.log
45+
46+
# Notebooks
47+
.ipynb_checkpoints/
48+
49+
# React
50+
# .react-router/
51+
52+
# User
53+
.vim/
54+
.idea/
55+
.next/
56+
.yarn/
57+
.cache/
58+
.clinic/
59+
.vscode/
60+
.wrangler/
61+
build/
62+
compile/
63+
dist/
64+
.astro/
65+
/docs/content/docs/reference/
66+
.cursor/
67+
.claude/
68+
.serena/
69+
.mcp.json
70+
.env
71+
.env.local
72+
messages.js
73+
.user/
74+
tmuxp.yaml

.react-router/types/+future.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Generated by React Router
2+
3+
import "react-router";
4+
5+
declare module "react-router" {
6+
interface Future {
7+
v8_middleware: false
8+
}
9+
}

.react-router/types/+routes.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Generated by React Router
2+
3+
import "react-router"
4+
5+
declare module "react-router" {
6+
interface Register {
7+
pages: Pages
8+
routeFiles: RouteFiles
9+
routeModules: RouteModules
10+
}
11+
}
12+
13+
type Pages = {
14+
"/": {
15+
params: {};
16+
};
17+
};
18+
19+
type RouteFiles = {
20+
"root.tsx": {
21+
id: "root";
22+
page: "/";
23+
};
24+
"home/route.tsx": {
25+
id: "home/route";
26+
page: "/";
27+
};
28+
};
29+
30+
type RouteModules = {
31+
"root": typeof import("./routes/root.tsx");
32+
"home/route": typeof import("./routes/home/route.tsx");
33+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Generated by React Router
2+
3+
declare module "virtual:react-router/server-build" {
4+
import { ServerBuild } from "react-router";
5+
export const assets: ServerBuild["assets"];
6+
export const assetsBuildDirectory: ServerBuild["assetsBuildDirectory"];
7+
export const basename: ServerBuild["basename"];
8+
export const entry: ServerBuild["entry"];
9+
export const future: ServerBuild["future"];
10+
export const isSpaMode: ServerBuild["isSpaMode"];
11+
export const prerender: ServerBuild["prerender"];
12+
export const publicPath: ServerBuild["publicPath"];
13+
export const routeDiscovery: ServerBuild["routeDiscovery"];
14+
export const routes: ServerBuild["routes"];
15+
export const ssr: ServerBuild["ssr"];
16+
export const unstable_getCriticalCss: ServerBuild["unstable_getCriticalCss"];
17+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Generated by React Router
2+
3+
import type { GetInfo, GetAnnotations } from "react-router/internal";
4+
5+
type Module = typeof import("../root.js")
6+
7+
type Info = GetInfo<{
8+
file: "root.tsx",
9+
module: Module
10+
}>
11+
12+
type Matches = [{
13+
id: "root";
14+
module: typeof import("../root.js");
15+
}];
16+
17+
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
18+
19+
export namespace Route {
20+
// links
21+
export type LinkDescriptors = Annotations["LinkDescriptors"];
22+
export type LinksFunction = Annotations["LinksFunction"];
23+
24+
// meta
25+
export type MetaArgs = Annotations["MetaArgs"];
26+
export type MetaDescriptors = Annotations["MetaDescriptors"];
27+
export type MetaFunction = Annotations["MetaFunction"];
28+
29+
// headers
30+
export type HeadersArgs = Annotations["HeadersArgs"];
31+
export type HeadersFunction = Annotations["HeadersFunction"];
32+
33+
// middleware
34+
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
35+
36+
// clientMiddleware
37+
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
38+
39+
// loader
40+
export type LoaderArgs = Annotations["LoaderArgs"];
41+
42+
// clientLoader
43+
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
44+
45+
// action
46+
export type ActionArgs = Annotations["ActionArgs"];
47+
48+
// clientAction
49+
export type ClientActionArgs = Annotations["ClientActionArgs"];
50+
51+
// HydrateFallback
52+
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
53+
54+
// Component
55+
export type ComponentProps = Annotations["ComponentProps"];
56+
57+
// ErrorBoundary
58+
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
59+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Generated by React Router
2+
3+
import type { GetInfo, GetAnnotations } from "react-router/internal";
4+
5+
type Module = typeof import("../route.js")
6+
7+
type Info = GetInfo<{
8+
file: "home/route.tsx",
9+
module: Module
10+
}>
11+
12+
type Matches = [{
13+
id: "root";
14+
module: typeof import("../../root.js");
15+
}, {
16+
id: "home/route";
17+
module: typeof import("../route.js");
18+
}];
19+
20+
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
21+
22+
export namespace Route {
23+
// links
24+
export type LinkDescriptors = Annotations["LinkDescriptors"];
25+
export type LinksFunction = Annotations["LinksFunction"];
26+
27+
// meta
28+
export type MetaArgs = Annotations["MetaArgs"];
29+
export type MetaDescriptors = Annotations["MetaDescriptors"];
30+
export type MetaFunction = Annotations["MetaFunction"];
31+
32+
// headers
33+
export type HeadersArgs = Annotations["HeadersArgs"];
34+
export type HeadersFunction = Annotations["HeadersFunction"];
35+
36+
// middleware
37+
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
38+
39+
// clientMiddleware
40+
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
41+
42+
// loader
43+
export type LoaderArgs = Annotations["LoaderArgs"];
44+
45+
// clientLoader
46+
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
47+
48+
// action
49+
export type ActionArgs = Annotations["ActionArgs"];
50+
51+
// clientAction
52+
export type ClientActionArgs = Annotations["ClientActionArgs"];
53+
54+
// HydrateFallback
55+
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
56+
57+
// Component
58+
export type ComponentProps = Annotations["ComponentProps"];
59+
60+
// ErrorBoundary
61+
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
62+
}

@translate.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { readFile, writeFile } from "node:fs/promises"
2+
import { openai } from "@ai-sdk/openai"
3+
import { tasks } from "@clack/prompts"
4+
import { generateObject } from "ai"
5+
import dotenv from "dotenv"
6+
import { execa } from "execa"
7+
import { po } from "gettext-parser"
8+
import { objectKeys } from "ts-extras"
9+
import { z } from "zod"
10+
import { LanguageIdDefault, Languages } from "#constants/language.ts"
11+
import type { LanguageId } from "#constants/language.ts"
12+
13+
process.chdir(import.meta.dirname)
14+
dotenv.config()
15+
16+
const $ = execa({
17+
stdout: ["inherit", "pipe"],
18+
verbose: "short",
19+
preferLocal: true,
20+
})
21+
22+
await $`lingui extract`
23+
await translateLanguages()
24+
// Handled by Vite
25+
// await $`lingui compile`
26+
27+
async function translateLanguages() {
28+
await tasks(
29+
objectKeys(Languages).map(languageId => ({
30+
title: `Translating ${languageId}`,
31+
task: () => translateLanguage(languageId),
32+
})),
33+
)
34+
}
35+
36+
// TODO: extract empty messages and translate using JSON/JSONSchema
37+
async function translateLanguage(languageId: LanguageId) {
38+
if (languageId === LanguageIdDefault) return
39+
40+
const path = `locales/${languageId}/messages.po`
41+
const source = await readFile(path)
42+
43+
const pofile = po.parse(source)
44+
const translations = pofile.translations[""]
45+
46+
if (!translations) {
47+
return
48+
}
49+
50+
const missingTranslations = Object.values(translations).filter(
51+
message => message.msgstr && !message.msgstr.filter(Boolean).length,
52+
)
53+
54+
if (!missingTranslations.length) {
55+
return
56+
}
57+
58+
const result = await generateObject({
59+
model: openai("gpt-4.1"),
60+
schema: z.object({
61+
translations: z.array(
62+
z.object({
63+
msgid: z.string(),
64+
msgid_plural: z.string().optional(),
65+
msgstr: z.array(z.string()),
66+
}),
67+
),
68+
}),
69+
prompt: `
70+
You are a professional translator. Here is untranslated PO (gettext) translations.
71+
Translate them from **${LanguageIdDefault}** to **${languageId}**.
72+
73+
- Keep all msgid values unchanged
74+
- Preserve all placeholders like {variable}, %s, {{name}}, etc.
75+
- Maintain the exact JSON format
76+
77+
PO (gettext) translations:
78+
${JSON.stringify(missingTranslations, null, 2)}
79+
`,
80+
})
81+
82+
// Update added translations
83+
for (const translatedMessage of result.object.translations) {
84+
const missingMessage = translations[translatedMessage.msgid]
85+
if (missingMessage) {
86+
missingMessage.msgstr = translatedMessage.msgstr
87+
}
88+
}
89+
90+
// Delete removed translations
91+
pofile.obsolete = {}
92+
93+
const target = po.compile(pofile, { foldLength: Number.POSITIVE_INFINITY })
94+
await writeFile(path, target)
95+
}

0 commit comments

Comments
 (0)