@pie-players/pie-players-shared 0.2.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.
- package/dist/config/profile.d.ts +15 -0
- package/dist/config/profile.d.ts.map +1 -0
- package/dist/config/profile.js +27 -0
- package/dist/config/profile.js.map +1 -0
- package/dist/i18n/index.d.ts +13 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +12 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/loader.d.ts +36 -0
- package/dist/i18n/loader.d.ts.map +1 -0
- package/dist/i18n/loader.js +133 -0
- package/dist/i18n/loader.js.map +1 -0
- package/dist/i18n/scripts/check-coverage.d.ts +16 -0
- package/dist/i18n/scripts/check-coverage.d.ts.map +1 -0
- package/dist/i18n/scripts/check-coverage.js +262 -0
- package/dist/i18n/scripts/check-coverage.js.map +1 -0
- package/dist/i18n/scripts/scan-hardcoded.d.ts +16 -0
- package/dist/i18n/scripts/scan-hardcoded.d.ts.map +1 -0
- package/dist/i18n/scripts/scan-hardcoded.js +266 -0
- package/dist/i18n/scripts/scan-hardcoded.js.map +1 -0
- package/dist/i18n/simple-i18n.d.ts +69 -0
- package/dist/i18n/simple-i18n.d.ts.map +1 -0
- package/dist/i18n/simple-i18n.js +199 -0
- package/dist/i18n/simple-i18n.js.map +1 -0
- package/dist/i18n/translations/ar/common.json +36 -0
- package/dist/i18n/translations/ar/toolkit.json +48 -0
- package/dist/i18n/translations/ar/tools.json +109 -0
- package/dist/i18n/translations/en/common.json +36 -0
- package/dist/i18n/translations/en/toolkit.json +48 -0
- package/dist/i18n/translations/en/tools.json +109 -0
- package/dist/i18n/translations/es/common.json +36 -0
- package/dist/i18n/translations/es/toolkit.json +48 -0
- package/dist/i18n/translations/es/tools.json +109 -0
- package/dist/i18n/translations/zh/common.json +36 -0
- package/dist/i18n/translations/zh/toolkit.json +48 -0
- package/dist/i18n/translations/zh/tools.json +109 -0
- package/dist/i18n/types.d.ts +58 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +8 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/i18n/use-i18n-standalone.svelte.d.ts +87 -0
- package/dist/i18n/use-i18n-standalone.svelte.d.ts.map +1 -0
- package/dist/i18n/use-i18n-standalone.svelte.js +151 -0
- package/dist/i18n/use-i18n-standalone.svelte.js.map +1 -0
- package/dist/i18n/use-i18n.svelte.d.ts +67 -0
- package/dist/i18n/use-i18n.svelte.d.ts.map +1 -0
- package/dist/i18n/use-i18n.svelte.js +144 -0
- package/dist/i18n/use-i18n.svelte.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +53 -0
- package/dist/instrumentation/index.d.ts.map +1 -0
- package/dist/instrumentation/index.js +53 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +197 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js +267 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.d.ts +106 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js +182 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.d.ts +170 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.js +183 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +86 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +135 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/index.d.ts +12 -0
- package/dist/instrumentation/providers/index.d.ts.map +1 -0
- package/dist/instrumentation/providers/index.js +12 -0
- package/dist/instrumentation/providers/index.js.map +1 -0
- package/dist/instrumentation/types.d.ts +348 -0
- package/dist/instrumentation/types.d.ts.map +1 -0
- package/dist/instrumentation/types.js +9 -0
- package/dist/instrumentation/types.js.map +1 -0
- package/dist/loader-config.d.ts +76 -0
- package/dist/loader-config.d.ts.map +1 -0
- package/dist/loader-config.js +12 -0
- package/dist/loader-config.js.map +1 -0
- package/dist/loaders/ElementLoader.d.ts +72 -0
- package/dist/loaders/ElementLoader.d.ts.map +1 -0
- package/dist/loaders/ElementLoader.js +52 -0
- package/dist/loaders/ElementLoader.js.map +1 -0
- package/dist/loaders/EsmElementLoader.d.ts +67 -0
- package/dist/loaders/EsmElementLoader.d.ts.map +1 -0
- package/dist/loaders/EsmElementLoader.js +71 -0
- package/dist/loaders/EsmElementLoader.js.map +1 -0
- package/dist/loaders/IifeElementLoader.d.ts +61 -0
- package/dist/loaders/IifeElementLoader.d.ts.map +1 -0
- package/dist/loaders/IifeElementLoader.js +63 -0
- package/dist/loaders/IifeElementLoader.js.map +1 -0
- package/dist/loaders/index.d.ts +28 -0
- package/dist/loaders/index.d.ts.map +1 -0
- package/dist/loaders/index.js +25 -0
- package/dist/loaders/index.js.map +1 -0
- package/dist/object/index.d.ts +12 -0
- package/dist/object/index.d.ts.map +1 -0
- package/dist/object/index.js +40 -0
- package/dist/object/index.js.map +1 -0
- package/dist/pie/asset-handler.d.ts +64 -0
- package/dist/pie/asset-handler.d.ts.map +1 -0
- package/dist/pie/asset-handler.js +238 -0
- package/dist/pie/asset-handler.js.map +1 -0
- package/dist/pie/component-context.d.ts +22 -0
- package/dist/pie/component-context.d.ts.map +1 -0
- package/dist/pie/component-context.js +30 -0
- package/dist/pie/component-context.js.map +1 -0
- package/dist/pie/config.d.ts +39 -0
- package/dist/pie/config.d.ts.map +1 -0
- package/dist/pie/config.js +174 -0
- package/dist/pie/config.js.map +1 -0
- package/dist/pie/configure-initialization.d.ts +35 -0
- package/dist/pie/configure-initialization.d.ts.map +1 -0
- package/dist/pie/configure-initialization.js +141 -0
- package/dist/pie/configure-initialization.js.map +1 -0
- package/dist/pie/esm-loader.d.ts +93 -0
- package/dist/pie/esm-loader.d.ts.map +1 -0
- package/dist/pie/esm-loader.js +308 -0
- package/dist/pie/esm-loader.js.map +1 -0
- package/dist/pie/iife-loader.d.ts +76 -0
- package/dist/pie/iife-loader.d.ts.map +1 -0
- package/dist/pie/iife-loader.js +303 -0
- package/dist/pie/iife-loader.js.map +1 -0
- package/dist/pie/index.d.ts +31 -0
- package/dist/pie/index.d.ts.map +1 -0
- package/dist/pie/index.js +34 -0
- package/dist/pie/index.js.map +1 -0
- package/dist/pie/initialization.d.ts +40 -0
- package/dist/pie/initialization.d.ts.map +1 -0
- package/dist/pie/initialization.js +349 -0
- package/dist/pie/initialization.js.map +1 -0
- package/dist/pie/logger.d.ts +64 -0
- package/dist/pie/logger.d.ts.map +1 -0
- package/dist/pie/logger.js +45 -0
- package/dist/pie/logger.js.map +1 -0
- package/dist/pie/math-rendering.d.ts +69 -0
- package/dist/pie/math-rendering.d.ts.map +1 -0
- package/dist/pie/math-rendering.js +98 -0
- package/dist/pie/math-rendering.js.map +1 -0
- package/dist/pie/overrides.d.ts +43 -0
- package/dist/pie/overrides.d.ts.map +1 -0
- package/dist/pie/overrides.js +146 -0
- package/dist/pie/overrides.js.map +1 -0
- package/dist/pie/player-initializer.d.ts +55 -0
- package/dist/pie/player-initializer.d.ts.map +1 -0
- package/dist/pie/player-initializer.js +123 -0
- package/dist/pie/player-initializer.js.map +1 -0
- package/dist/pie/registry.d.ts +11 -0
- package/dist/pie/registry.d.ts.map +1 -0
- package/dist/pie/registry.js +21 -0
- package/dist/pie/registry.js.map +1 -0
- package/dist/pie/resource-monitor.d.ts +208 -0
- package/dist/pie/resource-monitor.d.ts.map +1 -0
- package/dist/pie/resource-monitor.js +969 -0
- package/dist/pie/resource-monitor.js.map +1 -0
- package/dist/pie/scoring.d.ts +17 -0
- package/dist/pie/scoring.d.ts.map +1 -0
- package/dist/pie/scoring.js +84 -0
- package/dist/pie/scoring.js.map +1 -0
- package/dist/pie/types.d.ts +136 -0
- package/dist/pie/types.d.ts.map +1 -0
- package/dist/pie/types.js +52 -0
- package/dist/pie/types.js.map +1 -0
- package/dist/pie/updates.d.ts +20 -0
- package/dist/pie/updates.d.ts.map +1 -0
- package/dist/pie/updates.js +175 -0
- package/dist/pie/updates.js.map +1 -0
- package/dist/pie/use-resource-monitor.svelte.d.ts +56 -0
- package/dist/pie/use-resource-monitor.svelte.d.ts.map +1 -0
- package/dist/pie/use-resource-monitor.svelte.js +117 -0
- package/dist/pie/use-resource-monitor.svelte.js.map +1 -0
- package/dist/pie/utils.d.ts +44 -0
- package/dist/pie/utils.d.ts.map +1 -0
- package/dist/pie/utils.js +74 -0
- package/dist/pie/utils.js.map +1 -0
- package/dist/types/custom-elements.d.ts +183 -0
- package/dist/types/custom-elements.d.ts.map +1 -0
- package/dist/types/custom-elements.js +8 -0
- package/dist/types/custom-elements.js.map +1 -0
- package/dist/types/index.d.ts +761 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +120 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/search.d.ts +105 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/search.js +12 -0
- package/dist/types/search.js.map +1 -0
- package/dist/types/transform.d.ts +48 -0
- package/dist/types/transform.d.ts.map +1 -0
- package/dist/types/transform.js +21 -0
- package/dist/types/transform.js.map +1 -0
- package/dist/ui/focus-trap.d.ts +10 -0
- package/dist/ui/focus-trap.d.ts.map +1 -0
- package/dist/ui/focus-trap.js +30 -0
- package/dist/ui/focus-trap.js.map +1 -0
- package/dist/ui/safe-storage.d.ts +3 -0
- package/dist/ui/safe-storage.d.ts.map +1 -0
- package/dist/ui/safe-storage.js +21 -0
- package/dist/ui/safe-storage.js.map +1 -0
- package/package.json +118 -0
- package/src/components/PieItemPlayer.svelte +604 -0
- package/src/components/PiePreviewLayout.svelte +144 -0
- package/src/components/PiePreviewToggle.svelte +110 -0
- package/src/components/PieSpinner.svelte +85 -0
- package/src/components/ToolSettingsButton.svelte +31 -0
- package/src/components/ToolSettingsPanel.svelte +90 -0
- package/src/components/index.ts +6 -0
- package/src/i18n/README.md +223 -0
- package/src/i18n/index.ts +26 -0
- package/src/i18n/loader.ts +156 -0
- package/src/i18n/scripts/check-coverage.ts +345 -0
- package/src/i18n/scripts/scan-hardcoded.ts +342 -0
- package/src/i18n/simple-i18n.ts +236 -0
- package/src/i18n/translations/ar/common.json +36 -0
- package/src/i18n/translations/ar/toolkit.json +48 -0
- package/src/i18n/translations/ar/tools.json +109 -0
- package/src/i18n/translations/en/common.json +36 -0
- package/src/i18n/translations/en/toolkit.json +48 -0
- package/src/i18n/translations/en/tools.json +109 -0
- package/src/i18n/translations/es/common.json +36 -0
- package/src/i18n/translations/es/toolkit.json +48 -0
- package/src/i18n/translations/es/tools.json +109 -0
- package/src/i18n/translations/zh/common.json +36 -0
- package/src/i18n/translations/zh/toolkit.json +48 -0
- package/src/i18n/translations/zh/tools.json +109 -0
- package/src/i18n/types.ts +66 -0
- package/src/i18n/use-i18n-standalone.svelte.ts +184 -0
- package/src/i18n/use-i18n.svelte.ts +163 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Hardcoded String Scanner
|
|
4
|
+
*
|
|
5
|
+
* Scans component files for hardcoded English strings that should use i18n.
|
|
6
|
+
* Adapted from pie-qti's hardcoded string scanner.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* bun run packages/players-shared/src/i18n/scripts/scan-hardcoded.ts
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --path <dir> - Directory to scan (default: packages/)
|
|
13
|
+
* --fix - Attempt to auto-fix issues (not implemented yet)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from "fs";
|
|
17
|
+
import { glob } from "glob";
|
|
18
|
+
import { dirname, relative, resolve } from "path";
|
|
19
|
+
import { fileURLToPath } from "url";
|
|
20
|
+
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
|
|
24
|
+
interface StringMatch {
|
|
25
|
+
file: string;
|
|
26
|
+
line: number;
|
|
27
|
+
context: string;
|
|
28
|
+
string: string;
|
|
29
|
+
suggestedKey: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load all English translations
|
|
34
|
+
*/
|
|
35
|
+
function loadEnglishTranslations(): Record<string, string> {
|
|
36
|
+
const basePath = resolve(__dirname, "../translations/en");
|
|
37
|
+
const translations: Record<string, string> = {};
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const common = JSON.parse(
|
|
41
|
+
readFileSync(resolve(basePath, "common.json"), "utf-8"),
|
|
42
|
+
);
|
|
43
|
+
const toolkit = JSON.parse(
|
|
44
|
+
readFileSync(resolve(basePath, "toolkit.json"), "utf-8"),
|
|
45
|
+
);
|
|
46
|
+
const tools = JSON.parse(
|
|
47
|
+
readFileSync(resolve(basePath, "tools.json"), "utf-8"),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Flatten all translations
|
|
51
|
+
flattenObject(common, "", translations);
|
|
52
|
+
flattenObject(toolkit, "", translations);
|
|
53
|
+
flattenObject(tools, "", translations);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error("Error loading English translations:", error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return translations;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Flatten nested object to dot notation
|
|
64
|
+
*/
|
|
65
|
+
function flattenObject(
|
|
66
|
+
obj: any,
|
|
67
|
+
prefix: string,
|
|
68
|
+
result: Record<string, string>,
|
|
69
|
+
) {
|
|
70
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
71
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
72
|
+
|
|
73
|
+
if (typeof value === "object" && value !== null) {
|
|
74
|
+
// Check for plural form
|
|
75
|
+
if ("one" in value && "other" in value) {
|
|
76
|
+
result[fullKey] = value.one as string;
|
|
77
|
+
} else {
|
|
78
|
+
flattenObject(value, fullKey, result);
|
|
79
|
+
}
|
|
80
|
+
} else if (typeof value === "string") {
|
|
81
|
+
result[fullKey] = value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Find translation key for a given string value
|
|
88
|
+
*/
|
|
89
|
+
function findTranslationKey(
|
|
90
|
+
translations: Record<string, string>,
|
|
91
|
+
searchValue: string,
|
|
92
|
+
): string | null {
|
|
93
|
+
// Exact match first
|
|
94
|
+
for (const [key, value] of Object.entries(translations)) {
|
|
95
|
+
if (value === searchValue) {
|
|
96
|
+
return key;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Fuzzy match (case-insensitive)
|
|
101
|
+
const lowerSearch = searchValue.toLowerCase();
|
|
102
|
+
for (const [key, value] of Object.entries(translations)) {
|
|
103
|
+
if (value.toLowerCase() === lowerSearch) {
|
|
104
|
+
return key;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Scan a file for hardcoded strings
|
|
113
|
+
*/
|
|
114
|
+
function scanFile(
|
|
115
|
+
filePath: string,
|
|
116
|
+
translations: Record<string, string>,
|
|
117
|
+
): StringMatch[] {
|
|
118
|
+
const content = readFileSync(filePath, "utf-8");
|
|
119
|
+
const lines = content.split("\n");
|
|
120
|
+
const matches: StringMatch[] = [];
|
|
121
|
+
|
|
122
|
+
// Patterns to match quoted English strings
|
|
123
|
+
// Matches: "Text", 'Text', but ignores: i18n.t(...), class="...", etc.
|
|
124
|
+
const stringPattern = /["']([A-Z][A-Za-z\s,.:;!?'\-()]+)["']/g;
|
|
125
|
+
|
|
126
|
+
// Patterns to exclude (already using i18n, CSS classes, imports, etc.)
|
|
127
|
+
const excludePatterns = [
|
|
128
|
+
/i18n\./,
|
|
129
|
+
/import\s+/,
|
|
130
|
+
/from\s+['"]/,
|
|
131
|
+
/class[:=]/,
|
|
132
|
+
/className[:=]/,
|
|
133
|
+
/aria-\w+[:=]/,
|
|
134
|
+
/data-\w+[:=]/,
|
|
135
|
+
/id[:=]/,
|
|
136
|
+
/key[:=]/,
|
|
137
|
+
/name[:=]/,
|
|
138
|
+
/type[:=]/,
|
|
139
|
+
/href[:=]/,
|
|
140
|
+
/src[:=]/,
|
|
141
|
+
/alt[:=]/,
|
|
142
|
+
/title[:=]/,
|
|
143
|
+
/placeholder[:=]/,
|
|
144
|
+
/console\./,
|
|
145
|
+
/\/\//, // Comments
|
|
146
|
+
/\/\*/, // Block comments
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
lines.forEach((line, index) => {
|
|
150
|
+
// Skip lines that match exclude patterns
|
|
151
|
+
if (excludePatterns.some((pattern) => pattern.test(line))) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Find all string matches in the line
|
|
156
|
+
let match;
|
|
157
|
+
while ((match = stringPattern.exec(line)) !== null) {
|
|
158
|
+
const string = match[1];
|
|
159
|
+
|
|
160
|
+
// Skip very short strings (likely not user-facing)
|
|
161
|
+
if (string.length < 3) continue;
|
|
162
|
+
|
|
163
|
+
// Skip strings that are mostly numbers or special characters
|
|
164
|
+
if (!/[a-zA-Z]{3,}/.test(string)) continue;
|
|
165
|
+
|
|
166
|
+
// Find corresponding translation key
|
|
167
|
+
const suggestedKey = findTranslationKey(translations, string);
|
|
168
|
+
|
|
169
|
+
matches.push({
|
|
170
|
+
file: filePath,
|
|
171
|
+
line: index + 1,
|
|
172
|
+
context: line.trim(),
|
|
173
|
+
string,
|
|
174
|
+
suggestedKey,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return matches;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Format scan results
|
|
184
|
+
*/
|
|
185
|
+
function formatResults(
|
|
186
|
+
matchesByFile: Map<string, StringMatch[]>,
|
|
187
|
+
rootDir: string,
|
|
188
|
+
): string {
|
|
189
|
+
let report = "\n";
|
|
190
|
+
report += "┌─────────────────────────────────────────────────────┐\n";
|
|
191
|
+
report += "│ Hardcoded String Scanner │\n";
|
|
192
|
+
report += "└─────────────────────────────────────────────────────┘\n\n";
|
|
193
|
+
|
|
194
|
+
const fileCount = matchesByFile.size;
|
|
195
|
+
const totalMatches = Array.from(matchesByFile.values()).reduce(
|
|
196
|
+
(sum, m) => sum + m.length,
|
|
197
|
+
0,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (fileCount === 0) {
|
|
201
|
+
report += "✅ No hardcoded strings found!\n\n";
|
|
202
|
+
return report;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Sort files by number of matches (descending)
|
|
206
|
+
const sortedFiles = Array.from(matchesByFile.entries()).sort(
|
|
207
|
+
(a, b) => b[1].length - a[1].length,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
for (const [file, matches] of sortedFiles) {
|
|
211
|
+
const relPath = relative(rootDir, file);
|
|
212
|
+
report += `📄 ${relPath} (${matches.length} match${matches.length === 1 ? "" : "es"})\n`;
|
|
213
|
+
report += "────────────────────────────────────────────────────────\n";
|
|
214
|
+
|
|
215
|
+
for (const match of matches) {
|
|
216
|
+
report += ` Line ${match.line}: ${match.context}\n`;
|
|
217
|
+
report += ` Found: "${match.string}"\n`;
|
|
218
|
+
|
|
219
|
+
if (match.suggestedKey) {
|
|
220
|
+
report += ` Use: i18n?.t('${match.suggestedKey}') ?? '${match.suggestedKey}'\n`;
|
|
221
|
+
} else {
|
|
222
|
+
report += ` Note: No matching translation key found. Consider adding to translations.\n`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
report += "\n";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
report += "\n";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
report += "════════════════════════════════════════════════════════\n";
|
|
232
|
+
report += "📊 Summary:\n";
|
|
233
|
+
report += "────────────────────────────────────────────────────────\n";
|
|
234
|
+
report += ` Total files scanned: ${fileCount}\n`;
|
|
235
|
+
report += ` Files with matches: ${fileCount}\n`;
|
|
236
|
+
report += ` Total hardcoded strings: ${totalMatches}\n\n`;
|
|
237
|
+
report += "⚠️ Recommendation: Replace hardcoded strings with i18n keys\n";
|
|
238
|
+
report += " for proper internationalization support.\n";
|
|
239
|
+
report += "════════════════════════════════════════════════════════\n";
|
|
240
|
+
|
|
241
|
+
return report;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Find project root by looking for package.json with workspaces
|
|
246
|
+
*/
|
|
247
|
+
function findProjectRoot(): string {
|
|
248
|
+
let currentDir = process.cwd();
|
|
249
|
+
while (currentDir !== "/") {
|
|
250
|
+
try {
|
|
251
|
+
const pkgPath = resolve(currentDir, "package.json");
|
|
252
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
253
|
+
if (pkg.workspaces) {
|
|
254
|
+
return currentDir;
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
// Continue searching
|
|
258
|
+
}
|
|
259
|
+
currentDir = dirname(currentDir);
|
|
260
|
+
}
|
|
261
|
+
// Fallback to script location's root
|
|
262
|
+
return resolve(__dirname, "../../../..");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Main entry point
|
|
267
|
+
*/
|
|
268
|
+
async function main() {
|
|
269
|
+
const args = process.argv.slice(2);
|
|
270
|
+
const pathArg = args.indexOf("--path");
|
|
271
|
+
|
|
272
|
+
// Find project root and default to scanning packages/
|
|
273
|
+
const projectRoot = findProjectRoot();
|
|
274
|
+
const defaultPath = resolve(projectRoot, "packages/");
|
|
275
|
+
const scanPath =
|
|
276
|
+
pathArg !== -1 ? resolve(projectRoot, args[pathArg + 1]) : defaultPath;
|
|
277
|
+
|
|
278
|
+
console.log("🔍 Scanning for hardcoded strings...\n");
|
|
279
|
+
console.log(`📂 Scan path: ${scanPath}\n`);
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
// Load English translations for matching
|
|
283
|
+
const translations = loadEnglishTranslations();
|
|
284
|
+
console.log(
|
|
285
|
+
`📖 Loaded ${Object.keys(translations).length} translation keys\n`,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Find all component files
|
|
289
|
+
const allFiles = await glob(`${scanPath}/**/*.{svelte,ts,tsx}`, {
|
|
290
|
+
absolute: true,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Filter out unwanted directories manually
|
|
294
|
+
const files = allFiles.filter((file) => {
|
|
295
|
+
return (
|
|
296
|
+
!file.includes("/node_modules/") &&
|
|
297
|
+
!file.includes("/dist/") &&
|
|
298
|
+
!file.includes("/.svelte-kit/") &&
|
|
299
|
+
!file.includes("/build/") &&
|
|
300
|
+
!file.endsWith(".spec.ts") &&
|
|
301
|
+
!file.endsWith(".test.ts") &&
|
|
302
|
+
!file.includes("/scripts/")
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
console.log(`📁 Found ${files.length} files to scan\n`);
|
|
307
|
+
|
|
308
|
+
// Scan all files
|
|
309
|
+
const matchesByFile = new Map<string, StringMatch[]>();
|
|
310
|
+
|
|
311
|
+
for (const file of files) {
|
|
312
|
+
const matches = scanFile(file, translations);
|
|
313
|
+
if (matches.length > 0) {
|
|
314
|
+
matchesByFile.set(file, matches);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Print results
|
|
319
|
+
const report = formatResults(matchesByFile, projectRoot);
|
|
320
|
+
console.log(report);
|
|
321
|
+
|
|
322
|
+
// Exit with warning if matches found
|
|
323
|
+
if (matchesByFile.size > 0) {
|
|
324
|
+
console.warn(
|
|
325
|
+
"\n⚠️ Found hardcoded strings. Consider using i18n for these strings.\n",
|
|
326
|
+
);
|
|
327
|
+
// Don't exit with error - this is informational only
|
|
328
|
+
process.exit(0);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log("✅ No hardcoded strings found!\n");
|
|
332
|
+
process.exit(0);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error("\n❌ Fatal error during scan:", error);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Run if executed directly
|
|
340
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
341
|
+
main();
|
|
342
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple I18n Implementation
|
|
3
|
+
*
|
|
4
|
+
* Lightweight i18n without external dependencies.
|
|
5
|
+
* Used for standalone components that don't need the full service architecture.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { I18nConfig, PluralTranslation, TranslationBundle } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Simple I18n class for standalone use
|
|
12
|
+
*/
|
|
13
|
+
export class SimpleI18n {
|
|
14
|
+
private locale: string = "en";
|
|
15
|
+
private fallbackLocale: string = "en";
|
|
16
|
+
private direction: "ltr" | "rtl" = "ltr";
|
|
17
|
+
private translations = new Map<string, TranslationBundle>();
|
|
18
|
+
private listeners = new Set<() => void>();
|
|
19
|
+
private loadingPromises = new Map<string, Promise<void>>();
|
|
20
|
+
private config: I18nConfig;
|
|
21
|
+
|
|
22
|
+
constructor(config: I18nConfig = {}) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.fallbackLocale = config.fallbackLocale || "en";
|
|
25
|
+
|
|
26
|
+
// Load bundled translations
|
|
27
|
+
if (config.bundledTranslations) {
|
|
28
|
+
for (const [locale, bundle] of Object.entries(
|
|
29
|
+
config.bundledTranslations,
|
|
30
|
+
)) {
|
|
31
|
+
this.translations.set(locale, bundle);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Initialize with locale
|
|
38
|
+
*/
|
|
39
|
+
async initialize(config: I18nConfig): Promise<void> {
|
|
40
|
+
Object.assign(this.config, config);
|
|
41
|
+
|
|
42
|
+
const locale = config.locale || this.detectBrowserLocale();
|
|
43
|
+
await this.setLocale(locale);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Translate a key
|
|
48
|
+
*/
|
|
49
|
+
t(key: string, params?: Record<string, any>): string {
|
|
50
|
+
const translation = this.getTranslation(key);
|
|
51
|
+
return this.interpolate(translation as string, params);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Translate with pluralization
|
|
56
|
+
*/
|
|
57
|
+
tn(key: string, count: number, params?: Record<string, any>): string {
|
|
58
|
+
const translation = this.getTranslation(key);
|
|
59
|
+
|
|
60
|
+
if (typeof translation === "object") {
|
|
61
|
+
const pluralForm = this.selectPluralForm(count, this.locale);
|
|
62
|
+
const text = translation[pluralForm] || translation.other;
|
|
63
|
+
return this.interpolate(text, { ...params, count });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return this.interpolate(translation as string, { ...params, count });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get current locale
|
|
71
|
+
*/
|
|
72
|
+
getLocale(): string {
|
|
73
|
+
return this.locale;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Change locale
|
|
78
|
+
*/
|
|
79
|
+
async setLocale(locale: string): Promise<void> {
|
|
80
|
+
// Check if already loading
|
|
81
|
+
if (this.loadingPromises.has(locale)) {
|
|
82
|
+
await this.loadingPromises.get(locale);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if already loaded
|
|
87
|
+
if (this.translations.has(locale)) {
|
|
88
|
+
this.applyLocale(locale);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Load translations
|
|
93
|
+
const loadingPromise = this.loadTranslationsForLocale(locale);
|
|
94
|
+
this.loadingPromises.set(locale, loadingPromise);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await loadingPromise;
|
|
98
|
+
this.applyLocale(locale);
|
|
99
|
+
} finally {
|
|
100
|
+
this.loadingPromises.delete(locale);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get current direction
|
|
106
|
+
*/
|
|
107
|
+
getDirection(): "ltr" | "rtl" {
|
|
108
|
+
return this.direction;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get available locales
|
|
113
|
+
*/
|
|
114
|
+
getAvailableLocales(): string[] {
|
|
115
|
+
return Array.from(this.translations.keys());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check if locale is loaded
|
|
120
|
+
*/
|
|
121
|
+
isLocaleLoaded(locale: string): boolean {
|
|
122
|
+
return this.translations.has(locale);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Subscribe to changes
|
|
127
|
+
*/
|
|
128
|
+
subscribe(listener: () => void): () => void {
|
|
129
|
+
this.listeners.add(listener);
|
|
130
|
+
return () => {
|
|
131
|
+
this.listeners.delete(listener);
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if key exists
|
|
137
|
+
*/
|
|
138
|
+
hasKey(key: string): boolean {
|
|
139
|
+
const bundle = this.translations.get(this.locale);
|
|
140
|
+
return !!bundle?.translations[key];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private notifyListeners(): void {
|
|
144
|
+
for (const listener of this.listeners) {
|
|
145
|
+
listener();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private applyLocale(locale: string): void {
|
|
150
|
+
const bundle = this.translations.get(locale);
|
|
151
|
+
if (!bundle) return;
|
|
152
|
+
|
|
153
|
+
this.locale = locale;
|
|
154
|
+
this.direction = bundle.direction;
|
|
155
|
+
|
|
156
|
+
this.applyDOMDirection();
|
|
157
|
+
this.notifyListeners();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private applyDOMDirection(): void {
|
|
161
|
+
if (typeof document === "undefined") return;
|
|
162
|
+
|
|
163
|
+
document.documentElement.setAttribute("dir", this.direction);
|
|
164
|
+
document.documentElement.setAttribute("lang", this.locale);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async loadTranslationsForLocale(locale: string): Promise<void> {
|
|
168
|
+
if (!this.config.loadTranslations) {
|
|
169
|
+
throw new Error(`No translation loader configured for locale: ${locale}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const bundle = await this.config.loadTranslations(locale);
|
|
173
|
+
this.translations.set(locale, bundle);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private getTranslation(key: string): string | PluralTranslation {
|
|
177
|
+
// Try current locale
|
|
178
|
+
const currentBundle = this.translations.get(this.locale);
|
|
179
|
+
if (currentBundle?.translations[key]) {
|
|
180
|
+
return currentBundle.translations[key];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Try fallback locale
|
|
184
|
+
const fallbackBundle = this.translations.get(this.fallbackLocale);
|
|
185
|
+
if (fallbackBundle?.translations[key]) {
|
|
186
|
+
return fallbackBundle.translations[key];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Missing key
|
|
190
|
+
if (this.config.onMissingKey) {
|
|
191
|
+
this.config.onMissingKey(key, this.locale);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return key;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private interpolate(text: string, params?: Record<string, any>): string {
|
|
198
|
+
if (!params) return text;
|
|
199
|
+
|
|
200
|
+
return text.replace(/\{(\w+)\}/g, (match, key) => {
|
|
201
|
+
return params[key]?.toString() || match;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private selectPluralForm(
|
|
206
|
+
count: number,
|
|
207
|
+
locale: string,
|
|
208
|
+
): keyof PluralTranslation {
|
|
209
|
+
// Use Intl.PluralRules if available
|
|
210
|
+
if (typeof Intl !== "undefined" && Intl.PluralRules) {
|
|
211
|
+
try {
|
|
212
|
+
const rules = new Intl.PluralRules(locale);
|
|
213
|
+
const category = rules.select(count);
|
|
214
|
+
return category as keyof PluralTranslation;
|
|
215
|
+
} catch {
|
|
216
|
+
// Fall through to simple rules
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Fallback to simple rules
|
|
221
|
+
if (count === 0) return "zero";
|
|
222
|
+
if (count === 1) return "one";
|
|
223
|
+
return "other";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private detectBrowserLocale(): string {
|
|
227
|
+
if (typeof navigator === "undefined") return "en";
|
|
228
|
+
|
|
229
|
+
const browserLang =
|
|
230
|
+
navigator.language ||
|
|
231
|
+
(navigator.languages && navigator.languages[0]) ||
|
|
232
|
+
"en";
|
|
233
|
+
|
|
234
|
+
return browserLang.split("-")[0];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"save": "حفظ",
|
|
4
|
+
"cancel": "إلغاء",
|
|
5
|
+
"close": "إغلاق",
|
|
6
|
+
"loading": "جار التحميل...",
|
|
7
|
+
"error": "خطأ",
|
|
8
|
+
"retry": "إعادة المحاولة",
|
|
9
|
+
"back": "رجوع",
|
|
10
|
+
"next": "التالي",
|
|
11
|
+
"previous": "السابق",
|
|
12
|
+
"submit": "إرسال",
|
|
13
|
+
"continue": "متابعة",
|
|
14
|
+
"finish": "إنهاء",
|
|
15
|
+
"yes": "نعم",
|
|
16
|
+
"no": "لا",
|
|
17
|
+
"ok": "موافق",
|
|
18
|
+
"settings": "الإعدادات",
|
|
19
|
+
"language": "اللغة",
|
|
20
|
+
"theme": "المظهر",
|
|
21
|
+
"settings_aria": "فتح قائمة الإعدادات",
|
|
22
|
+
"rtl_notice": "اتجاه النص من اليمين إلى اليسار نشط",
|
|
23
|
+
"question": {
|
|
24
|
+
"one": "سؤال",
|
|
25
|
+
"other": "أسئلة"
|
|
26
|
+
},
|
|
27
|
+
"item": {
|
|
28
|
+
"one": "عنصر",
|
|
29
|
+
"other": "عناصر"
|
|
30
|
+
},
|
|
31
|
+
"character": {
|
|
32
|
+
"one": "حرف",
|
|
33
|
+
"other": "حروف"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"assessment": {
|
|
3
|
+
"title": "التقييم",
|
|
4
|
+
"questions": {
|
|
5
|
+
"one": "{count} سؤال",
|
|
6
|
+
"other": "{count} أسئلة"
|
|
7
|
+
},
|
|
8
|
+
"question_of": "السؤال {current} من {total}",
|
|
9
|
+
"student_name": "اسم الطالب",
|
|
10
|
+
"fullscreen": "ملء الشاشة",
|
|
11
|
+
"exit_fullscreen": "الخروج من ملء الشاشة",
|
|
12
|
+
"enter_fullscreen_aria": "الدخول إلى ملء الشاشة",
|
|
13
|
+
"exit_fullscreen_aria": "الخروج من ملء الشاشة"
|
|
14
|
+
},
|
|
15
|
+
"accommodation": {
|
|
16
|
+
"audio": "صوت",
|
|
17
|
+
"audio_aria": "تبديل الصوت/تحويل النص إلى كلام",
|
|
18
|
+
"contrast": "التباين",
|
|
19
|
+
"contrast_aria": "تبديل تباين الألوان"
|
|
20
|
+
},
|
|
21
|
+
"navigation": {
|
|
22
|
+
"back": "← رجوع",
|
|
23
|
+
"next": "التالي ←",
|
|
24
|
+
"previous": "السابق",
|
|
25
|
+
"submit": "إرسال",
|
|
26
|
+
"navigate_to": "الانتقال إلى السؤال {index}",
|
|
27
|
+
"section": "القسم"
|
|
28
|
+
},
|
|
29
|
+
"section": {
|
|
30
|
+
"title": "بنية التقييم",
|
|
31
|
+
"add": "+ إضافة قسم",
|
|
32
|
+
"untitled": "قسم بدون عنوان",
|
|
33
|
+
"remove": "حذف القسم",
|
|
34
|
+
"items": {
|
|
35
|
+
"one": "{count} عنصر",
|
|
36
|
+
"other": "{count} عناصر"
|
|
37
|
+
},
|
|
38
|
+
"aria_label": "القسم: {title}",
|
|
39
|
+
"drop_zone": "إسقاط العناصر هنا",
|
|
40
|
+
"unassigned": "عناصر غير مخصصة",
|
|
41
|
+
"unassigned_instruction": "اسحب هذه العناصر إلى الأقسام",
|
|
42
|
+
"unassigned_item_aria": "سؤال غير مخصص: {title}"
|
|
43
|
+
},
|
|
44
|
+
"item": {
|
|
45
|
+
"load_error": "تعذر تحميل السؤال.",
|
|
46
|
+
"untitled": "بدون عنوان"
|
|
47
|
+
}
|
|
48
|
+
}
|