Skip to content

feat: localize JS keyboard search 🗺️#658

Draft
darcywong00 wants to merge 10 commits intomasterfrom
feat/search-i18n
Draft

feat: localize JS keyboard search 🗺️#658
darcywong00 wants to merge 10 commits intomasterfrom
feat/search-i18n

Conversation

@darcywong00
Copy link
Contributor

@darcywong00 darcywong00 commented Feb 12, 2026

For #384

Most of the PHP keyboard search pages can be localized.
This follows on by allowing the JS keyboard search to also be localized.

Off-the-shelf, i18n-next looked really good, but I opted for something that didn't need a framework:
https://medium.com/@mihura.ian/translations-in-vanilla-javascript-c942c2095170

Some of the changes

The JSON files make it easy to

  • add strings
  • add formatted parameters to strings
    For example:
t('pageNumberOfTotalPages', {pageNumber: res.context.pageNumber, totalPages: res.context.totalPages})

I had to add search.js and i18n.js as modules in order to import the JSON files.

Sample screenshots

french sil french more

Test-bot: skip

@darcywong00 darcywong00 added this to the A19S22 milestone Feb 12, 2026
@darcywong00 darcywong00 requested a review from mcdurdin February 12, 2026 04:12
@keymanapp-test-bot
Copy link

User Test Results

Test specification and instructions

User tests are not required

@keyman-server keyman-server modified the milestones: A19S22, A19S23 Feb 13, 2026
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to .mjs and then we can treat it as a module by file ext.

Comment on lines 70 to 75
if (str_contains($jsFile, '/js/i18n/') || str_contains($jsFile, 'search.js')) { ?>
<script src='<?=$jsFile?>' type='module' ></script>
<?php } else { ?>
<script src='<?=$jsFile?>'></script>
<?php } ?>
<?php }
} ?>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like this kind of specialization -- it is hard to maintain because it is putting test for specific files into the framework code.

It'd be better to make that determination on the basis of the file extension being .mjs:

      if (str_ends_with($jsFile, '.mjs')) { ?>

But overall, better to just use echo:

Suggested change
if (str_contains($jsFile, '/js/i18n/') || str_contains($jsFile, 'search.js')) { ?>
<script src='<?=$jsFile?>' type='module' ></script>
<?php } else { ?>
<script src='<?=$jsFile?>'></script>
<?php } ?>
<?php }
} ?>
$jsFileType = str_ends_with($jsFile, '.mjs') ? "type='module'" : "";
echo "<script src='$jsFile' $jsFileType></script>
}
?>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. GH had an error trying to apply the suggestion, so I manually updated in e87862c

{
"resultOne": {
"text": "resultado",
"crowdinContext": "1 keyboard result found"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the crowdinContext being repeated for each language -- this is a lot of busy noise.

Couldn't we just have an object map?

{ 
  "resultOne": "resultado", 
  ... 
}

Comment on lines +7 to +9
import translationEN from './en.json' with { type: 'json' };
import translationES from './es.json' with { type: 'json' };
import translationFR from './fr.json' with { type: 'json' };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not scalable to many translations. We need to use dynamic imports, and see also my comment on en.json - the translations need to be scoped.

@@ -0,0 +1,54 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't scale, because it's just one json file, so strings are going to get all muddled together. We are going to need to split per .js file I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are going to need to split per .js file I think?

So the strings relating to search.mjs would go in a folder like this?
/cdn/dev/js/i18n/search/en.json, es.json, etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we wanting to dynamically load strings in a pattern similar to PHP (Locale.php)

where strings = [domain][language][key].... ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the strings relating to search.mjs would go in a folder like this?
/cdn/dev/js/i18n/search/en.json, es.json, etc?

Yes

Are we wanting to dynamically load strings in a pattern similar to PHP (Locale.php)

where strings = [domain][language][key].... ?

Something like that yes, with dynamic imports

static objNavigate(obj, path){
var aPath = path.split('.');
try {
return aPath.reduce((a, v) => a[v].text, obj);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't going to work with a.b.c is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. Updated to fix the nesting so this now works

objNavigate({a: {b: {c: {text:123}}}}, "a.b.c") // returns 123

if (!I18n.translations[language].hasOwnProperty(key)) {
// key is missing for current language
console.warn(`key '${key}' missing in '${language}' translations`);
return '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fallback to en string would be better because we know we have those.

darcywong00 and others added 5 commits February 16, 2026 07:58
</div>

<script src="<?= cdn('legacy-keyboard-search/search.js'); ?>"></script> No newline at end of file
<script src="<?= cdn('legacy-keyboard-search/search.mjs'); ?>" type="module"></script> No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't think we should touch the legacy keyboard search should we? Just the current version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants