@mxml3gend/gloss 0.1.0

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.
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="273" height="273"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="273" height="273" viewBox="0 0 273 273"><image width="273" height="273" xlink:href="data:image/png;base64,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"></image></svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
2
+ @media (prefers-color-scheme: dark) { :root { filter: none; } }
3
+ </style></svg>
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>ui</title>
8
+ <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
9
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
10
+ <link rel="shortcut icon" href="/favicon.ico" />
11
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
12
+ <meta name="apple-mobile-web-app-title" content="Gloss" />
13
+ <link rel="manifest" href="/site.webmanifest" />
14
+ <script type="module" crossorigin src="/assets/index-Dhb2pVPI.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-CREq9Gop.css">
16
+ </head>
17
+ <body>
18
+ <div id="root"></div>
19
+ </body>
20
+ </html>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "Gloss",
3
+ "short_name": "Gloss",
4
+ "icons": [
5
+ {
6
+ "src": "/web-app-manifest-192x192.png",
7
+ "sizes": "192x192",
8
+ "type": "image/png",
9
+ "purpose": "maskable"
10
+ },
11
+ {
12
+ "src": "/web-app-manifest-512x512.png",
13
+ "sizes": "512x512",
14
+ "type": "image/png",
15
+ "purpose": "maskable"
16
+ }
17
+ ],
18
+ "theme_color": "#ffffff",
19
+ "background_color": "#ffffff",
20
+ "display": "standalone"
21
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
package/dist/usage.js ADDED
@@ -0,0 +1,241 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createScanMatcher } from "./scanFilters.js";
4
+ import { isLikelyTranslationKey } from "./translationKeys.js";
5
+ const SUPPORTED_EXTENSIONS = [
6
+ ".ts",
7
+ ".tsx",
8
+ ".js",
9
+ ".jsx",
10
+ ".mjs",
11
+ ".cjs",
12
+ ".vue",
13
+ ".svelte",
14
+ ];
15
+ const SKIP_DIRECTORIES = new Set([
16
+ "node_modules",
17
+ ".git",
18
+ "dist",
19
+ "build",
20
+ ".next",
21
+ ".nuxt",
22
+ "coverage",
23
+ ]);
24
+ const projectRoot = () => process.env.INIT_CWD || process.cwd();
25
+ const translationsDir = (cfg) => {
26
+ if (path.isAbsolute(cfg.path)) {
27
+ return cfg.path;
28
+ }
29
+ return path.join(projectRoot(), cfg.path);
30
+ };
31
+ const normalizePath = (filePath) => filePath.split(path.sep).join("/");
32
+ const hasSkippedPathSegment = (relativePath) => normalizePath(relativePath)
33
+ .split("/")
34
+ .some((segment) => SKIP_DIRECTORIES.has(segment));
35
+ const isSupportedFile = (name) => SUPPORTED_EXTENSIONS.some((extension) => name.endsWith(extension));
36
+ const extractTranslationKeys = (content) => {
37
+ const keys = new Set();
38
+ const regexes = [
39
+ /\b(?:t|i18n\.t|translate)\(\s*["'`]([^"'`]+)["'`]\s*[\),]/g,
40
+ /\bi18nKey\s*=\s*["'`]([^"'`]+)["'`]/g,
41
+ ];
42
+ for (const regex of regexes) {
43
+ let match = regex.exec(content);
44
+ while (match) {
45
+ const key = match[1]?.trim();
46
+ if (key && isLikelyTranslationKey(key)) {
47
+ keys.add(key);
48
+ }
49
+ match = regex.exec(content);
50
+ }
51
+ }
52
+ return keys;
53
+ };
54
+ const extractRelativeImports = (content) => {
55
+ const imports = new Set();
56
+ const importRegexes = [
57
+ /import\s+[\s\S]*?\s+from\s+["']([^"']+)["']/g,
58
+ /import\(\s*["']([^"']+)["']\s*\)/g,
59
+ ];
60
+ for (const regex of importRegexes) {
61
+ let match = regex.exec(content);
62
+ while (match) {
63
+ const specifier = match[1];
64
+ if (specifier.startsWith(".")) {
65
+ imports.add(specifier);
66
+ }
67
+ match = regex.exec(content);
68
+ }
69
+ }
70
+ return Array.from(imports);
71
+ };
72
+ const resolveImport = async (fromFile, specifier) => {
73
+ const basePath = path.resolve(path.dirname(fromFile), specifier);
74
+ const candidates = [];
75
+ if (path.extname(basePath)) {
76
+ candidates.push(basePath);
77
+ }
78
+ else {
79
+ for (const extension of SUPPORTED_EXTENSIONS) {
80
+ candidates.push(`${basePath}${extension}`);
81
+ }
82
+ for (const extension of SUPPORTED_EXTENSIONS) {
83
+ candidates.push(path.join(basePath, `index${extension}`));
84
+ }
85
+ }
86
+ for (const candidate of candidates) {
87
+ try {
88
+ const stat = await awaitStat(candidate);
89
+ if (stat?.isFile()) {
90
+ return candidate;
91
+ }
92
+ }
93
+ catch {
94
+ continue;
95
+ }
96
+ }
97
+ return null;
98
+ };
99
+ const awaitStat = async (filePath) => {
100
+ try {
101
+ return await fs.stat(filePath);
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ };
107
+ const isPageFile = (relativePath) => {
108
+ const normalized = normalizePath(relativePath);
109
+ const fileName = path.basename(normalized);
110
+ const hasPagesSegment = normalized.includes("/pages/");
111
+ const hasRoutesSegment = normalized.includes("/routes/");
112
+ const isAppEntry = /^App\.(tsx?|jsx?)$/.test(fileName);
113
+ const isNextAppPage = normalized.includes("/app/") &&
114
+ /(\/|^)(page|layout|route)\.(tsx?|jsx?|js|ts|vue|svelte)$/.test(normalized);
115
+ const isSvelteKitPage = normalized.includes("/routes/") &&
116
+ /(\/|^)\+page(\.server)?\.(ts|js|svelte)$/.test(normalized);
117
+ return (hasPagesSegment ||
118
+ hasRoutesSegment ||
119
+ isAppEntry ||
120
+ isNextAppPage ||
121
+ isSvelteKitPage);
122
+ };
123
+ const collectFiles = async (directory, projectDir, shouldScanFile, files) => {
124
+ const entries = await fs.readdir(directory, { withFileTypes: true });
125
+ for (const entry of entries) {
126
+ if (entry.name.startsWith(".")) {
127
+ continue;
128
+ }
129
+ const fullPath = path.join(directory, entry.name);
130
+ if (entry.isDirectory()) {
131
+ if (SKIP_DIRECTORIES.has(entry.name)) {
132
+ continue;
133
+ }
134
+ await collectFiles(fullPath, projectDir, shouldScanFile, files);
135
+ continue;
136
+ }
137
+ if (!entry.isFile() || !isSupportedFile(entry.name)) {
138
+ continue;
139
+ }
140
+ const relativePath = normalizePath(path.relative(projectDir, fullPath));
141
+ if (hasSkippedPathSegment(relativePath)) {
142
+ continue;
143
+ }
144
+ if (!shouldScanFile(relativePath)) {
145
+ continue;
146
+ }
147
+ const content = await fs.readFile(fullPath, "utf8");
148
+ files.push({
149
+ filePath: fullPath,
150
+ relativePath,
151
+ keys: extractTranslationKeys(content),
152
+ imports: extractRelativeImports(content),
153
+ });
154
+ }
155
+ };
156
+ export async function buildKeyUsageMap(cfg) {
157
+ const root = projectRoot();
158
+ const i18nDirectory = translationsDir(cfg);
159
+ const candidateRoots = [
160
+ path.dirname(i18nDirectory),
161
+ path.join(root, "src"),
162
+ path.join(root, "app"),
163
+ path.join(root, "pages"),
164
+ path.join(root, "routes"),
165
+ ];
166
+ const sourceRoots = Array.from(new Set(candidateRoots.filter((candidate) => path.resolve(candidate) !== root)));
167
+ const files = [];
168
+ const shouldScanFile = createScanMatcher(cfg.scan);
169
+ for (const sourceRoot of sourceRoots) {
170
+ const stat = await awaitStat(sourceRoot);
171
+ if (!stat?.isDirectory()) {
172
+ continue;
173
+ }
174
+ await collectFiles(sourceRoot, root, shouldScanFile, files);
175
+ }
176
+ const fileByPath = new Map(files.map((file) => [path.resolve(file.filePath), file]));
177
+ const adjacency = new Map();
178
+ for (const file of files) {
179
+ const imports = [];
180
+ for (const specifier of file.imports) {
181
+ const resolvedImport = await resolveImport(file.filePath, specifier);
182
+ if (resolvedImport) {
183
+ const normalizedImport = path.resolve(resolvedImport);
184
+ if (fileByPath.has(normalizedImport)) {
185
+ imports.push(normalizedImport);
186
+ }
187
+ }
188
+ }
189
+ adjacency.set(path.resolve(file.filePath), imports);
190
+ }
191
+ const pages = files.filter((file) => isPageFile(file.relativePath));
192
+ const resultFiles = files
193
+ .filter((file) => file.keys.size > 0)
194
+ .map((file) => {
195
+ const normalizedRelativePath = normalizePath(file.relativePath);
196
+ return {
197
+ id: normalizedRelativePath,
198
+ file: normalizedRelativePath,
199
+ keys: Array.from(file.keys).sort(),
200
+ };
201
+ })
202
+ .sort((left, right) => left.file.localeCompare(right.file));
203
+ const resultPages = pages
204
+ .map((pageFile) => {
205
+ const visited = new Set();
206
+ const queue = [path.resolve(pageFile.filePath)];
207
+ const keys = new Set();
208
+ while (queue.length > 0) {
209
+ const current = queue.pop();
210
+ if (!current || visited.has(current)) {
211
+ continue;
212
+ }
213
+ visited.add(current);
214
+ const currentFile = fileByPath.get(current);
215
+ if (!currentFile) {
216
+ continue;
217
+ }
218
+ for (const key of currentFile.keys) {
219
+ keys.add(key);
220
+ }
221
+ for (const next of adjacency.get(current) ?? []) {
222
+ if (!visited.has(next)) {
223
+ queue.push(next);
224
+ }
225
+ }
226
+ }
227
+ const normalizedRelativePath = normalizePath(pageFile.relativePath);
228
+ const id = normalizedRelativePath.replace(/\.[^.]+$/, "");
229
+ return {
230
+ id,
231
+ file: normalizedRelativePath,
232
+ keys: Array.from(keys).sort(),
233
+ };
234
+ })
235
+ .sort((left, right) => left.file.localeCompare(right.file));
236
+ return {
237
+ pages: resultPages,
238
+ files: resultFiles,
239
+ generatedAt: new Date().toISOString(),
240
+ };
241
+ }
@@ -0,0 +1,102 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createScanMatcher } from "./scanFilters.js";
4
+ import { isLikelyTranslationKey } from "./translationKeys.js";
5
+ const IGNORED_DIRECTORIES = new Set([
6
+ "node_modules",
7
+ "dist",
8
+ "build",
9
+ "out",
10
+ ".git",
11
+ ".next",
12
+ ".nuxt",
13
+ ".turbo",
14
+ "coverage",
15
+ "storybook-static",
16
+ ]);
17
+ const SCANNED_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"]);
18
+ const USAGE_REGEXES = [
19
+ /\b(?:t|i18n\.t|translate)\(\s*["'`]([^"'`]+)["'`]\s*[\),]/g,
20
+ /\bi18nKey\s*=\s*["'`]([^"'`]+)["'`]/g,
21
+ ];
22
+ const projectRoot = () => process.env.INIT_CWD || process.cwd();
23
+ const normalizePath = (filePath) => filePath.split(path.sep).join("/");
24
+ const isScannableFile = (fileName) => SCANNED_EXTENSIONS.has(path.extname(fileName));
25
+ const hasIgnoredPathSegment = (relativePath) => normalizePath(relativePath)
26
+ .split("/")
27
+ .some((segment) => IGNORED_DIRECTORIES.has(segment));
28
+ const resolveTranslationsDir = (cfg, cwd) => {
29
+ if (path.isAbsolute(cfg.path)) {
30
+ return cfg.path;
31
+ }
32
+ return path.resolve(cwd, cfg.path);
33
+ };
34
+ export const inferUsageRoot = (cfg) => {
35
+ const cwd = projectRoot();
36
+ const translationsDirectory = resolveTranslationsDir(cfg, cwd);
37
+ const relativeToCwd = path.relative(cwd, translationsDirectory);
38
+ const isInsideProject = relativeToCwd.length === 0 ||
39
+ (!relativeToCwd.startsWith("..") && !path.isAbsolute(relativeToCwd));
40
+ if (!isInsideProject) {
41
+ return cwd;
42
+ }
43
+ const parentDirectory = path.dirname(translationsDirectory);
44
+ if (path.basename(parentDirectory) === "src") {
45
+ return path.dirname(parentDirectory);
46
+ }
47
+ return parentDirectory;
48
+ };
49
+ export async function scanUsage(rootDir = projectRoot(), scan) {
50
+ const usage = {};
51
+ const seenFilesByKey = new Map();
52
+ const shouldScanFile = createScanMatcher(scan);
53
+ const scanDirectory = async (directory) => {
54
+ const entries = await fs.readdir(directory, { withFileTypes: true });
55
+ for (const entry of entries) {
56
+ const fullPath = path.join(directory, entry.name);
57
+ if (entry.isDirectory()) {
58
+ if (IGNORED_DIRECTORIES.has(entry.name)) {
59
+ continue;
60
+ }
61
+ await scanDirectory(fullPath);
62
+ continue;
63
+ }
64
+ if (!entry.isFile() || !isScannableFile(entry.name)) {
65
+ continue;
66
+ }
67
+ const relativePath = normalizePath(path.relative(rootDir, fullPath));
68
+ if (hasIgnoredPathSegment(relativePath)) {
69
+ continue;
70
+ }
71
+ if (!shouldScanFile(relativePath)) {
72
+ continue;
73
+ }
74
+ const source = await fs.readFile(fullPath, "utf8");
75
+ for (const usageRegex of USAGE_REGEXES) {
76
+ let match = usageRegex.exec(source);
77
+ while (match) {
78
+ const key = match[1]?.trim();
79
+ if (key && isLikelyTranslationKey(key)) {
80
+ if (!usage[key]) {
81
+ usage[key] = { count: 0, files: [] };
82
+ seenFilesByKey.set(key, new Set());
83
+ }
84
+ usage[key].count += 1;
85
+ const fileSet = seenFilesByKey.get(key);
86
+ if (fileSet && !fileSet.has(relativePath)) {
87
+ fileSet.add(relativePath);
88
+ usage[key].files.push(relativePath);
89
+ }
90
+ }
91
+ match = usageRegex.exec(source);
92
+ }
93
+ usageRegex.lastIndex = 0;
94
+ }
95
+ }
96
+ };
97
+ await scanDirectory(rootDir);
98
+ for (const value of Object.values(usage)) {
99
+ value.files.sort((left, right) => left.localeCompare(right));
100
+ }
101
+ return usage;
102
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@mxml3gend/gloss",
3
+ "version": "0.1.0",
4
+ "description": "Local-first CLI + web app for managing i18n translation files",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "exports": {
9
+ ".": "./dist/index.js"
10
+ },
11
+ "bin": {
12
+ "gloss": "dist/index.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "scripts": {
26
+ "dev": "tsx src/index.ts",
27
+ "dev:server": "tsx src/devServer.ts",
28
+ "build": "tsc -p tsconfig.json && node ./scripts/copy-ui-dist.mjs",
29
+ "test": "npm run build && node --test tests/**/*.test.mjs"
30
+ },
31
+ "devDependencies": {
32
+ "@types/cors": "^2.8.19",
33
+ "@types/express": "^5.0.6",
34
+ "@types/node": "^25.2.3",
35
+ "@types/open": "^6.1.0",
36
+ "tsx": "^4.21.0"
37
+ },
38
+ "dependencies": {
39
+ "cors": "^2.8.6",
40
+ "express": "^5.2.1",
41
+ "open": "^11.0.0"
42
+ }
43
+ }