-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Per-extension language preferences #258
Comments
I have had this similar issue with the i18n library. This caused use to have to write a custom solution that manually fetchs locales based on the user's selection. This would be a greatly welcomed feature in web extensions for my team and I. |
Cool idea! |
browser.i18n.getMessage()
Today, Google publishes a video on Android YouTube channel: Per-app language preferences. I recommend everyone to watch this video in its entirety, which is only 5 minutes. This video is my final purpose for this issue (just replace Android with Browser, and replace app with extension). So I changed the title to "Per-extension language preferences". If browsers support Further more, browsers can supply a built-in language menu for users, like below: And supply a related api for developers to integrate this function in their language select menu in extension. For example: |
browser.i18n.getMessage()
I like the idea of being able to call Comparing with |
Yes, I implemented this solution before. First, async read user's language preference from |
Yes, such a solution would be valuable. |
I also prefer the |
The original comment mentions that "if the extension would like to supply a language selecting menu in the extension settings, it can't use const locale = 'en_US';
const messages = await (await fetch(chrome.runtime.getURL(`/_locales/${locale}/messages.json`))).json()
const message = messages[message_name]; This does not seem too bad and overall this feature is trivially polyfillable even currently. The only caveat is that this workaround would be async while Also, turns out this exact idea was proposed back in 2016 (comment 5, code example it links to), so compatibility should be good.
Would this persist across browser restarts/extension reloads? What should happen when a locale is removed during the extension update? |
@bershanskiy you've forgot to process the "placeholders" :). The |
Sure, this is basic POC example. A better polyfil would also check for actual existence of the file, would provide a list of available languages, etc. Also, there are a few other considerations:
Yes, it is. However, it seems like making
That makes sense. But also it would make sense to make |
@bershanskiy As I said, the feature has workaround, but it is not trivial. This is why I made this proposal. I also explained it at comment-10 4 years ago. In my personal solution, I even move The other issues you mentioned, I think they are all specification/implementation details.
Here is an introduction to Android Per-app language preferences as reference. |
Just to clarify, I originally imagined this to be a solution for the common use-case when user wants to change a language:
Then when the extension re/starts, browser loads the selected language (or default if the selected doesn't exist anymore), fully async, blocking only the extension. I imagine it already works like this just the selected language doesn't come from the user preference. But now that I'm thinking about it, all this could be done by the browser without any extension interaction. Something like the extension keyboard shortcuts, just less hidden :). |
Yes, just like the picture I drew at #issuecomment-1236272054. Developers just use current i18n api without doing anything else (except update already opened extension pages). The new api is only used to integrate it with the developer-self-supplied language select menu. |
In order to express this proposal completely and clearly, I reorganized the content at the first post of this issue. |
One more nitpick about the proposal: should the language selection sync to user browser account (like |
Good question. Local or sync? I often encounter this problem in the development process. In other words, does the same person use different extension languages on different devices? My personal answer is: maybe (like dev/testers) , but most users would probably prefer sync. I think this is left to browsers to decide. There are some other similar settings in the browser, like the Pin status per extensions. |
Looks good @hanguokai! Some remarks: Per-language syntaxConsidering the browser needs to load one or more locale files to initialise i18n.getMessage, having an async initialiser method could be a solution to this. Something like the following:
@bershanskiy Using API namesTo better express what each API does, some different set of method names seems to make sense: If we do not want to lock the method names to extensions, we can rename Extension to Runtime. AsyncConsidering the stand from Google on sync/async code: It makes sense to make Remove custom-set-languageAs we already experienced issues with the behaviour of Custom language scopeIt is not yet clear in what parts of the UI browsers should use the per-extension language. Should the language also be changed for other parts of the UI like:
Language tagAs for language tag syntax. An underscore (_) has been used for folder structures only while the the hyphen (-) is the standard. Other APIs like Language labelsHow will browsers get the language labels for each language tag for First thought is to translate them in their own language as this is most likely understandable by the user. |
Would this also need to change the languages returned by the web |
@xeenon at the moment the navigator APIs are not dependent on the browser UI language. They are dependent on the acceptLanguages header / browser preference. So there seems no reason for the language of navigator APIs to be changed with this proposal. |
@carlosjeurissen My replies is below.
I see this is moved to #274
The name should follow existing naming conventions. In my option,
async is Ok for me.
I'm willing to listen to other people for these edge cases. My opinion is:
Developers should use the tags and labels returned by
IMO, |
Thanks for your replies @hanguokai ! My replies are below.
Correct. We concluded in the meeting it makes sense to split the issue in half and put the getMessage proposal in a separate issue as this could be implemented separately and has a higher chance of being implemented short term. We can continue discussion in #274. Would love to hear your feedback.
During the meeting today it seemed some members were confused about the intention of the method. And thought it would set the language of the whole browser, not just the extension scope. Considering this adding extension can add added clarity to what the method is doing.
My proposal was not to ditch The browser might want to do some work in figuring out if the language tag being passed is valid. That is why I think it makes sense to reject the promise with an error stating the passed language tag is invalid or unavailable, versus directly throwing an exception when calling the method.
Now you mention action badge text and extension contextMenus, it seems to make most sense to keep them as is, and let it be the extension developers responsibility to update the labels if the extensions language changes.
This would be good indeed. However, we should define what getAllLanguages should return and how it comes up with the names. That was what my original comment about language tag labels was about. |
@stevenmason At present, no substantive progress. This is one of the things that I am dissatisfied with. Maybe they just don't have the human resources to do it. In practice, developers currently have to abandon |
Thank you for the prompt response. I guess it's not a high priority if there is a work around. Even option 3 would be great and I assume it wouldn't be much work. |
I think there are two pieces here. Native Browser UIFrom the Chrome side, I don't think this is something we'd pursue. I think this type of UI is best handled by the extension itself.
Allowing Other Languages in browser.i18nThis, I readily agree, is a pain point in the API today. It always falls back to the language the user has selected for the browser and, as is called out here and in issue #274 , this may not be desirable for the user. I do think we should do something about this. That's most similar to option 3 here and I think is further captured and expanded upon in #274. I'm going to add the chrome-opposed label to this issue, because I don't think we're going to add UI for this in the near future. I am supportive of adding better API support for this, but that's separately tracked in #274. |
@rdcronin My reply is as follows. I wish you'd take a little more time to think about it. GoalThe goal of this proposal is to enhance the History
After the above iteration, this is what this proposal looks like now. Use CaseThe real user need is that users select a preferred language in the settings and save it. After that, each UI of the extension is displayed in that language, like the popup page, content scripts, notifications and other extension pages. APITo achieve this goal, and as a general feature, it is best to have the browser save this setting rather than having each extension save it itself. So the browser should provide APIs like If the browser does not provide this support, the extension code will become as follows: let languageResource = null;
async function getLanguageResource() {
if (!languageResource) {
// read setting from storage, here I think storage.sync is better than storage.local
let { lang } = await browser.storage.sync.get({ lang: "en_US" });
languageResource = await initializeLanguageResource(lang);
}
return languageResource;
}
// in various places of building UI
async function buildX() {
let div = document.createElement('div');
let resource = await getLanguageResource();
div.textContent = resource.getMessage('title');
}
async function buildY() {
let button = document.createElement('button');
let resource = await getLanguageResource();
button.textContent = resource.getMessage('buttonName');
} Note that this is just sample code, and concurrent initialization should be avoided in practice. If the browser supports // in various places of building UI
function buildX() {
let div = document.createElement('div');
div.textContent = browser.i18n.getMessage('title');
}
function buildY() {
let button = document.createElement('button');
button.textContent = browser.i18n.getMessage('buttonName');
} In addition, this API is also beneficial for performance optimization. The browser usually cache the default language resource after first loading it. But for API in #274 , the browser doesn't know whether to cache the resource, because that API may dynamically load one or more language resources anytime, anywhere. The Browser UIFirst of all, the browser's built-in UI support for language selection is optional, and the API I mentioned earlier is more important. But if the browser has built-in support for language selection, it has the following benefits:
In my initial idea, the user would right-click on the extension icon in the toolbar and then select the preferred language of the extension. Similar the following code by creating a context menu for the action. for (let {langCode, langName} of allLanguages) {
let context = {
id: `lang-${langCode}`,
title: langName,
contexts: ["action"]
};
chrome.contextMenus.create(context);
}
I18N is a general feature, not only for developers, but also for users. In this respect, the browser can play a bigger role.
Indeed. But the messages files provided by the extension means what languages the extension should support. Anyway, for extensions that already support multiple languages based on
For this proposal, the browser's built-in UI is optional, and I don't require the browser to must support it.
Websites and extensions are different. Websites don't have a unified API to support i18n, so browsers can't provide a unified UI for websites, but extensions can. |
Because the browser UI part is optional for this proposal, I removed the "chrome-opposed" label and added the "follow-up" label for further discussion. |
Thank you for the detailed response, @hanguokai ! I'm still not entirely on board that there's a major effective difference between the two versions of code to create the HTML that were included -- in all likelihood, we'd recommend any developers that wanted to support that to just write a wrapper around i18n like getMessageInPreferredLanguage(), which abstracts out the getLanguageResource(). There would be a difference between one version being sync (ish, due to browser implementation details) and another being async, but I think in the majority of cases that developers are dynamically generating HTML like that, it would depend on other async data (e.g. retrieving other settings, retrieving user data, etc), so the difference becomes less impactful. The browser caching is an interesting point, but is also very dependent on browser implementation details, and I'd be hesitant to design (or introduce) APIs based on that behavior if we think it may change. However, when discussing this more with Oliver, I did remember another part of this that I don't think has been brought up here. In addition to setting the value returned by chrome.i18n.getMessage() (which could be worked around by allowing the extension to get a message from another language), we also allow embedding localized messages in other resources, such as CSS files. In Chrome, these are rewritten on-the-fly as they're fetched from the extension, using the user's current preferred locale. If the extension wanted to replicate this behavior with custom language preferences, they would need to dynamically generate these resources when they otherwise wouldn't. While I don't think the additional wrapping in JS adds significant complexity, I do think that requiring styles to be dynamically generated through JS instead of packaged as CSS is a significant burden. I think that's enough to convince me that this is worthwhile doing -- I agree the other use cases are valuable and it's a nice-to-have for extension developers, and combined with that, I think there's sufficient justification for this. I'm still opposed to introducing browser UI for this at this time. While I understand the utility and desire behind it, I think we should hold off on anything that requires standardizing UI between different browsers. With this, I think the set of API methods we would need would be |
Thank you for discussing this issue during the meeting(2024/05/09). This proposal consists of two parts: the API part and the Browser UI part. Now, all browsers are supportive of the API part. I will add corresponding labels. Next, we hope to see a formal proposal that includes the behavior of the API and some details. I plan to write a formal proposal in the coming weeks. We can discuss the details in the proposal. |
If getCurrentLanguage is added, its format will be supported by
I agree browser UI implementation for changing languages should not block the implementation of the API (there's nothing preventing an UI to be created for this later on) |
@xPaw currently the extension language will always match the browser language which can be fetched using |
I think yes, because both this api and
I think this is not true. For example, the language of the browser UI is English,
|
You are right indeed. Messages from getMessage() would only be in the language of getUILanguage if the extension has messages in this language defined. Just double checked and i18n.getMessage('@@ui_locale') also returns the UI language (yet in Chrome it returns a This gives motivation to already have an
With the above comment I was referring to the current state of affairs. Not a hypothetical future situation in which |
Yes, |
It should indeed probably be separate from #569. My suggestion would be to create a joined proposal for the methods |
Thanks for suggestion. However, in this proposal (#comment-0), it already contains |
The point I was trying to make is that |
This proposal only has 4 simple and intuitive methods, which is not a lot. In addition, proposal and implementation are two different things, no one requires all implementations at once. It is common in Web standards. So, don't worry, I'll get it done soon. |
I just create a formal version of the proposal at #641 |
I was wondering if a simpler approach (if not already discussed) would work. Browsers get the i18n.getMessage() based on default_locale which falls back to the browser locale. Since extensions would need to display ONE locale at a time, per-extension language preferences can be achieved by setting the The fallback process would be: setDefaultLocale -> default_locale -> browser locale browser.i18n.setDefaultLocale(
'en', // string
)
browser.i18n.unsetDefaultLocale() // revert back to "default_locale"
// or
browser.i18n.clearDefaultLocale() // revert back to "default_locale" Another similar method could be a new property e.g. extension locale. Everything else would remain as it was and browser uses the new "extension_locale". browser.i18n.setExtensionLocale(
'en', // string
)
browser.i18n.unsetExtensionLocale()
// or
browser.i18n.clearExtensionLocale() The fallback process would be: extension_locale -> default_locale -> browser locale Switching LocaleSwitching locale would be applied when refreshing the page, without the need for a live-replacement or additional change listeners. Missing LocaleIn case a locale is set that is not available, the next fallback is used without breaking errors. i18n.getAllLanguages()
|
Update: In order to express it completely and clearly, I reorganized the proposal and edited it many times after 2022-09-12.
Summary
Currently,
browser.i18n
only display one default language to users. Users can't change the language to other extension supported languages independently. Developers need to use their own solutions to provide users with multiple language select menu.It makes sense for an app to use another language independently of the operating system. For example, Android supports per-app language preferences (here is its docs and video). The same goes for extensions. This proposal brings per-app language preferences to browser extensions.
Tracking bugs: Chromium-1365283 , Firefox-1791356
Main Content
1. Browser built-in supply a language select menu per extension
Since this is a general purpose feature for all users and extensions, browser built-in support is best.
If browsers built-in support a language select menu per extensions, all developers, all existing extensions and users can get this benefit right away. Ideally, developers just use current i18n api without doing anything. Another benefit is that it's much easier for developers to test i18n.
2. New APIs for developers
Ideally, developers just use current i18n api without doing anything if there is a browser-supplied language select menu. The new api is only used to integrate this feature with the developer-supplied language select menu. For example, in the extension's options page, developers use
get/setCurrentLanguage
andgetAllLanguages
to create a language select menu for users.i18n.setCurrentLanguage(code)
is persistent. It is a setting per extensions which saved by browsers. If the extension remove a language in the new version and current language is that, then browser fall back to the default language.code
is standard language code, like 'en-US', not 'en_US'(folder name).How to get language display names? Use Intl.DisplayNames, for example:
After changing the language, the browser triggers a
onLanguageChanged
event. This event is useful for updating UI for already opened extension pages and other UI parts like badgeText, badgeTitle and context menu.3. Another New API (optional for implementation)
At present,
i18n.getMessage()
doesn't allow specifying a different language. I suggest add a new property to specify a language in the options parameter(the 3rd parameter which already support a "escapeLt" property). Maybe it is useful for some developers or some use cases.Related References
https://developer.chrome.com/docs/extensions/reference/i18n/
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n
https://crbug.com/660704
#252
The text was updated successfully, but these errors were encountered: