From 06c84f92d3791278a80c1ab710e995535768612d Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Wed, 4 Nov 2026 23:35:25 +0400 Subject: [PATCH] Ignore Arabic short vowels and shadda in chat autocomplete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arabic catalog strings carry optional marks ("تتبّع"), but people type without them, so "تتبع" found no suggestion. Fold the marks out of both the draft and the candidates before comparing. --- src/voice/chatPreferences.js | 12 ++++++------ src/voice/chatPreferences.test.mjs | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/voice/chatPreferences.js b/src/voice/chatPreferences.js index b094624bb..e478620cd 100644 --- a/src/voice/chatPreferences.js +++ b/src/voice/chatPreferences.js @@ -22,6 +22,10 @@ export function bindChatPreferences({ ui, getDraft, setDraft }) { msg('Reset to globe view'), msg('Show earthquakes'), ].map((text) => t(text)); + // Arabic short vowels and shadda are optional in writing: typing "تتبع" + // still finds "تتبّع". + const fold = (text) => + text.toLowerCase().replace(/[\u064B-\u065F\u0670]/g, ''); try { const stored = JSON.parse(localStorage.getItem(key)); if (typeof stored?.quick === 'boolean') quick = stored.quick; @@ -31,18 +35,14 @@ export function bindChatPreferences({ ui, getDraft, setDraft }) { function render() { const draft = getDraft(); if (historyIndex >= 0 && draft !== recent[historyIndex]) resetHistory(); - const query = draft.trim().toLowerCase(); + const query = fold(draft.trim()); const groups = []; if (query) { const candidates = [...recent, ...(quick ? quickCommands() : [])]; groups.push({ label: t('Suggestions'), items: [...new Set(candidates)] - .filter( - (text) => - text.toLowerCase().includes(query) && - text.toLowerCase() !== query, - ) + .filter((text) => fold(text).includes(query) && fold(text) !== query) .slice(0, 4), }); } else { diff --git a/src/voice/chatPreferences.test.mjs b/src/voice/chatPreferences.test.mjs index 916fc3342..28dcdefcb 100644 --- a/src/voice/chatPreferences.test.mjs +++ b/src/voice/chatPreferences.test.mjs @@ -1,5 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; +import { setLocale } from '../i18n/index.js'; import { bindChatPreferences } from './chatPreferences.js'; import { createTestVoiceUi } from '../testSupport/voiceUi.mjs'; @@ -99,3 +100,21 @@ test('editing a recalled message ends recall, and sending resets it', (t) => { press('ArrowUp'); assert.equal(field.value, 'Show live flights now'); }); + +test('autocomplete ignores Arabic short vowels and shadda', (t) => { + setLocale('ar'); + t.after(() => setLocale('en')); + const ui = createTestVoiceUi(); + let draft = ''; + const prefs = bindChatPreferences({ ui, getDraft: () => draft }); + t.after(() => prefs.destroy()); + const suggested = (text) => { + draft = text; + prefs.render(); + return ui.state.chat.suggestions.flatMap((group) => group.items); + }; + // The quick command is written "تتبّع", with a shadda. + assert.deepEqual(suggested('تتبع'), ['اعرض تتبّع الرحلات المباشر']); + prefs.remember('انتقل إلى طوكيو'); + assert.deepEqual(suggested('طُوكيو'), ['انتقل إلى طوكيو']); +});