lokal-core 1.0.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/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ // Core exports
2
+ export { ASTParser, type ExtractedString, type ScanOptions, type ScanResult } from './ast/parser';
3
+ export { ConfigLoader, type LokalConfig, defaultConfig } from './config/loader';
4
+ export { FileStorage, ContentHasher, type LocaleData, type LocaleFile } from './storage/file-storage';
5
+ export {
6
+ AITranslator,
7
+ TranslationProviderFactory,
8
+ OpenAIProvider,
9
+ GeminiProvider,
10
+ type TranslationProvider,
11
+ type TranslationRequest,
12
+ type TranslationResult,
13
+ type TranslationBatch
14
+ } from './ai/translator';
@@ -0,0 +1,113 @@
1
+ declare module 'lokal-core' {
2
+ export class ASTParser {
3
+ constructor(options: {
4
+ filePath: string;
5
+ functionName?: string;
6
+ componentName?: string;
7
+ });
8
+ scanDirectory(dirPath: string): ScanResult;
9
+ }
10
+
11
+ export interface ExtractedString {
12
+ key: string;
13
+ value: string;
14
+ location?: {
15
+ file: string;
16
+ line: number;
17
+ column: number;
18
+ };
19
+ }
20
+
21
+ export interface ScanResult {
22
+ strings: ExtractedString[];
23
+ errors: string[];
24
+ }
25
+
26
+ export interface ScanOptions {
27
+ filePath: string;
28
+ functionName?: string;
29
+ componentName?: string;
30
+ }
31
+
32
+ export class ConfigLoader {
33
+ load(configPath?: string): Promise<LokalConfig>;
34
+ loadSync(configPath?: string): LokalConfig;
35
+ }
36
+
37
+ export interface LokalConfig {
38
+ sourceDir: string;
39
+ outputDir: string;
40
+ defaultLocale: string;
41
+ functionName: string;
42
+ componentName: string;
43
+ }
44
+
45
+ export const defaultConfig: LokalConfig;
46
+
47
+ export class FileStorage {
48
+ constructor(outputDir: string);
49
+ loadLocale(locale: string): LocaleFile | null;
50
+ saveLocale(locale: string, data: LocaleData): void;
51
+ mergeLocaleData(locale: string, newData: Record<string, string>): LocaleData;
52
+ getAvailableLocales(): string[];
53
+ }
54
+
55
+ export interface LocaleData {
56
+ [key: string]: string;
57
+ }
58
+
59
+ export interface LocaleFile {
60
+ locale: string;
61
+ data: LocaleData;
62
+ }
63
+
64
+ export class ContentHasher {
65
+ static hash(content: string): string;
66
+ }
67
+
68
+ export class AITranslator {
69
+ constructor(provider: TranslationProvider, options?: {
70
+ batchSize?: number;
71
+ });
72
+ translateBatch(request: TranslationBatch): Promise<TranslationResult[]>;
73
+ }
74
+
75
+ export interface TranslationProvider {
76
+ translate(request: TranslationRequest): Promise<TranslationResult>;
77
+ }
78
+
79
+ export interface TranslationRequest {
80
+ text: string;
81
+ sourceLocale: string;
82
+ targetLocale: string;
83
+ context?: string;
84
+ }
85
+
86
+ export interface TranslationResult {
87
+ translatedText: string;
88
+ success: boolean;
89
+ error?: string;
90
+ }
91
+
92
+ export interface TranslationBatch {
93
+ items: TranslationRequest[];
94
+ }
95
+
96
+ export class OpenAIProvider implements TranslationProvider {
97
+ constructor(apiKey: string, options?: {
98
+ model?: string;
99
+ temperature?: number;
100
+ });
101
+ translate(request: TranslationRequest): Promise<TranslationResult>;
102
+ }
103
+
104
+ export class GeminiProvider implements TranslationProvider {
105
+ constructor(apiKey: string, options?: {
106
+ model?: string;
107
+ temperature?: number;
108
+ });
109
+ translate(request: TranslationRequest): Promise<TranslationResult>;
110
+ }
111
+
112
+ export function TranslationProviderFactory(provider: 'openai' | 'gemini', apiKey: string, options?: object): TranslationProvider;
113
+ }
@@ -0,0 +1,200 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as crypto from 'crypto';
4
+
5
+ export interface LocaleData {
6
+ [key: string]: string | LocaleData;
7
+ }
8
+
9
+ export interface LocaleFile {
10
+ locale: string;
11
+ data: LocaleData;
12
+ hash: string;
13
+ lastUpdated: string;
14
+ }
15
+
16
+ /**
17
+ * Content hash for incremental sync
18
+ */
19
+ export class ContentHasher {
20
+ /**
21
+ * Generate a hash for content to detect changes
22
+ */
23
+ static hash(content: string): string {
24
+ return crypto.createHash('md5').update(content).digest('hex');
25
+ }
26
+
27
+ /**
28
+ * Generate a hash for a locale data object
29
+ */
30
+ static hashLocaleData(data: LocaleData): string {
31
+ const content = JSON.stringify(data, Object.keys(data).sort());
32
+ return this.hash(content);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * File system persistence for locale files
38
+ */
39
+ export class FileStorage {
40
+ private basePath: string;
41
+
42
+ constructor(basePath: string) {
43
+ this.basePath = basePath;
44
+ this.ensureDirectoryExists(basePath);
45
+ }
46
+
47
+ /**
48
+ * Ensure directory exists
49
+ */
50
+ private ensureDirectoryExists(dirPath: string): void {
51
+ if (!fs.existsSync(dirPath)) {
52
+ fs.mkdirSync(dirPath, { recursive: true });
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Load locale data from file
58
+ */
59
+ loadLocale(locale: string): LocaleFile | null {
60
+ const filePath = this.getLocalePath(locale);
61
+
62
+ if (!fs.existsSync(filePath)) {
63
+ return null;
64
+ }
65
+
66
+ try {
67
+ const content = fs.readFileSync(filePath, 'utf-8');
68
+ return JSON.parse(content) as LocaleFile;
69
+ } catch (error) {
70
+ console.warn(`Failed to load locale ${locale}: ${error}`);
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Save locale data to file
77
+ */
78
+ saveLocale(locale: string, data: LocaleData): void {
79
+ const filePath = this.getLocalePath(locale);
80
+ const hash = ContentHasher.hashLocaleData(data);
81
+
82
+ const localeFile: LocaleFile = {
83
+ locale,
84
+ data,
85
+ hash,
86
+ lastUpdated: new Date().toISOString(),
87
+ };
88
+
89
+ const content = JSON.stringify(localeFile, null, 2);
90
+ fs.writeFileSync(filePath, content, 'utf-8');
91
+ }
92
+
93
+ /**
94
+ * Get all locale files
95
+ */
96
+ loadAllLocales(): Map<string, LocaleFile> {
97
+ const locales = new Map<string, LocaleFile>();
98
+
99
+ if (!fs.existsSync(this.basePath)) {
100
+ return locales;
101
+ }
102
+
103
+ const files = fs.readdirSync(this.basePath);
104
+
105
+ for (const file of files) {
106
+ if (file.endsWith('.json')) {
107
+ const locale = file.replace('.json', '');
108
+ const localeFile = this.loadLocale(locale);
109
+ if (localeFile) {
110
+ locales.set(locale, localeFile);
111
+ }
112
+ }
113
+ }
114
+
115
+ return locales;
116
+ }
117
+
118
+ /**
119
+ * Get the file path for a locale
120
+ */
121
+ private getLocalePath(locale: string): string {
122
+ return path.join(this.basePath, `${locale}.json`);
123
+ }
124
+
125
+ /**
126
+ * Check if a locale file exists
127
+ */
128
+ localeExists(locale: string): boolean {
129
+ return fs.existsSync(this.getLocalePath(locale));
130
+ }
131
+
132
+ /**
133
+ * Delete a locale file
134
+ */
135
+ deleteLocale(locale: string): boolean {
136
+ const filePath = this.getLocalePath(locale);
137
+
138
+ if (fs.existsSync(filePath)) {
139
+ fs.unlinkSync(filePath);
140
+ return true;
141
+ }
142
+
143
+ return false;
144
+ }
145
+
146
+ /**
147
+ * Get all available locales
148
+ */
149
+ getAvailableLocales(): string[] {
150
+ if (!fs.existsSync(this.basePath)) {
151
+ return [];
152
+ }
153
+
154
+ return fs.readdirSync(this.basePath)
155
+ .filter(file => file.endsWith('.json'))
156
+ .map(file => file.replace('.json', ''));
157
+ }
158
+
159
+ /**
160
+ * Merge new data with existing locale data
161
+ */
162
+ mergeLocaleData(locale: string, newData: LocaleData, preserveExisting: boolean = true): LocaleData {
163
+ const existing = this.loadLocale(locale);
164
+
165
+ if (!existing) {
166
+ return newData;
167
+ }
168
+
169
+ if (preserveExisting) {
170
+ return this.deepMerge(existing.data, newData);
171
+ }
172
+
173
+ return newData;
174
+ }
175
+
176
+ /**
177
+ * Deep merge two objects
178
+ */
179
+ private deepMerge(target: any, source: any): any {
180
+ if (typeof target !== 'object' || typeof source !== 'object') {
181
+ return source;
182
+ }
183
+
184
+ const result = { ...target };
185
+
186
+ for (const key in source) {
187
+ if (source.hasOwnProperty(key)) {
188
+ if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
189
+ result[key] = this.deepMerge(target[key] || {}, source[key]);
190
+ } else {
191
+ result[key] = source[key];
192
+ }
193
+ }
194
+ }
195
+
196
+ return result;
197
+ }
198
+ }
199
+
200
+ export default FileStorage;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Type declarations for @babel/traverse
3
+ * This file provides type definitions when the package's built-in types are not detected
4
+ */
5
+
6
+ declare module '@babel/traverse' {
7
+ import { Node as BabelNode } from '@babel/types';
8
+
9
+ export interface NodePath<T = BabelNode> {
10
+ node: T;
11
+ parent: BabelNode | null;
12
+ parentPath: NodePath | null;
13
+ scope: Scope;
14
+ hub: any;
15
+ context: any;
16
+ state: any;
17
+ opts: any;
18
+
19
+ get(key: string): any;
20
+ pushContext(context: any): void;
21
+ popContext(): void;
22
+ call(callback: (path: NodePath) => void, key: string): void;
23
+ isReferencedIdentifier(): boolean;
24
+ isBindingIdentifier(): boolean;
25
+ replaceWith(node: BabelNode | NodePath): void;
26
+ remove(): void;
27
+ find(callback: (path: NodePath) => boolean): NodePath | null;
28
+ findParent(callback: (path: NodePath) => boolean): NodePath | null;
29
+ traverse(visitors: Visitor<any>): void;
30
+ }
31
+
32
+ export interface Scope {
33
+ parent: Scope | null;
34
+ parentBlock: BabelNode;
35
+ block: BabelNode;
36
+ path: NodePath;
37
+ references: Map<string, NodePath[]>;
38
+ bindings: Map<string, Binding>;
39
+
40
+ getBinding(name: string): Binding | undefined;
41
+ hasBinding(name: string): boolean;
42
+ getOwnBinding(name: string): Binding | undefined;
43
+ }
44
+
45
+ export interface Binding {
46
+ kind: 'var' | 'let' | 'const' | 'module' | 'param' | 'local' | 'unknown';
47
+ path: NodePath;
48
+ scope: Scope;
49
+ constant: boolean;
50
+ constantViolations: NodePath[];
51
+ referencers: NodePath[];
52
+ references: number;
53
+ }
54
+
55
+ export interface Visitor<T> {
56
+ [key: string]: (path: NodePath<T>, state: any) => void;
57
+ }
58
+
59
+ export interface TraverseOptions {
60
+ scope?: boolean;
61
+ pragma?: string;
62
+ createParentMap?: boolean;
63
+ }
64
+
65
+ export default function traverse<S extends BabelNode = BabelNode>(
66
+ parent: S,
67
+ visitors: Visitor,
68
+ scope?: Scope,
69
+ state?: any,
70
+ opts?: TraverseOptions
71
+ ): void;
72
+
73
+ export { BabelNode as Node };
74
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "module": "ESNext",
7
+ "target": "ES2020"
8
+ },
9
+ "include": [
10
+ "src/**/*"
11
+ ],
12
+ "exclude": [
13
+ "node_modules",
14
+ "dist",
15
+ "**/*.test.ts"
16
+ ]
17
+ }