@lokascript/i18n 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/README.md +286 -0
- package/dist/browser.cjs +7669 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +50 -0
- package/dist/browser.d.ts +50 -0
- package/dist/browser.js +7592 -0
- package/dist/browser.js.map +1 -0
- package/dist/hyperfixi-i18n.min.js +2 -0
- package/dist/hyperfixi-i18n.min.js.map +1 -0
- package/dist/hyperfixi-i18n.mjs +8558 -0
- package/dist/hyperfixi-i18n.mjs.map +1 -0
- package/dist/index.cjs +14205 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +947 -0
- package/dist/index.d.ts +947 -0
- package/dist/index.js +14095 -0
- package/dist/index.js.map +1 -0
- package/dist/transformer-Ckask-yw.d.cts +1041 -0
- package/dist/transformer-Ckask-yw.d.ts +1041 -0
- package/package.json +84 -0
- package/src/browser.ts +122 -0
- package/src/compatibility/browser-tests/grammar-demo.spec.ts +169 -0
- package/src/constants.ts +366 -0
- package/src/dictionaries/ar.ts +233 -0
- package/src/dictionaries/bn.ts +156 -0
- package/src/dictionaries/de.ts +233 -0
- package/src/dictionaries/derive.ts +515 -0
- package/src/dictionaries/en.ts +237 -0
- package/src/dictionaries/es.ts +233 -0
- package/src/dictionaries/fr.ts +233 -0
- package/src/dictionaries/hi.ts +270 -0
- package/src/dictionaries/id.ts +233 -0
- package/src/dictionaries/index.ts +238 -0
- package/src/dictionaries/it.ts +233 -0
- package/src/dictionaries/ja.ts +233 -0
- package/src/dictionaries/ko.ts +233 -0
- package/src/dictionaries/ms.ts +276 -0
- package/src/dictionaries/pl.ts +239 -0
- package/src/dictionaries/pt.ts +237 -0
- package/src/dictionaries/qu.ts +233 -0
- package/src/dictionaries/ru.ts +270 -0
- package/src/dictionaries/sw.ts +233 -0
- package/src/dictionaries/th.ts +156 -0
- package/src/dictionaries/tl.ts +276 -0
- package/src/dictionaries/tr.ts +233 -0
- package/src/dictionaries/uk.ts +270 -0
- package/src/dictionaries/vi.ts +210 -0
- package/src/dictionaries/zh.ts +233 -0
- package/src/enhanced-i18n.test.ts +454 -0
- package/src/enhanced-i18n.ts +713 -0
- package/src/examples/new-languages.ts +326 -0
- package/src/formatting.test.ts +213 -0
- package/src/formatting.ts +416 -0
- package/src/grammar/direct-mappings.ts +353 -0
- package/src/grammar/grammar.test.ts +1053 -0
- package/src/grammar/index.ts +59 -0
- package/src/grammar/profiles/index.ts +860 -0
- package/src/grammar/transformer.ts +1318 -0
- package/src/grammar/types.ts +630 -0
- package/src/index.ts +202 -0
- package/src/new-languages.test.ts +389 -0
- package/src/parser/analyze-conflicts.test.ts +229 -0
- package/src/parser/ar.ts +40 -0
- package/src/parser/create-provider.ts +309 -0
- package/src/parser/de.ts +36 -0
- package/src/parser/es.ts +31 -0
- package/src/parser/fr.ts +31 -0
- package/src/parser/id.ts +34 -0
- package/src/parser/index.ts +50 -0
- package/src/parser/ja.ts +36 -0
- package/src/parser/ko.ts +37 -0
- package/src/parser/locale-manager.test.ts +198 -0
- package/src/parser/locale-manager.ts +197 -0
- package/src/parser/parser-integration.test.ts +439 -0
- package/src/parser/pt.ts +37 -0
- package/src/parser/qu.ts +37 -0
- package/src/parser/sw.ts +37 -0
- package/src/parser/tr.ts +38 -0
- package/src/parser/types.ts +113 -0
- package/src/parser/zh.ts +38 -0
- package/src/plugins/vite.ts +224 -0
- package/src/plugins/webpack.ts +124 -0
- package/src/pluralization.test.ts +197 -0
- package/src/pluralization.ts +393 -0
- package/src/runtime.ts +441 -0
- package/src/ssr-integration.ts +225 -0
- package/src/test-setup.ts +195 -0
- package/src/translation-validation.test.ts +171 -0
- package/src/translator.test.ts +252 -0
- package/src/translator.ts +297 -0
- package/src/types.ts +209 -0
- package/src/utils/locale.ts +190 -0
- package/src/utils/tokenizer-adapter.ts +469 -0
- package/src/utils/tokenizer.ts +19 -0
- package/src/validators/index.ts +174 -0
- package/src/validators/schema.ts +129 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// packages/i18n/src/index.ts
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Type Exports
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
DictionaryCategory,
|
|
9
|
+
Dictionary,
|
|
10
|
+
I18nConfig,
|
|
11
|
+
TranslationOptions,
|
|
12
|
+
ValidationResult,
|
|
13
|
+
ValidationError,
|
|
14
|
+
ValidationWarning,
|
|
15
|
+
LocaleMetadata,
|
|
16
|
+
TranslationContext,
|
|
17
|
+
TokenType,
|
|
18
|
+
Token,
|
|
19
|
+
TranslationResult,
|
|
20
|
+
} from './types';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
DICTIONARY_CATEGORIES,
|
|
24
|
+
isDictionaryCategory,
|
|
25
|
+
getDictionaryCategory,
|
|
26
|
+
forEachCategory,
|
|
27
|
+
findInDictionary,
|
|
28
|
+
translateFromEnglish,
|
|
29
|
+
} from './types';
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Translator Exports
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
export { HyperscriptTranslator } from './translator';
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Dictionary Exports
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
dictionaries,
|
|
43
|
+
en,
|
|
44
|
+
es,
|
|
45
|
+
ko,
|
|
46
|
+
zh,
|
|
47
|
+
fr,
|
|
48
|
+
de,
|
|
49
|
+
ja,
|
|
50
|
+
ar,
|
|
51
|
+
tr,
|
|
52
|
+
id,
|
|
53
|
+
qu,
|
|
54
|
+
sw,
|
|
55
|
+
pt,
|
|
56
|
+
supportedLocales,
|
|
57
|
+
isLocaleSupported,
|
|
58
|
+
getDictionary,
|
|
59
|
+
} from './dictionaries';
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// Parser Integration Exports
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
65
|
+
export type { KeywordProvider, KeywordProviderOptions } from './parser';
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
createKeywordProvider,
|
|
69
|
+
createEnglishProvider,
|
|
70
|
+
ENGLISH_COMMANDS,
|
|
71
|
+
ENGLISH_KEYWORDS,
|
|
72
|
+
UNIVERSAL_ENGLISH_KEYWORDS,
|
|
73
|
+
// Locale packs
|
|
74
|
+
esKeywords,
|
|
75
|
+
esDictionary,
|
|
76
|
+
jaKeywords,
|
|
77
|
+
jaDictionary,
|
|
78
|
+
frKeywords,
|
|
79
|
+
frDictionary,
|
|
80
|
+
deKeywords,
|
|
81
|
+
deDictionary,
|
|
82
|
+
arKeywords,
|
|
83
|
+
arDictionary,
|
|
84
|
+
koKeywords,
|
|
85
|
+
koDictionary,
|
|
86
|
+
zhKeywords,
|
|
87
|
+
zhDictionary,
|
|
88
|
+
trKeywords,
|
|
89
|
+
trDictionary,
|
|
90
|
+
idKeywords,
|
|
91
|
+
idDictionary,
|
|
92
|
+
quKeywords,
|
|
93
|
+
quDictionary,
|
|
94
|
+
swKeywords,
|
|
95
|
+
swDictionary,
|
|
96
|
+
ptKeywords,
|
|
97
|
+
ptDictionary,
|
|
98
|
+
// Locale management
|
|
99
|
+
LocaleManager,
|
|
100
|
+
detectBrowserLocale,
|
|
101
|
+
} from './parser';
|
|
102
|
+
|
|
103
|
+
// Re-export key utilities
|
|
104
|
+
export { detectLocale, getBrowserLocales, isRTL } from './utils/locale';
|
|
105
|
+
export { tokenize } from './utils/tokenizer';
|
|
106
|
+
export { validate } from './validators';
|
|
107
|
+
|
|
108
|
+
// Plugin exports
|
|
109
|
+
export { hyperscriptI18nVitePlugin } from './plugins/vite';
|
|
110
|
+
export { HyperscriptI18nWebpackPlugin } from './plugins/webpack';
|
|
111
|
+
|
|
112
|
+
// SSR integration
|
|
113
|
+
export { SSRLocaleManager, createExpressI18nMiddleware, withI18n } from './ssr-integration';
|
|
114
|
+
export type { SSRLocaleContext, SSRLocaleOptions } from './ssr-integration';
|
|
115
|
+
|
|
116
|
+
// Pluralization
|
|
117
|
+
export {
|
|
118
|
+
pluralRules,
|
|
119
|
+
getPlural,
|
|
120
|
+
PluralAwareTranslator,
|
|
121
|
+
pluralTimeExpressions,
|
|
122
|
+
} from './pluralization';
|
|
123
|
+
export type { PluralRule, PluralForms } from './pluralization';
|
|
124
|
+
|
|
125
|
+
// Formatting
|
|
126
|
+
export {
|
|
127
|
+
NumberFormatter,
|
|
128
|
+
DateFormatter,
|
|
129
|
+
LocaleFormatter,
|
|
130
|
+
getFormatter,
|
|
131
|
+
formatForLocale,
|
|
132
|
+
} from './formatting';
|
|
133
|
+
export type {
|
|
134
|
+
NumberFormatOptions,
|
|
135
|
+
DateFormatOptions,
|
|
136
|
+
RelativeTimeFormatOptions,
|
|
137
|
+
} from './formatting';
|
|
138
|
+
|
|
139
|
+
// Runtime i18n
|
|
140
|
+
export { RuntimeI18nManager, initializeI18n, getI18n, runtimeI18n } from './runtime';
|
|
141
|
+
export type { RuntimeI18nOptions } from './runtime';
|
|
142
|
+
|
|
143
|
+
// Create and export default translator instance
|
|
144
|
+
import { HyperscriptTranslator } from './translator';
|
|
145
|
+
export const defaultTranslator = new HyperscriptTranslator({ locale: 'en' });
|
|
146
|
+
|
|
147
|
+
// Create and export default runtime instance for browser usage
|
|
148
|
+
import { RuntimeI18nManager } from './runtime';
|
|
149
|
+
export const defaultRuntime = new RuntimeI18nManager({ locale: 'en' });
|
|
150
|
+
|
|
151
|
+
// Enhanced I18n Implementation (following enhanced pattern)
|
|
152
|
+
export {
|
|
153
|
+
TypedI18nContextImplementation,
|
|
154
|
+
createI18nContext,
|
|
155
|
+
createEnhancedI18n,
|
|
156
|
+
enhancedI18nImplementation,
|
|
157
|
+
EnhancedI18nInputSchema,
|
|
158
|
+
EnhancedI18nOutputSchema,
|
|
159
|
+
} from './enhanced-i18n';
|
|
160
|
+
export type { EnhancedI18nInput, EnhancedI18nOutput } from './enhanced-i18n';
|
|
161
|
+
|
|
162
|
+
// Grammar-aware transformation system (Phase 2)
|
|
163
|
+
export {
|
|
164
|
+
// Types
|
|
165
|
+
type SemanticRole,
|
|
166
|
+
type WordOrder,
|
|
167
|
+
type AdpositionType,
|
|
168
|
+
type MorphologyType,
|
|
169
|
+
type GrammaticalMarker,
|
|
170
|
+
type LanguageProfile,
|
|
171
|
+
type GrammarRule,
|
|
172
|
+
type PatternMatcher,
|
|
173
|
+
type PatternTransform,
|
|
174
|
+
type ParsedStatement,
|
|
175
|
+
type ParsedElement,
|
|
176
|
+
UNIVERSAL_PATTERNS,
|
|
177
|
+
LANGUAGE_FAMILY_DEFAULTS,
|
|
178
|
+
reorderRoles,
|
|
179
|
+
insertMarkers,
|
|
180
|
+
transformStatement,
|
|
181
|
+
// Profiles
|
|
182
|
+
profiles,
|
|
183
|
+
getProfile,
|
|
184
|
+
getSupportedLocales,
|
|
185
|
+
englishProfile,
|
|
186
|
+
japaneseProfile,
|
|
187
|
+
koreanProfile,
|
|
188
|
+
chineseProfile,
|
|
189
|
+
arabicProfile,
|
|
190
|
+
turkishProfile,
|
|
191
|
+
spanishProfile,
|
|
192
|
+
indonesianProfile,
|
|
193
|
+
quechuaProfile,
|
|
194
|
+
swahiliProfile,
|
|
195
|
+
// Transformer
|
|
196
|
+
GrammarTransformer,
|
|
197
|
+
parseStatement,
|
|
198
|
+
toLocale,
|
|
199
|
+
toEnglish,
|
|
200
|
+
translate,
|
|
201
|
+
examples as grammarExamples,
|
|
202
|
+
} from './grammar';
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for newly added language support
|
|
3
|
+
* Turkish, Indonesian, Quechua, and Swahili
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { HyperscriptTranslator } from './translator';
|
|
8
|
+
import { dictionaries } from './dictionaries';
|
|
9
|
+
import { PluralAwareTranslator, pluralRules } from './pluralization';
|
|
10
|
+
|
|
11
|
+
describe('New Language Support', () => {
|
|
12
|
+
const translator = new HyperscriptTranslator({ locale: 'en' });
|
|
13
|
+
|
|
14
|
+
describe('Turkish (tr)', () => {
|
|
15
|
+
it('should have complete dictionary coverage', () => {
|
|
16
|
+
expect(dictionaries.tr).toBeDefined();
|
|
17
|
+
expect(dictionaries.tr.commands.on).toBe('üzerinde');
|
|
18
|
+
expect(dictionaries.tr.commands.add).toBe('ekle');
|
|
19
|
+
expect(dictionaries.tr.commands.if).toBe('eğer');
|
|
20
|
+
expect(dictionaries.tr.logical.and).toBe('ve');
|
|
21
|
+
expect(dictionaries.tr.events.click).toBe('tıklama');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should translate basic commands', () => {
|
|
25
|
+
const result = translator.translate('on click add .active to me', { from: 'en', to: 'tr' });
|
|
26
|
+
// TODO: Once grammar transformation is implemented, test for native word order: 'tıklama üzerinde'
|
|
27
|
+
// Current word-by-word translation preserves English order
|
|
28
|
+
expect(result).toContain('tıklama'); // click → tıklama
|
|
29
|
+
expect(result).toContain('üzerinde'); // on → üzerinde
|
|
30
|
+
expect(result).toContain('ekle'); // add → ekle
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should handle conditional logic', () => {
|
|
34
|
+
const result = translator.translate('if my value is empty then hide me else show me', {
|
|
35
|
+
from: 'en',
|
|
36
|
+
to: 'tr',
|
|
37
|
+
});
|
|
38
|
+
expect(result).toContain('eğer');
|
|
39
|
+
expect(result).toContain('yoksa');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should translate time expressions', () => {
|
|
43
|
+
const result = translator.translate('wait 3 seconds then continue', { from: 'en', to: 'tr' });
|
|
44
|
+
expect(result).toContain('bekle');
|
|
45
|
+
expect(result).toContain('saniye');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle events properly', () => {
|
|
49
|
+
const result = translator.translate('on mouseenter add .hover', { from: 'en', to: 'tr' });
|
|
50
|
+
expect(result).toContain('fare_gir');
|
|
51
|
+
expect(result).toContain('ekle');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should support pluralization', () => {
|
|
55
|
+
expect(pluralRules.tr).toBeDefined();
|
|
56
|
+
expect(pluralRules.tr(1)).toBe('one');
|
|
57
|
+
expect(pluralRules.tr(5)).toBe('other');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle plural time expressions', () => {
|
|
61
|
+
const singular = PluralAwareTranslator.translateTimeExpression(1, 'second', 'tr');
|
|
62
|
+
const plural = PluralAwareTranslator.translateTimeExpression(5, 'second', 'tr');
|
|
63
|
+
expect(singular).toBe('1 saniye');
|
|
64
|
+
expect(plural).toBe('5 saniye');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('Indonesian (id)', () => {
|
|
69
|
+
it('should have complete dictionary coverage', () => {
|
|
70
|
+
expect(dictionaries.id).toBeDefined();
|
|
71
|
+
expect(dictionaries.id.commands.on).toBe('pada');
|
|
72
|
+
expect(dictionaries.id.commands.add).toBe('tambah');
|
|
73
|
+
expect(dictionaries.id.commands.if).toBe('jika');
|
|
74
|
+
expect(dictionaries.id.logical.and).toBe('dan');
|
|
75
|
+
expect(dictionaries.id.events.click).toBe('klik');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should translate basic commands', () => {
|
|
79
|
+
const result = translator.translate('on click toggle .selected', { from: 'en', to: 'id' });
|
|
80
|
+
// TODO: Once grammar transformation is implemented, test for native word order
|
|
81
|
+
expect(result).toContain('klik'); // click → klik
|
|
82
|
+
expect(result).toContain('pada'); // on → pada
|
|
83
|
+
expect(result).toContain('ganti'); // toggle → ganti
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should handle form interactions', () => {
|
|
87
|
+
const result = translator.translate('on submit take form data then send it', {
|
|
88
|
+
from: 'en',
|
|
89
|
+
to: 'id',
|
|
90
|
+
});
|
|
91
|
+
// TODO: Test expects Swahili-like translations - verify Indonesian dictionary values
|
|
92
|
+
expect(result).toContain('kirim'); // submit/send → kirim
|
|
93
|
+
expect(result).toContain('ambil'); // take → ambil
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should translate async operations', () => {
|
|
97
|
+
const result = translator.translate('fetch /api/data then put result into #content', {
|
|
98
|
+
from: 'en',
|
|
99
|
+
to: 'id',
|
|
100
|
+
});
|
|
101
|
+
expect(result).toContain('ambil'); // fetch → ambil
|
|
102
|
+
expect(result).toContain('taruh'); // put → taruh
|
|
103
|
+
expect(result).toContain('hasil'); // result → hasil
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should support simple pluralization (no actual plural forms)', () => {
|
|
107
|
+
expect(pluralRules.id).toBeDefined();
|
|
108
|
+
expect(pluralRules.id(1)).toBe('other');
|
|
109
|
+
expect(pluralRules.id(5)).toBe('other');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should handle time expressions without pluralization', () => {
|
|
113
|
+
const singular = PluralAwareTranslator.translateTimeExpression(1, 'minute', 'id');
|
|
114
|
+
const plural = PluralAwareTranslator.translateTimeExpression(5, 'minute', 'id');
|
|
115
|
+
expect(singular).toBe('1 menit');
|
|
116
|
+
expect(plural).toBe('5 menit');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('Quechua (qu)', () => {
|
|
121
|
+
it('should have complete dictionary coverage', () => {
|
|
122
|
+
expect(dictionaries.qu).toBeDefined();
|
|
123
|
+
expect(dictionaries.qu.commands.on).toBe('kaqpi');
|
|
124
|
+
expect(dictionaries.qu.commands.add).toBe('yapay');
|
|
125
|
+
expect(dictionaries.qu.commands.if).toBe('sichus');
|
|
126
|
+
expect(dictionaries.qu.logical.and).toBe('chaymanta');
|
|
127
|
+
expect(dictionaries.qu.events.click).toBe('ñitiy');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should translate basic commands', () => {
|
|
131
|
+
const result = translator.translate('on click put "Allin p\'unchaw" into #greeting', {
|
|
132
|
+
from: 'en',
|
|
133
|
+
to: 'qu',
|
|
134
|
+
});
|
|
135
|
+
// TODO: Once grammar transformation is implemented, test for native SOV word order
|
|
136
|
+
expect(result).toContain('ñitiy'); // click → ñitiy
|
|
137
|
+
expect(result).toContain('kaqpi'); // on → kaqpi
|
|
138
|
+
expect(result).toContain('churay'); // put → churay
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should handle complex expressions', () => {
|
|
142
|
+
const result = translator.translate('if target matches .button then add .pressed', {
|
|
143
|
+
from: 'en',
|
|
144
|
+
to: 'qu',
|
|
145
|
+
});
|
|
146
|
+
expect(result).toContain('sichus'); // if → sichus
|
|
147
|
+
expect(result).toContain('tupan'); // matches → tupan
|
|
148
|
+
expect(result).toContain('yapay'); // add → yapay
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should translate element references', () => {
|
|
152
|
+
const result = translator.translate('tell closest .card to add .highlight', {
|
|
153
|
+
from: 'en',
|
|
154
|
+
to: 'qu',
|
|
155
|
+
});
|
|
156
|
+
expect(result).toContain('niy'); // tell → niy
|
|
157
|
+
expect(result).toContain('aswan_kaylla'); // closest → aswan_kaylla
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should support pluralization', () => {
|
|
161
|
+
expect(pluralRules.qu).toBeDefined();
|
|
162
|
+
expect(pluralRules.qu(1)).toBe('one');
|
|
163
|
+
expect(pluralRules.qu(5)).toBe('other');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle plural time expressions', () => {
|
|
167
|
+
const singular = PluralAwareTranslator.translateTimeExpression(1, 'hour', 'qu');
|
|
168
|
+
const plural = PluralAwareTranslator.translateTimeExpression(3, 'hour', 'qu');
|
|
169
|
+
expect(singular).toBe('1 hora');
|
|
170
|
+
expect(plural).toBe('3 horakuna');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('Swahili (sw)', () => {
|
|
175
|
+
it('should have complete dictionary coverage', () => {
|
|
176
|
+
expect(dictionaries.sw).toBeDefined();
|
|
177
|
+
expect(dictionaries.sw.commands.on).toBe('kwenye');
|
|
178
|
+
expect(dictionaries.sw.commands.add).toBe('ongeza');
|
|
179
|
+
expect(dictionaries.sw.commands.if).toBe('kama');
|
|
180
|
+
expect(dictionaries.sw.logical.and).toBe('na');
|
|
181
|
+
expect(dictionaries.sw.events.click).toBe('bonyeza');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should translate basic commands', () => {
|
|
185
|
+
const result = translator.translate('on click put "Habari za asubuhi" into .message', {
|
|
186
|
+
from: 'en',
|
|
187
|
+
to: 'sw',
|
|
188
|
+
});
|
|
189
|
+
// TODO: Once grammar transformation is implemented, test for native word order
|
|
190
|
+
expect(result).toContain('bonyeza'); // click → bonyeza
|
|
191
|
+
expect(result).toContain('kwenye'); // on → kwenye
|
|
192
|
+
expect(result).toContain('weka'); // put → weka
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should handle double-click events', () => {
|
|
196
|
+
const result = translator.translate('on dblclick focus on me', { from: 'en', to: 'sw' });
|
|
197
|
+
expect(result).toContain('bonyeza_mara_mbili'); // dblclick → bonyeza_mara_mbili
|
|
198
|
+
expect(result).toContain('zingatia'); // focus → zingatia
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should translate navigation commands', () => {
|
|
202
|
+
const result = translator.translate('go to next .panel with transition', {
|
|
203
|
+
from: 'en',
|
|
204
|
+
to: 'sw',
|
|
205
|
+
});
|
|
206
|
+
expect(result).toContain('nenda'); // go → nenda
|
|
207
|
+
expect(result).toContain('ijayo'); // next → ijayo
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should support pluralization', () => {
|
|
211
|
+
expect(pluralRules.sw).toBeDefined();
|
|
212
|
+
expect(pluralRules.sw(1)).toBe('one');
|
|
213
|
+
expect(pluralRules.sw(5)).toBe('other');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should handle time expressions with special plural for hours', () => {
|
|
217
|
+
const singularHour = PluralAwareTranslator.translateTimeExpression(1, 'hour', 'sw');
|
|
218
|
+
const pluralHours = PluralAwareTranslator.translateTimeExpression(5, 'hour', 'sw');
|
|
219
|
+
expect(singularHour).toBe('1 saa');
|
|
220
|
+
expect(pluralHours).toBe('5 masaa');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('Cross-language consistency', () => {
|
|
225
|
+
const testExpressions = [
|
|
226
|
+
'on click add .active',
|
|
227
|
+
'if my value exists then show me',
|
|
228
|
+
'wait 2 seconds then hide me',
|
|
229
|
+
'on mouseenter tell closest .parent to add .hover',
|
|
230
|
+
'fetch /api/data then put result into #content',
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
const newLanguages = ['tr', 'id', 'qu', 'sw'];
|
|
234
|
+
|
|
235
|
+
it('should translate all test expressions in all new languages', () => {
|
|
236
|
+
for (const lang of newLanguages) {
|
|
237
|
+
for (const expr of testExpressions) {
|
|
238
|
+
expect(() => {
|
|
239
|
+
const result = translator.translate(expr, { from: 'en', to: lang });
|
|
240
|
+
expect(result).toBeTruthy();
|
|
241
|
+
expect(result.length).toBeGreaterThan(0);
|
|
242
|
+
}).not.toThrow();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should maintain consistent command translations', () => {
|
|
248
|
+
const commands = ['add', 'remove', 'show', 'hide', 'wait', 'put'];
|
|
249
|
+
|
|
250
|
+
for (const lang of newLanguages) {
|
|
251
|
+
const dict = dictionaries[lang];
|
|
252
|
+
for (const cmd of commands) {
|
|
253
|
+
expect(dict.commands[cmd]).toBeTruthy();
|
|
254
|
+
expect(dict.commands[cmd].length).toBeGreaterThan(0);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should have consistent event translations', () => {
|
|
260
|
+
const events = ['click', 'mouseenter', 'mouseleave', 'focus', 'blur', 'submit'];
|
|
261
|
+
|
|
262
|
+
for (const lang of newLanguages) {
|
|
263
|
+
const dict = dictionaries[lang];
|
|
264
|
+
for (const event of events) {
|
|
265
|
+
expect(dict.events[event]).toBeTruthy();
|
|
266
|
+
expect(dict.events[event].length).toBeGreaterThan(0);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should support ordinal numbers in all new languages', () => {
|
|
272
|
+
for (const lang of newLanguages) {
|
|
273
|
+
const first = PluralAwareTranslator.getOrdinal(1, lang);
|
|
274
|
+
const second = PluralAwareTranslator.getOrdinal(2, lang);
|
|
275
|
+
const third = PluralAwareTranslator.getOrdinal(3, lang);
|
|
276
|
+
|
|
277
|
+
expect(first).toBeTruthy();
|
|
278
|
+
expect(second).toBeTruthy();
|
|
279
|
+
expect(third).toBeTruthy();
|
|
280
|
+
expect(first).not.toBe(second);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('Cultural and linguistic accuracy', () => {
|
|
286
|
+
it('should use appropriate Turkish agglutination patterns', () => {
|
|
287
|
+
const dict = dictionaries.tr;
|
|
288
|
+
// Turkish typically uses suffixes, our translations should reflect this
|
|
289
|
+
expect(dict.modifiers.to).toBe('e');
|
|
290
|
+
expect(dict.modifiers.from).toBe('den');
|
|
291
|
+
expect(dict.modifiers.in).toBe('içinde');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should use appropriate Indonesian word order', () => {
|
|
295
|
+
const dict = dictionaries.id;
|
|
296
|
+
// Indonesian follows SVO order, similar to English
|
|
297
|
+
expect(dict.commands.if).toBe('jika');
|
|
298
|
+
expect(dict.logical.then).toBe('lalu');
|
|
299
|
+
expect(dict.logical.else).toBe('lainnya');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should reflect Quechua agglutinative nature', () => {
|
|
303
|
+
const dict = dictionaries.qu;
|
|
304
|
+
// Quechua uses complex suffixation
|
|
305
|
+
expect(dict.modifiers.to).toBe('man');
|
|
306
|
+
expect(dict.modifiers.from).toBe('manta');
|
|
307
|
+
expect(dict.expressions.children).toBe('wawakuna'); // -kuna is plural marker
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should use appropriate Swahili noun class system hints', () => {
|
|
311
|
+
const dict = dictionaries.sw;
|
|
312
|
+
// Swahili has noun classes, reflected in some translations
|
|
313
|
+
expect(dict.expressions.children).toBe('watoto'); // wa- prefix for people
|
|
314
|
+
expect(dict.values.window).toBe('dirisha'); // appropriate class
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('Real-world usage scenarios', () => {
|
|
319
|
+
it('should handle e-commerce interactions in Turkish', () => {
|
|
320
|
+
const expr =
|
|
321
|
+
'on click if stock exists then add product to cart else show out-of-stock message';
|
|
322
|
+
const result = translator.translate(expr, { from: 'en', to: 'tr' });
|
|
323
|
+
// TODO: Test native word order once grammar transformation is implemented
|
|
324
|
+
expect(result).toContain('tıklama'); // click
|
|
325
|
+
expect(result).toContain('üzerinde'); // on
|
|
326
|
+
expect(result).toContain('eğer'); // if
|
|
327
|
+
expect(result).toContain('yoksa'); // else
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should handle form validation in Indonesian', () => {
|
|
331
|
+
const expr = 'on blur if my value is empty then add .error to closest .field';
|
|
332
|
+
const result = translator.translate(expr, { from: 'en', to: 'id' });
|
|
333
|
+
expect(result).toContain('blur'); // blur (event name preserved or translated)
|
|
334
|
+
expect(result).toContain('jika'); // if
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should handle social interactions in Quechua', () => {
|
|
338
|
+
const expr = 'on click toggle .liked then send like-count to /api/likes';
|
|
339
|
+
const result = translator.translate(expr, { from: 'en', to: 'qu' });
|
|
340
|
+
expect(result).toContain('ñitiy'); // click
|
|
341
|
+
expect(result).toContain('kaqpi'); // on
|
|
342
|
+
expect(result).toContain('tikray'); // toggle
|
|
343
|
+
expect(result).toContain('kachay'); // send
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should handle dashboard updates in Swahili', () => {
|
|
347
|
+
const expr = 'on load fetch /api/dashboard then for each item put item into next .widget';
|
|
348
|
+
const result = translator.translate(expr, { from: 'en', to: 'sw' });
|
|
349
|
+
expect(result).toContain('pakia'); // load
|
|
350
|
+
expect(result).toContain('kwenye'); // on
|
|
351
|
+
expect(result).toContain('leta'); // fetch
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe('Performance and edge cases', () => {
|
|
356
|
+
const testLanguages = ['tr', 'id', 'qu', 'sw'];
|
|
357
|
+
|
|
358
|
+
it('should handle very long expressions efficiently', () => {
|
|
359
|
+
const longExpr =
|
|
360
|
+
'on click if my value matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/ then add .valid to closest .form-group then remove .invalid from closest .form-group then put "Valid email" into next .feedback else add .invalid to closest .form-group then remove .valid from closest .form-group then put "Invalid email format" into next .feedback';
|
|
361
|
+
|
|
362
|
+
for (const lang of testLanguages) {
|
|
363
|
+
expect(() => {
|
|
364
|
+
const result = translator.translate(longExpr, { from: 'en', to: lang });
|
|
365
|
+
expect(result.length).toBeGreaterThan(0);
|
|
366
|
+
}).not.toThrow();
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('should handle expressions with mixed content', () => {
|
|
371
|
+
const mixedExpr = 'on click put "Hello مرحبا Hola" into #multilingual-content';
|
|
372
|
+
|
|
373
|
+
for (const lang of testLanguages) {
|
|
374
|
+
const result = translator.translate(mixedExpr, { from: 'en', to: lang });
|
|
375
|
+
expect(result).toContain('"Hello مرحبا Hola"'); // String literals should be preserved
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should handle numeric expressions correctly', () => {
|
|
380
|
+
const numericExpr = 'wait 1.5 seconds then set my opacity to 0.5';
|
|
381
|
+
|
|
382
|
+
for (const lang of testLanguages) {
|
|
383
|
+
const result = translator.translate(numericExpr, { from: 'en', to: lang });
|
|
384
|
+
expect(result).toContain('1.5');
|
|
385
|
+
expect(result).toContain('0.5');
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
});
|