@lokascript/domain-learn 2.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.
- package/dist/generators/gloss-generator.d.ts +18 -0
- package/dist/generators/learn-renderer.d.ts +13 -0
- package/dist/generators/sentence-generator.d.ts +34 -0
- package/dist/index.cjs +6116 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +441 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +6056 -0
- package/dist/index.js.map +1 -0
- package/dist/profiles/ar.d.ts +2 -0
- package/dist/profiles/de.d.ts +2 -0
- package/dist/profiles/en.d.ts +2 -0
- package/dist/profiles/es.d.ts +2 -0
- package/dist/profiles/fr.d.ts +2 -0
- package/dist/profiles/index.d.ts +20 -0
- package/dist/profiles/ja.d.ts +2 -0
- package/dist/profiles/ko.d.ts +2 -0
- package/dist/profiles/pt.d.ts +2 -0
- package/dist/profiles/tr.d.ts +2 -0
- package/dist/profiles/zh.d.ts +2 -0
- package/dist/schemas/index.d.ts +31 -0
- package/dist/tokenizers/index.d.ts +23 -0
- package/dist/types.d.ts +266 -0
- package/package.json +63 -0
- package/src/__tests__/schemas.test.ts +145 -0
- package/src/__tests__/sentence-generation.test.ts +189 -0
- package/src/generators/gloss-generator.ts +145 -0
- package/src/generators/learn-renderer.ts +291 -0
- package/src/generators/sentence-generator.ts +501 -0
- package/src/index.ts +237 -0
- package/src/profiles/ar.ts +526 -0
- package/src/profiles/de.ts +481 -0
- package/src/profiles/en.ts +181 -0
- package/src/profiles/es.ts +829 -0
- package/src/profiles/fr.ts +466 -0
- package/src/profiles/index.ts +34 -0
- package/src/profiles/ja.ts +301 -0
- package/src/profiles/ko.ts +286 -0
- package/src/profiles/pt.ts +484 -0
- package/src/profiles/tr.ts +511 -0
- package/src/profiles/zh.ts +256 -0
- package/src/schemas/index.ts +576 -0
- package/src/tokenizers/index.ts +409 -0
- package/src/types.ts +321 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentence Generation Tests — Verify morphology rendering across languages
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
createLearnDSL,
|
|
8
|
+
generateForFunction,
|
|
9
|
+
generateAllFunctions,
|
|
10
|
+
generateCrossLingual,
|
|
11
|
+
generateGloss,
|
|
12
|
+
renderLearn,
|
|
13
|
+
ALL_VERBS,
|
|
14
|
+
ALL_FUNCTIONS,
|
|
15
|
+
} from '../index';
|
|
16
|
+
import type { MultilingualDSL } from '@lokascript/framework';
|
|
17
|
+
|
|
18
|
+
describe('Sentence Generation', () => {
|
|
19
|
+
let learn: MultilingualDSL;
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
learn = createLearnDSL();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('English Sentences', () => {
|
|
26
|
+
it('should render commanding form', () => {
|
|
27
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
28
|
+
const result = generateForFunction(node, 'commanding', 'en');
|
|
29
|
+
expect(result).not.toBeNull();
|
|
30
|
+
expect(result!.sentence).toContain('add');
|
|
31
|
+
expect(result!.sentence).toContain('.active');
|
|
32
|
+
expect(result!.sentence).toContain('#button');
|
|
33
|
+
expect(result!.function).toBe('commanding');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should render describing form with thirdPerson', () => {
|
|
37
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
38
|
+
const result = generateForFunction(node, 'describing', 'en');
|
|
39
|
+
expect(result).not.toBeNull();
|
|
40
|
+
expect(result!.sentence).toContain('adds');
|
|
41
|
+
expect(result!.sentence).toContain('the system');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should render narrating form with past tense', () => {
|
|
45
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
46
|
+
const result = generateForFunction(node, 'narrating', 'en');
|
|
47
|
+
expect(result).not.toBeNull();
|
|
48
|
+
expect(result!.sentence).toContain('added');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should render questioning form', () => {
|
|
52
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
53
|
+
const result = generateForFunction(node, 'questioning', 'en');
|
|
54
|
+
expect(result).not.toBeNull();
|
|
55
|
+
expect(result!.sentence).toContain('Did');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should render negating form', () => {
|
|
59
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
60
|
+
const result = generateForFunction(node, 'negating', 'en');
|
|
61
|
+
expect(result).not.toBeNull();
|
|
62
|
+
expect(result!.sentence).toContain('did not');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should render planning form', () => {
|
|
66
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
67
|
+
const result = generateForFunction(node, 'planning', 'en');
|
|
68
|
+
expect(result).not.toBeNull();
|
|
69
|
+
expect(result!.sentence).toContain('will');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should render progressing form', () => {
|
|
73
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
74
|
+
const result = generateForFunction(node, 'progressing', 'en');
|
|
75
|
+
expect(result).not.toBeNull();
|
|
76
|
+
expect(result!.sentence).toContain('adding');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle irregular verbs correctly', () => {
|
|
80
|
+
const node = learn.parse('[go destination:#page]', 'explicit');
|
|
81
|
+
const past = generateForFunction(node, 'narrating', 'en');
|
|
82
|
+
expect(past).not.toBeNull();
|
|
83
|
+
expect(past!.sentence).toContain('went');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should handle source-role verbs', () => {
|
|
87
|
+
const node = learn.parse('[remove patient:.active source:#button]', 'explicit');
|
|
88
|
+
const result = generateForFunction(node, 'commanding', 'en');
|
|
89
|
+
expect(result).not.toBeNull();
|
|
90
|
+
expect(result!.sentence).toContain('from');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('Japanese Sentences (SOV)', () => {
|
|
95
|
+
it('should render te-form command', () => {
|
|
96
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
97
|
+
const result = generateForFunction(node, 'commanding', 'ja');
|
|
98
|
+
expect(result).not.toBeNull();
|
|
99
|
+
expect(result!.sentence).toContain('追加して');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should render polite present (masu)', () => {
|
|
103
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
104
|
+
const result = generateForFunction(node, 'describing', 'ja');
|
|
105
|
+
expect(result).not.toBeNull();
|
|
106
|
+
expect(result!.sentence).toContain('追加します');
|
|
107
|
+
expect(result!.sentence).toContain('システム');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should place verb last in SOV order', () => {
|
|
111
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
112
|
+
const result = generateForFunction(node, 'commanding', 'ja');
|
|
113
|
+
expect(result).not.toBeNull();
|
|
114
|
+
// Verb should be at the end
|
|
115
|
+
const sentence = result!.sentence;
|
|
116
|
+
expect(sentence.endsWith('追加して')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('Arabic Sentences (VSO)', () => {
|
|
121
|
+
it('should render imperative', () => {
|
|
122
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
123
|
+
const result = generateForFunction(node, 'commanding', 'ar');
|
|
124
|
+
expect(result).not.toBeNull();
|
|
125
|
+
expect(result!.sentence).toContain('أضِف');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('All Languages x All Functions', () => {
|
|
130
|
+
const languages = ['en', 'ja', 'es', 'ar', 'zh', 'ko', 'fr', 'tr', 'de', 'pt'];
|
|
131
|
+
|
|
132
|
+
it('should generate sentences for all 70 language x function combinations', () => {
|
|
133
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
134
|
+
let count = 0;
|
|
135
|
+
for (const lang of languages) {
|
|
136
|
+
for (const fn of ALL_FUNCTIONS) {
|
|
137
|
+
const result = generateForFunction(node, fn, lang);
|
|
138
|
+
expect(result).not.toBeNull();
|
|
139
|
+
expect(result!.sentence.length).toBeGreaterThan(0);
|
|
140
|
+
count++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
expect(count).toBe(70);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('generateAllFunctions', () => {
|
|
148
|
+
it('should return 7 sentences for English', () => {
|
|
149
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
150
|
+
const results = generateAllFunctions(node, 'en');
|
|
151
|
+
expect(results).toHaveLength(7);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('generateCrossLingual', () => {
|
|
156
|
+
it('should return sentences for all registered languages', () => {
|
|
157
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
158
|
+
const results = generateCrossLingual(node, 'commanding');
|
|
159
|
+
expect(results.length).toBe(10);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('renderLearn', () => {
|
|
164
|
+
it('should render DSL text in English', () => {
|
|
165
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
166
|
+
const text = renderLearn(node, 'en');
|
|
167
|
+
expect(text).toContain('add');
|
|
168
|
+
expect(text).toContain('.active');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should render DSL text in Japanese', () => {
|
|
172
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
173
|
+
const text = renderLearn(node, 'ja');
|
|
174
|
+
expect(text).toContain('追加して');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('generateGloss', () => {
|
|
179
|
+
it('should produce interlinear gloss for add', () => {
|
|
180
|
+
const node = learn.parse('[add patient:.active destination:#button]', 'explicit');
|
|
181
|
+
const gloss = generateGloss(node, 'commanding', 'ja');
|
|
182
|
+
expect(gloss).not.toBeNull();
|
|
183
|
+
expect(gloss!.tokens.length).toBeGreaterThan(0);
|
|
184
|
+
expect(gloss!.roles.length).toBe(gloss!.tokens.length);
|
|
185
|
+
expect(gloss!.english.length).toBe(gloss!.tokens.length);
|
|
186
|
+
expect(gloss!.roles).toContain('VERB');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gloss Generator — Interlinear Gloss for Learning
|
|
3
|
+
*
|
|
4
|
+
* Produces interlinear glosses from a SemanticNode, showing:
|
|
5
|
+
* tokens: target language tokens ["#buttonに", ".activeを", "追加して"]
|
|
6
|
+
* roles: grammatical labels ["DEST", "PAT", "VERB"]
|
|
7
|
+
* english: English gloss ["to #button", ".active", "add (imperative)"]
|
|
8
|
+
*
|
|
9
|
+
* Glosses help ESL students understand the grammatical function of each
|
|
10
|
+
* word/particle in context, and help code students understand the mapping
|
|
11
|
+
* between natural language and programming operations.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { SemanticNode } from '@lokascript/framework';
|
|
15
|
+
import { extractRoleValue } from '@lokascript/framework';
|
|
16
|
+
import type { InterlinearGloss, CommunicativeFunction } from '../types';
|
|
17
|
+
import { resolveMarker, attachMarker, getProfile } from './sentence-generator';
|
|
18
|
+
|
|
19
|
+
/** Abbreviated role labels for gloss display */
|
|
20
|
+
const ROLE_LABELS: Record<string, string> = {
|
|
21
|
+
patient: 'PAT',
|
|
22
|
+
destination: 'DEST',
|
|
23
|
+
source: 'SRC',
|
|
24
|
+
instrument: 'INST',
|
|
25
|
+
style: 'STYLE',
|
|
26
|
+
manner: 'MANNER',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const FUNCTION_LABELS: Record<string, string> = {
|
|
30
|
+
commanding: 'imperative',
|
|
31
|
+
describing: 'present',
|
|
32
|
+
narrating: 'past',
|
|
33
|
+
questioning: 'question',
|
|
34
|
+
negating: 'negative',
|
|
35
|
+
planning: 'future',
|
|
36
|
+
progressing: 'progressive',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** SOV languages where particles attach to nouns */
|
|
40
|
+
const POSTPOSITIONAL = new Set(['ja', 'ko']);
|
|
41
|
+
|
|
42
|
+
/** Command profiles for gloss generation */
|
|
43
|
+
const PROFILES: Record<string, { hasPatient: boolean; targetRole: string | null }> = {
|
|
44
|
+
add: { hasPatient: true, targetRole: 'destination' },
|
|
45
|
+
remove: { hasPatient: true, targetRole: 'source' },
|
|
46
|
+
toggle: { hasPatient: true, targetRole: 'destination' },
|
|
47
|
+
put: { hasPatient: true, targetRole: 'destination' },
|
|
48
|
+
set: { hasPatient: true, targetRole: 'destination' },
|
|
49
|
+
show: { hasPatient: true, targetRole: null },
|
|
50
|
+
hide: { hasPatient: true, targetRole: null },
|
|
51
|
+
get: { hasPatient: false, targetRole: 'source' },
|
|
52
|
+
wait: { hasPatient: true, targetRole: null },
|
|
53
|
+
fetch: { hasPatient: false, targetRole: 'source' },
|
|
54
|
+
send: { hasPatient: true, targetRole: 'destination' },
|
|
55
|
+
go: { hasPatient: false, targetRole: 'destination' },
|
|
56
|
+
increment: { hasPatient: true, targetRole: null },
|
|
57
|
+
decrement: { hasPatient: true, targetRole: null },
|
|
58
|
+
take: { hasPatient: true, targetRole: 'source' },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate an interlinear gloss for a SemanticNode.
|
|
63
|
+
*/
|
|
64
|
+
export function generateGloss(
|
|
65
|
+
node: SemanticNode,
|
|
66
|
+
fn: CommunicativeFunction,
|
|
67
|
+
language: string
|
|
68
|
+
): InterlinearGloss | null {
|
|
69
|
+
const profile = getProfile(language);
|
|
70
|
+
if (!profile) return null;
|
|
71
|
+
|
|
72
|
+
const verb = node.action;
|
|
73
|
+
const cmdProfile = PROFILES[verb];
|
|
74
|
+
if (!cmdProfile) return null;
|
|
75
|
+
|
|
76
|
+
const forms = profile.morphologyTable[verb];
|
|
77
|
+
if (!forms) return null;
|
|
78
|
+
|
|
79
|
+
const frame = profile.frames.frames.find(f => f.function === fn);
|
|
80
|
+
if (!frame) return null;
|
|
81
|
+
|
|
82
|
+
const tokens: string[] = [];
|
|
83
|
+
const roles: string[] = [];
|
|
84
|
+
const english: string[] = [];
|
|
85
|
+
|
|
86
|
+
const patientValue = extractRoleValue(node, 'patient') || '';
|
|
87
|
+
const targetRole = cmdProfile.targetRole;
|
|
88
|
+
const targetValue = targetRole ? extractRoleValue(node, targetRole) || '' : '';
|
|
89
|
+
|
|
90
|
+
const isSOV = profile.frames.wordOrder === 'SOV';
|
|
91
|
+
|
|
92
|
+
if (isSOV) {
|
|
93
|
+
// SOV order: target, patient, verb
|
|
94
|
+
if (targetValue && targetRole) {
|
|
95
|
+
const marker = resolveMarker(verb, targetRole, language);
|
|
96
|
+
tokens.push(attachMarker(targetValue, marker));
|
|
97
|
+
roles.push(ROLE_LABELS[targetRole] || targetRole.toUpperCase());
|
|
98
|
+
english.push(attachMarker(targetValue, resolveMarker(verb, targetRole, 'en')));
|
|
99
|
+
}
|
|
100
|
+
if (patientValue && cmdProfile.hasPatient) {
|
|
101
|
+
const marker = resolveMarker(verb, 'patient', language);
|
|
102
|
+
tokens.push(attachMarker(patientValue, marker));
|
|
103
|
+
roles.push(ROLE_LABELS.patient);
|
|
104
|
+
english.push(patientValue);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// SVO order: patient, target (verb is handled separately)
|
|
108
|
+
if (patientValue && cmdProfile.hasPatient) {
|
|
109
|
+
const marker = resolveMarker(verb, 'patient', language);
|
|
110
|
+
tokens.push(attachMarker(patientValue, marker));
|
|
111
|
+
roles.push(ROLE_LABELS.patient);
|
|
112
|
+
english.push(patientValue);
|
|
113
|
+
}
|
|
114
|
+
if (targetValue && targetRole) {
|
|
115
|
+
const marker = resolveMarker(verb, targetRole, language);
|
|
116
|
+
tokens.push(attachMarker(targetValue, marker));
|
|
117
|
+
roles.push(ROLE_LABELS[targetRole] || targetRole.toUpperCase());
|
|
118
|
+
english.push(attachMarker(targetValue, resolveMarker(verb, targetRole, 'en')));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Resolve verb form
|
|
123
|
+
const verbFormPath = frame.verbForm;
|
|
124
|
+
const parts = verbFormPath.split('.');
|
|
125
|
+
let verbValue: unknown = forms;
|
|
126
|
+
for (const part of parts) {
|
|
127
|
+
if (verbValue == null || typeof verbValue !== 'object') break;
|
|
128
|
+
verbValue = (verbValue as Record<string, unknown>)[part];
|
|
129
|
+
}
|
|
130
|
+
const verbStr = typeof verbValue === 'string' ? verbValue : verb;
|
|
131
|
+
|
|
132
|
+
if (isSOV) {
|
|
133
|
+
// Verb last in SOV
|
|
134
|
+
tokens.push(verbStr);
|
|
135
|
+
roles.push('VERB');
|
|
136
|
+
english.push(`${verb} (${FUNCTION_LABELS[fn] || fn})`);
|
|
137
|
+
} else {
|
|
138
|
+
// Verb first in SVO — prepend
|
|
139
|
+
tokens.unshift(verbStr);
|
|
140
|
+
roles.unshift('VERB');
|
|
141
|
+
english.unshift(`${verb} (${FUNCTION_LABELS[fn] || fn})`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { tokens, roles, english };
|
|
145
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learn Renderer — SemanticNode → DSL text in any language
|
|
3
|
+
*
|
|
4
|
+
* Inverse of the parser: converts a parsed SemanticNode back to
|
|
5
|
+
* natural-language DSL text. Used for cross-language translation
|
|
6
|
+
* exercises and round-trip verification.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SemanticNode } from '@lokascript/framework';
|
|
10
|
+
import { extractRoleValue } from '@lokascript/framework';
|
|
11
|
+
|
|
12
|
+
/** Verb keyword translations: action → language → native keyword */
|
|
13
|
+
const COMMAND_KEYWORDS: Record<string, Record<string, string>> = {
|
|
14
|
+
add: {
|
|
15
|
+
en: 'add',
|
|
16
|
+
ja: '追加して',
|
|
17
|
+
es: 'agrega',
|
|
18
|
+
ar: 'أضف',
|
|
19
|
+
zh: '添加',
|
|
20
|
+
ko: '추가해',
|
|
21
|
+
fr: 'ajoute',
|
|
22
|
+
tr: 'ekle',
|
|
23
|
+
de: 'füge hinzu',
|
|
24
|
+
pt: 'adicione',
|
|
25
|
+
},
|
|
26
|
+
remove: {
|
|
27
|
+
en: 'remove',
|
|
28
|
+
ja: '削除して',
|
|
29
|
+
es: 'elimina',
|
|
30
|
+
ar: 'أزل',
|
|
31
|
+
zh: '移除',
|
|
32
|
+
ko: '제거해',
|
|
33
|
+
fr: 'supprime',
|
|
34
|
+
tr: 'kaldır',
|
|
35
|
+
de: 'entferne',
|
|
36
|
+
pt: 'remova',
|
|
37
|
+
},
|
|
38
|
+
toggle: {
|
|
39
|
+
en: 'toggle',
|
|
40
|
+
ja: '切り替えて',
|
|
41
|
+
es: 'alterna',
|
|
42
|
+
ar: 'بدّل',
|
|
43
|
+
zh: '切换',
|
|
44
|
+
ko: '전환해',
|
|
45
|
+
fr: 'bascule',
|
|
46
|
+
tr: 'değiştir',
|
|
47
|
+
de: 'schalte um',
|
|
48
|
+
pt: 'alterne',
|
|
49
|
+
},
|
|
50
|
+
put: {
|
|
51
|
+
en: 'put',
|
|
52
|
+
ja: '置いて',
|
|
53
|
+
es: 'pon',
|
|
54
|
+
ar: 'ضع',
|
|
55
|
+
zh: '放置',
|
|
56
|
+
ko: '넣어',
|
|
57
|
+
fr: 'mets',
|
|
58
|
+
tr: 'koy',
|
|
59
|
+
de: 'setze',
|
|
60
|
+
pt: 'coloque',
|
|
61
|
+
},
|
|
62
|
+
set: {
|
|
63
|
+
en: 'set',
|
|
64
|
+
ja: '設定して',
|
|
65
|
+
es: 'establece',
|
|
66
|
+
ar: 'عيّن',
|
|
67
|
+
zh: '设置',
|
|
68
|
+
ko: '설정해',
|
|
69
|
+
fr: 'définis',
|
|
70
|
+
tr: 'ayarla',
|
|
71
|
+
de: 'stelle ein',
|
|
72
|
+
pt: 'defina',
|
|
73
|
+
},
|
|
74
|
+
show: {
|
|
75
|
+
en: 'show',
|
|
76
|
+
ja: '表示して',
|
|
77
|
+
es: 'muestra',
|
|
78
|
+
ar: 'أظهر',
|
|
79
|
+
zh: '显示',
|
|
80
|
+
ko: '표시해',
|
|
81
|
+
fr: 'affiche',
|
|
82
|
+
tr: 'göster',
|
|
83
|
+
de: 'zeige',
|
|
84
|
+
pt: 'mostre',
|
|
85
|
+
},
|
|
86
|
+
hide: {
|
|
87
|
+
en: 'hide',
|
|
88
|
+
ja: '隠して',
|
|
89
|
+
es: 'oculta',
|
|
90
|
+
ar: 'أخفِ',
|
|
91
|
+
zh: '隐藏',
|
|
92
|
+
ko: '숨겨',
|
|
93
|
+
fr: 'masque',
|
|
94
|
+
tr: 'gizle',
|
|
95
|
+
de: 'verberge',
|
|
96
|
+
pt: 'esconda',
|
|
97
|
+
},
|
|
98
|
+
get: {
|
|
99
|
+
en: 'get',
|
|
100
|
+
ja: '取得して',
|
|
101
|
+
es: 'obtén',
|
|
102
|
+
ar: 'احصل',
|
|
103
|
+
zh: '获取',
|
|
104
|
+
ko: '가져와',
|
|
105
|
+
fr: 'obtiens',
|
|
106
|
+
tr: 'al',
|
|
107
|
+
de: 'rufe ab',
|
|
108
|
+
pt: 'obtenha',
|
|
109
|
+
},
|
|
110
|
+
wait: {
|
|
111
|
+
en: 'wait',
|
|
112
|
+
ja: '待って',
|
|
113
|
+
es: 'espera',
|
|
114
|
+
ar: 'انتظر',
|
|
115
|
+
zh: '等待',
|
|
116
|
+
ko: '기다려',
|
|
117
|
+
fr: 'attends',
|
|
118
|
+
tr: 'bekle',
|
|
119
|
+
de: 'warte',
|
|
120
|
+
pt: 'espere',
|
|
121
|
+
},
|
|
122
|
+
fetch: {
|
|
123
|
+
en: 'fetch',
|
|
124
|
+
ja: '取得して',
|
|
125
|
+
es: 'busca',
|
|
126
|
+
ar: 'اجلب',
|
|
127
|
+
zh: '获取',
|
|
128
|
+
ko: '가져와',
|
|
129
|
+
fr: 'récupère',
|
|
130
|
+
tr: 'getir',
|
|
131
|
+
de: 'rufe ab',
|
|
132
|
+
pt: 'busque',
|
|
133
|
+
},
|
|
134
|
+
send: {
|
|
135
|
+
en: 'send',
|
|
136
|
+
ja: '送って',
|
|
137
|
+
es: 'envía',
|
|
138
|
+
ar: 'أرسل',
|
|
139
|
+
zh: '发送',
|
|
140
|
+
ko: '보내',
|
|
141
|
+
fr: 'envoie',
|
|
142
|
+
tr: 'gönder',
|
|
143
|
+
de: 'sende',
|
|
144
|
+
pt: 'envie',
|
|
145
|
+
},
|
|
146
|
+
go: {
|
|
147
|
+
en: 'go',
|
|
148
|
+
ja: '行って',
|
|
149
|
+
es: 've',
|
|
150
|
+
ar: 'اذهب',
|
|
151
|
+
zh: '前往',
|
|
152
|
+
ko: '이동해',
|
|
153
|
+
fr: 'va',
|
|
154
|
+
tr: 'git',
|
|
155
|
+
de: 'gehe',
|
|
156
|
+
pt: 'vá',
|
|
157
|
+
},
|
|
158
|
+
increment: {
|
|
159
|
+
en: 'increment',
|
|
160
|
+
ja: '増加して',
|
|
161
|
+
es: 'incrementa',
|
|
162
|
+
ar: 'زد',
|
|
163
|
+
zh: '增加',
|
|
164
|
+
ko: '증가시켜',
|
|
165
|
+
fr: 'incrémente',
|
|
166
|
+
tr: 'artır',
|
|
167
|
+
de: 'erhöhe',
|
|
168
|
+
pt: 'incremente',
|
|
169
|
+
},
|
|
170
|
+
decrement: {
|
|
171
|
+
en: 'decrement',
|
|
172
|
+
ja: '減少して',
|
|
173
|
+
es: 'decrementa',
|
|
174
|
+
ar: 'أنقص',
|
|
175
|
+
zh: '减少',
|
|
176
|
+
ko: '감소시켜',
|
|
177
|
+
fr: 'décrémente',
|
|
178
|
+
tr: 'azalt',
|
|
179
|
+
de: 'verringere',
|
|
180
|
+
pt: 'decremente',
|
|
181
|
+
},
|
|
182
|
+
take: {
|
|
183
|
+
en: 'take',
|
|
184
|
+
ja: '取って',
|
|
185
|
+
es: 'toma',
|
|
186
|
+
ar: 'خذ',
|
|
187
|
+
zh: '取走',
|
|
188
|
+
ko: '가져가',
|
|
189
|
+
fr: 'prends',
|
|
190
|
+
tr: 'al',
|
|
191
|
+
de: 'nimm',
|
|
192
|
+
pt: 'pegue',
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** Role marker translations for DSL text rendering */
|
|
197
|
+
const MARKERS: Record<string, Record<string, string>> = {
|
|
198
|
+
to: {
|
|
199
|
+
en: 'to',
|
|
200
|
+
ja: 'に',
|
|
201
|
+
es: 'a',
|
|
202
|
+
ar: 'إلى',
|
|
203
|
+
zh: '到',
|
|
204
|
+
ko: '에',
|
|
205
|
+
fr: 'à',
|
|
206
|
+
tr: 'a',
|
|
207
|
+
de: 'zu',
|
|
208
|
+
pt: 'a',
|
|
209
|
+
},
|
|
210
|
+
from: {
|
|
211
|
+
en: 'from',
|
|
212
|
+
ja: 'から',
|
|
213
|
+
es: 'de',
|
|
214
|
+
ar: 'من',
|
|
215
|
+
zh: '从',
|
|
216
|
+
ko: '에서',
|
|
217
|
+
fr: 'de',
|
|
218
|
+
tr: 'dan',
|
|
219
|
+
de: 'von',
|
|
220
|
+
pt: 'de',
|
|
221
|
+
},
|
|
222
|
+
into: {
|
|
223
|
+
en: 'into',
|
|
224
|
+
ja: 'に',
|
|
225
|
+
es: 'en',
|
|
226
|
+
ar: 'في',
|
|
227
|
+
zh: '到',
|
|
228
|
+
ko: '에',
|
|
229
|
+
fr: 'dans',
|
|
230
|
+
tr: 'a',
|
|
231
|
+
de: 'in',
|
|
232
|
+
pt: 'em',
|
|
233
|
+
},
|
|
234
|
+
on: {
|
|
235
|
+
en: 'on',
|
|
236
|
+
ja: 'で',
|
|
237
|
+
es: 'en',
|
|
238
|
+
ar: 'على',
|
|
239
|
+
zh: '在',
|
|
240
|
+
ko: '에',
|
|
241
|
+
fr: 'sur',
|
|
242
|
+
tr: 'da',
|
|
243
|
+
de: 'auf',
|
|
244
|
+
pt: 'em',
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const SOV_LANGUAGES = new Set(['ja', 'ko', 'tr']);
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Render a SemanticNode as DSL text in the target language.
|
|
252
|
+
* Produces the commanding form (imperative) of the command.
|
|
253
|
+
*/
|
|
254
|
+
export function renderLearn(node: SemanticNode, language: string): string {
|
|
255
|
+
const keyword = COMMAND_KEYWORDS[node.action]?.[language] ?? node.action;
|
|
256
|
+
const patient = extractRoleValue(node, 'patient') || '';
|
|
257
|
+
const destination = extractRoleValue(node, 'destination') || '';
|
|
258
|
+
const source = extractRoleValue(node, 'source') || '';
|
|
259
|
+
|
|
260
|
+
const parts: string[] = [];
|
|
261
|
+
|
|
262
|
+
if (SOV_LANGUAGES.has(language)) {
|
|
263
|
+
// SOV: target markers patient markers verb
|
|
264
|
+
if (destination) {
|
|
265
|
+
const marker = MARKERS.to[language] || '';
|
|
266
|
+
parts.push(marker ? `${destination}${marker}` : destination);
|
|
267
|
+
}
|
|
268
|
+
if (source) {
|
|
269
|
+
const marker = MARKERS.from[language] || '';
|
|
270
|
+
parts.push(marker ? `${source}${marker}` : source);
|
|
271
|
+
}
|
|
272
|
+
if (patient) parts.push(patient);
|
|
273
|
+
parts.push(keyword);
|
|
274
|
+
} else {
|
|
275
|
+
// SVO/VSO: verb patient marker target
|
|
276
|
+
parts.push(keyword);
|
|
277
|
+
if (patient) parts.push(patient);
|
|
278
|
+
if (destination) {
|
|
279
|
+
const marker = MARKERS.to[language] || '';
|
|
280
|
+
if (marker) parts.push(marker);
|
|
281
|
+
parts.push(destination);
|
|
282
|
+
}
|
|
283
|
+
if (source) {
|
|
284
|
+
const marker = MARKERS.from[language] || '';
|
|
285
|
+
if (marker) parts.push(marker);
|
|
286
|
+
parts.push(source);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return parts.filter(Boolean).join(' ');
|
|
291
|
+
}
|