Skip to content

Commit b585e56

Browse files
committed
more language support - choosing of layouts
1 parent df27880 commit b585e56

1 file changed

Lines changed: 81 additions & 9 deletions

File tree

scripts/keyboard/replace-keyboard-layout.js

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,14 @@ async function main() {
8282
.usage('<gridset> [options]')
8383
.argument('<gridset>', 'Path to .gridset file')
8484
.option('--page-name <substring>', 'Only update pages whose name includes this substring')
85-
.option('--layout <layoutId>', 'Target keyboard layout id', 'ar-arabic-101')
85+
.option('--all-letter-pages', 'Update all pages containing Action.Letter commands')
86+
.option('--layout <layoutId>', 'Target keyboard layout id')
87+
.option('--target-lang <code>', 'Pick target layout by language prefix (example: ar, en)')
88+
.option(
89+
'--target-layout-index <number>',
90+
'1-based index when --target-lang returns multiple layouts'
91+
)
92+
.option('--list-target-layouts', 'List layouts matching --target-lang and exit')
8693
.option('--source-layout <layoutId>', 'Source keyboard layout id', 'en-us')
8794
.option('--output <path>', 'Output .gridset path (default: add -keyboard-replaced)')
8895
.option('--flip-keys-for-grid-rl', 'Mirror key positions before mapping')
@@ -97,47 +104,107 @@ async function main() {
97104
return;
98105
}
99106

100-
if (!options.pageName) {
101-
console.error('Error: --page-name is required to avoid accidental global changes.');
107+
if (!options.pageName && !options.allLetterPages) {
108+
console.error('Error: provide --page-name <substring> or --all-letter-pages.');
102109
process.exit(1);
103110
}
104111

112+
if (options.pageName && options.allLetterPages) {
113+
console.log('Both --page-name and --all-letter-pages were provided; using --all-letter-pages.');
114+
}
115+
105116
const outputPath =
106117
options.output || gridsetPath.replace(/\.gridset$/i, '-keyboard-replaced.gridset');
107118

108119
let loadKeyboard;
120+
let getAvailableLayouts;
109121
let extractLayers;
110122
try {
111-
({ loadKeyboard, extractLayers } = require('worldalphabets'));
123+
({ loadKeyboard, getAvailableLayouts, extractLayers } = require('worldalphabets'));
112124
} catch (error) {
113125
console.error('Missing dependency: worldalphabets');
114126
console.error('Install it for this script with: npm install --no-save worldalphabets');
115127
process.exit(1);
116128
}
117129

130+
let targetLayoutId = options.layout || null;
131+
if (!targetLayoutId && options.targetLang) {
132+
const languageCode = String(options.targetLang).trim().toLowerCase();
133+
const layouts = await getAvailableLayouts();
134+
const matchingLayouts = layouts
135+
.filter((id) => typeof id === 'string')
136+
.filter((id) => id.toLowerCase().startsWith(`${languageCode}-`))
137+
.sort((a, b) => a.localeCompare(b));
138+
139+
if (matchingLayouts.length === 0) {
140+
console.error(`No keyboard layouts found for --target-lang ${languageCode}`);
141+
process.exit(1);
142+
}
143+
144+
if (options.listTargetLayouts) {
145+
console.log(`Layouts for ${languageCode}:`);
146+
matchingLayouts.forEach((layoutId, index) => {
147+
console.log(`${index + 1}. ${layoutId}`);
148+
});
149+
return;
150+
}
151+
152+
if (options.targetLayoutIndex !== undefined) {
153+
const parsedIndex = parseInt(String(options.targetLayoutIndex), 10);
154+
if (Number.isNaN(parsedIndex) || parsedIndex < 1 || parsedIndex > matchingLayouts.length) {
155+
console.error(
156+
`Invalid --target-layout-index ${options.targetLayoutIndex}. Valid range: 1-${matchingLayouts.length}`
157+
);
158+
process.exit(1);
159+
}
160+
targetLayoutId = matchingLayouts[parsedIndex - 1];
161+
} else {
162+
targetLayoutId = matchingLayouts[0];
163+
console.log(
164+
`Using first layout for ${languageCode}: ${targetLayoutId} (use --target-layout-index to select another)`
165+
);
166+
}
167+
} else if (targetLayoutId && options.targetLang) {
168+
console.log('Both --layout and --target-lang were provided; using --layout.');
169+
} else if (options.listTargetLayouts) {
170+
console.error('Error: --list-target-layouts requires --target-lang.');
171+
process.exit(1);
172+
}
173+
174+
if (!targetLayoutId) {
175+
targetLayoutId = 'ar-arabic-101';
176+
console.log(`No target layout provided. Using default: ${targetLayoutId}`);
177+
}
178+
118179
console.log('Loading layouts...');
119180
const sourceLayout = await loadKeyboard(options.sourceLayout);
120-
const targetLayout = await loadKeyboard(options.layout);
181+
const targetLayout = await loadKeyboard(targetLayoutId);
121182
const { layers: sourceLayers, charToCodes } = buildLayerMaps(sourceLayout, extractLayers);
122183
const { layers: targetLayers } = buildLayerMaps(targetLayout, extractLayers);
123184
const positionMap = buildPositionMap(sourceLayout);
124185

125186
console.log('Loading gridset...');
126187
const zip = new AdmZip(gridsetPath);
127188
const entries = zip.getEntries();
128-
const nameFilter = String(options.pageName).toLowerCase();
189+
const useAllLetterPages = Boolean(options.allLetterPages);
190+
const nameFilter = useAllLetterPages ? '' : String(options.pageName).toLowerCase();
129191
const parser = new XMLParser({ ignoreAttributes: false });
130-
const builder = new XMLBuilder({ ignoreAttributes: false, suppressEmptyNode: true });
192+
const builder = new XMLBuilder({
193+
ignoreAttributes: false,
194+
suppressEmptyNode: true,
195+
suppressBooleanAttributes: false
196+
});
131197

132198
let pagesMatched = 0;
199+
let pagesWithLetterButtons = 0;
133200
let buttonsUpdated = 0;
134201
let buttonsSkipped = 0;
135202

136203
for (const entry of entries) {
137204
if (entry.isDirectory) continue;
138205
if (!entry.entryName.startsWith('Grids/') || !entry.entryName.endsWith('/grid.xml')) continue;
139206
const pageName = entry.entryName.slice('Grids/'.length, -'/grid.xml'.length);
140-
if (!pageName.toLowerCase().includes(nameFilter)) continue;
207+
if (!useAllLetterPages && !pageName.toLowerCase().includes(nameFilter)) continue;
141208

142209
pagesMatched += 1;
143210

@@ -154,6 +221,7 @@ async function main() {
154221
}
155222

156223
let gridUpdated = 0;
224+
let gridHasLetterButtons = false;
157225

158226
for (const cell of cells) {
159227
const content = cell.Content || cell.content;
@@ -171,7 +239,7 @@ async function main() {
171239
}
172240
if (!targetCommand) continue;
173241

174-
const params = asArray(targetCommand.Parameter);
242+
const params = asArray(targetCommand.Parameter || targetCommand.parameter);
175243
let letterParam = null;
176244
for (const param of params) {
177245
const key = String(getAttr(param, ['@_Key', '@_key']) || '').toLowerCase();
@@ -181,6 +249,7 @@ async function main() {
181249
}
182250
}
183251
if (!letterParam) continue;
252+
gridHasLetterButtons = true;
184253

185254
const original = normalizeChar(getAttr(letterParam, ['#text']) || '');
186255
if (!original || original.length !== 1) {
@@ -228,13 +297,16 @@ async function main() {
228297
buttonsUpdated += 1;
229298
}
230299

300+
if (gridHasLetterButtons) pagesWithLetterButtons += 1;
301+
231302
if (gridUpdated > 0) {
232303
const rebuilt = builder.build(parsed);
233304
zip.updateFile(entry.entryName, Buffer.from(rebuilt, 'utf8'));
234305
}
235306
}
236307

237308
console.log(`Pages matched: ${pagesMatched}`);
309+
console.log(`Pages with Action.Letter buttons: ${pagesWithLetterButtons}`);
238310
console.log(`Buttons updated: ${buttonsUpdated}`);
239311
console.log(`Buttons skipped: ${buttonsSkipped}`);
240312

0 commit comments

Comments
 (0)