Skip to content
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

Open
hanguokai opened this issue Aug 19, 2022 · 58 comments
Open

Per-extension language preferences #258

hanguokai opened this issue Aug 19, 2022 · 58 comments
Labels
i18n-tracker Group bringing to attention of Internationalization, or tracked by i18n but not needing response. proposal Proposal for a change or new feature supportive: chrome Supportive from Chrome supportive: firefox Supportive from Firefox supportive: safari Supportive from Safari topic: localization

Comments

@hanguokai
Copy link
Member

hanguokai commented Aug 19, 2022

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

Screen Shot 2022-09-04 at 14 30 17

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

/**
 * get the current language used by i18n.getMessage()
 * return the language tag,  e.g. en-US, zh-CN.
 */
i18n.getCurrentLanguage() => code: String.

/**
 * set a new language used in this extension.
 * if code is null, revert to the default state(follow the browser UI language).
 * if code is not valid, reject it.
 * return a Promise, resolved when the operation is complete.
 */
i18n.setCurrentLanguage(code: String) => Promise<void>

/**
 * get all languages that supported by this extensions.
 * return a Promise, resolved with an array of language tags.
 */
i18n.getAllLanguages() => code_array: Promise<Array<String>>

/**
 * After i18n changed to a new language, the browser triggers a language changed event.
 * callback is (new_language_code: String) => void
 */
i18n.onLanguageChanged.addListener(callback)

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 and getAllLanguages 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:

const currentLanguage = i18n.getCurrentLanguage();
const displayName = new Intl.DisplayNames([currentLanguage], 
      { type: 'language' , languageDisplay: 'standard' });

const allLanguages = await i18n.getAllLanguages();
for (let code of allLanguages) {
    let name = displayName.of(code)); // language display name for code
    // use code and name to create a language select
}

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.

i18n.onLanguageChanged.addListener(function(newCode) {
    // update extension page
    button.textContent = i18n.getMessage(buttonLabel);

    // update extension UI parts
    action.setTitle({title: i18n.getMessage(title)});
    action.setBadgeText({text: i18n.getMessage(text)});
    contextMenus.update(...);
});

3. Another New API (optional for implementation)

i18n.getMessage(messageName, substitutions?,  {language: "langCode"})

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

@jcblw
Copy link

jcblw commented Aug 19, 2022

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.

@xeenon
Copy link
Collaborator

xeenon commented Aug 23, 2022

Cool idea!

@xeenon xeenon added supportive: safari Supportive from Safari proposal Proposal for a change or new feature labels Sep 1, 2022
@xeenon xeenon changed the title allow specifying a different language for browser.i18n.getMessage() allow specifying a different language for browser.i18n.getMessage() Sep 1, 2022
@xeenon xeenon added the agenda Discuss in future meetings label Sep 1, 2022
@hanguokai
Copy link
Member Author

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 browser.i18n.getMessage(a-specific-language), then developers can use this API to implement a language select menu for users (save user's preference in storage). Otherwise, developers have to write their own solution.

Further more, browsers can supply a built-in language menu for users, like below:
Screen Shot 2022-09-04 at 14 30 17

And supply a related api for developers to integrate this function in their language select menu in extension. For example:
i18n.getCurrentLanguage() / i18n.setCurrentLanguage(), i18n.onCurrentLanguageChanged. In this way, user's language preference is saved by browser, not in storage by developers.

@hanguokai hanguokai changed the title allow specifying a different language for browser.i18n.getMessage() Per-extension language preferences Sep 4, 2022
@Juraj-Masiar
Copy link

I like the idea of being able to call i18n.setCurrentLanguage and browser would then serve locals from a different file.

Comparing with browser.i18n.getMessage + supplying language code, where one would have to read the "user-selected" language code from somewhere, probably using some async method, which would make the whole call async or one would have to await some "language init" before any language related operations starts, which is a terrible idea.

@hanguokai
Copy link
Member Author

I like the idea of being able to call i18n.setCurrentLanguage and browser would then serve locales from a different file.

Comparing with browser.i18n.getMessage + supplying language code, where one would have to read the "user-selected" language code from somewhere, probably using some async method, which would make the whole call async or one would have to await some "language init" before any language related operations starts, which is a terrible idea.

Yes, I implemented this solution before. First, async read user's language preference from browser.storage.local, then do other things. i18n.setCurrentLanguage(lang) is more easier than i18n.getMessage(lang_code). So I prefer i18n.setCurrentLanguage(lang) or completely let user use browser's extension-language-select UI setting. But i18n.getMessage(lang_code) may be useful for some developers or some use cases. If possible, hopefully both will be supported.

@yankovichv
Copy link

Yes, such a solution would be valuable.

@xeenon
Copy link
Collaborator

xeenon commented Sep 8, 2022

I also prefer the i18n.setCurrentLanguage() proposal too. Avoiding local storage each time a string is needed is good.

@bershanskiy
Copy link
Member

The original comment mentions that "if the extension would like to supply a language selecting menu in the extension settings, it can't use i18n.getMessage() and has to use other workarounds." Out of curiosity, I wrote one such workaround and it boils down to loading the locale JSON from extension files via fetch or XmlHttpRequest, parsing it, and taking the required message:

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 i18n.setCurrentLanguage() is synchronous.

Also, turns out this exact idea was proposed back in 2016 (comment 5, code example it links to), so compatibility should be good.

I also prefer the i18n.setCurrentLanguage() proposal too. Avoiding local storage each time a string is needed is good.

Would this persist across browser restarts/extension reloads? What should happen when a locale is removed during the extension update?

@Juraj-Masiar
Copy link

@bershanskiy you've forgot to process the "placeholders" :).
Also the workaround is still async, so all your code that would like to use translations now needs to await this.

The i18n.setCurrentLanguage should for sure be persisting. And removing lang would simply fallback to default lang, like it already does.

@bershanskiy
Copy link
Member

@bershanskiy you've forgot to process the "placeholders" :).

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:

  1. What language codes are to be used? E.g., en_US (like current folder name), en-us, en-US (like navigator.language), etc.
  2. Is there an equivalent for langaugechange event?

Also the workaround is still async, so all your code that would like to use translations now needs to await this.

Yes, it is. However, it seems like making i18n.getMessage sync and able to load strings from any locale would make it load a lot of useless data or be racy with i18n.setCurrentLanguage or might be needlessly blocking (while browser loads the file for the first time).

The i18n.setCurrentLanguage should for sure be persisting. And removing lang would simply fallback to default lang, like it already does.

That makes sense. But also it would make sense to make i18n.setCurrentLanguage async (like setBadgeText() and similar APIs) and make it throw exceptions on invalid languages or missing language files.

@hanguokai
Copy link
Member Author

hanguokai commented Sep 10, 2022

@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 locale/messages.json into JS file, so it can be access in sync way. I have published 18 extensions, counting the extensions for self-use only, there are nearly 30 extensions. It's too cumbersome to use this i18n workaround for every extension. If browsers built-in support a language-select menu per extensions, all developers, existing extensions and users can get this benefit without doing anything. Another benefit is that it's much easier for developers to test i18n.

The other issues you mentioned, I think they are all specification/implementation details.

  • language code: There may be some historical reasons for the inconsistency. The browser could use current name(folder name) or ignore case and hyphen. Let's leave it to the browser engineers for further discussion.
  • A language change event: Yes, see my previous post. This is useful for changing UI language for already opened extension pages.
  • i18n.setCurrentLanguage is persistent: Yes and must. It is a setting per extensions which saved by browsers.
  • i18n.setCurrentLanguage is sync or async: For me, I accept both.
  • i18n.getMessage(lang): browsers don't need to load all locales messages, it can only load one locale messages at the first time the language use (most of the time, only one is used).

Here is an introduction to Android Per-app language preferences as reference.

@Juraj-Masiar
Copy link

Just to clarify, I originally imagined this to be a solution for the common use-case when user wants to change a language:

  1. extension would present list of languages
  2. user clicks one
  3. it would call the i18n.setCurrentLanguage (async, since it should store it somewhere)
  4. event could fire, or one could call runtime.reload() to make sure things are changed everywhere, (implementing a change event in all pages / modules could be too much)

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 :).

@hanguokai
Copy link
Member Author

all this could be done by the browser without any extension interaction

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.

@hanguokai
Copy link
Member Author

In order to express this proposal completely and clearly, I reorganized the content at the first post of this issue.

@bershanskiy
Copy link
Member

One more nitpick about the proposal: should the language selection sync to user browser account (like storage.sync) or not (like storage.local)? Above discussion mentioned "local sotorage", but that could be accidental.

@hanguokai
Copy link
Member Author

One more nitpick about the proposal: should the language selection sync to user browser account (like storage.sync) or not (like storage.local)?

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.

@carlosjeurissen
Copy link
Contributor

carlosjeurissen commented Sep 15, 2022

Looks good @hanguokai! Some remarks:

Per-language syntax

Considering 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:

i18n.createLanguageInstance('pt-BR').then((languageInstance) => {
    let someMessage = languageInstance.getMessage('some_id');
  });

@bershanskiy Using fetch is not really a doable alternative as it doesn't handle fallbacks to less specific language tags and English.

API names

To better express what each API does, some different set of method names seems to make sense:
i18n.setCurrentLanguage --> i18n.setExtensionLanguage
i18n.getCurrentLanguage --> i18n.getCurrentExtensionLanguage
i18n.getAllLanguages --> i18n.getSupportedExtensionLanguages

If we do not want to lock the method names to extensions, we can rename Extension to Runtime.

Async

Considering the stand from Google on sync/async code:
Extension APIs are almost always asynchronous. Anything that cannot be resolved directly in the renderer must be asynchronous, and even if something can be done in the renderer today, it should frequently be asynchronous to prevent future breakage if we move something to the browser process.

It makes sense to make i18n.setCurrentLanguage async as @bershanskiy proposed as it needs to set storage and having it async has no clear downside. Same goes for i18n.getAllLanguages.

Remove custom-set-language

As we already experienced issues with the behaviour of undefined (#263), I suggest we clearly define the behaviour with i18n.setCurrentLanguage. First thought it is to accept null or a string, else throw a direct exception. If the string is not a supported or valid language tag, the returned promise can be rejected.

Custom language scope

It 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:

  • inline options_ui settings pages
  • action badge text
  • contextMenus
  • Native dialogs (Window.alert)
  • @@ui_locale predefined message, see Chrome i18n Docs
  • manifest and file replacements using the __MSG_messagename__ syntax.

Language tag

As for language tag syntax. An underscore (_) has been used for folder structures only while the the hyphen (-) is the standard. Other APIs like i18n.getUILanguage already return language tags with hyphens (-) so lets stick to that.

Language labels

How will browsers get the language labels for each language tag for i18n.getAllLanguages? In what language should the languages labels be? Should it be the browser UI language? The current extension language? Or be translated in its own language?

First thought is to translate them in their own language as this is most likely understandable by the user.

@xeenon
Copy link
Collaborator

xeenon commented Sep 15, 2022

Would this also need to change the languages returned by the web navigator APIs?

@carlosjeurissen
Copy link
Contributor

carlosjeurissen commented Sep 15, 2022

@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.

@hanguokai
Copy link
Member Author

@carlosjeurissen My replies is below.

Per-language syntax

I see this is moved to #274

API names

The name should follow existing naming conventions. In my option, browser.i18n namespace is completely around the internationalization for the extension itself, so I think there is no need to add "Extension" in the name.

Async

async is Ok for me.

custom-set-language

i18n.setCurrentLanguage is the core of these apis. The implementation should be consistent on all browsers. If the input is not correct, it should throw error. If developers use the tags returned by i18n.getAllLanguages, it should always be correct.

Custom language scope

I'm willing to listen to other people for these edge cases. My opinion is:

  • inline options_ui settings pages: Yes
  • action badge text: Yes. Because developers can setBadgeText by i18n.getMessage
  • contextMenus: Yes. Because it is created by contextMenus.create, which can specify the title by i18n.getMessage
  • Native dialogs (Window.alert): No. It should follow the browser UI language.
  • @@ui_locale predefined message, see Chrome i18n Docs: Yes
  • manifest and file replacements using the MSG_messagename syntax.: Yes

Language tag and label

Developers should use the tags and labels returned by i18n.getAllLanguages , that is [{ name: name_for_UI, code: tag_for_Value }, ……].

Would this also need to change the languages returned by the web navigator APIs?

IMO, navigator.language(s) should be unchanged.

@carlosjeurissen
Copy link
Contributor

Thanks for your replies @hanguokai ! My replies are below.

Per-language syntax

I see this is moved to #274

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.

The name should follow existing naming conventions. In my option, browser.i18n namespace is completely around the internationalization for the extension itself, so I think there is no need to add "Extension" in the name.

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.

i18n.setCurrentLanguage is the core of these apis. The implementation should be consistent on all browsers. If the input is not correct, it should throw error. If developers use the tags returned by i18n.getAllLanguages, it should always be correct.

My proposal was not to ditch i18n.setCurrentLanguage. It is indeed core to this set of APIs. My proposal here was to clearly define what should happen when passing null or undefined, or an empty string to setCurrentLanguage. When passing null, it for sure should fall back to the browser UI Language. That is why I added the header remove custom-set-language.

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.

Custom language scope
During the meeting we talked about some of the potential difficulties when it comes to some of these areas. For example, right clicking the extension icon shows not just native extension items but also items from the browser. Like "Remove extension". I'm not so sure we want them to be translated in a different language.

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.

Developers should use the tags and labels returned by i18n.getAllLanguages , that is [{ name: name_for_UI, code: tag_for_Value }, ……].

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.

@hanguokai
Copy link
Member Author

@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 browser.i18n api to support this feature (i.e. use other methods or frameworks), or workaround it, or mix these things. And the purpose of this proposal is to make these simple.

@stevenmason
Copy link

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.

@rdcronin
Copy link
Contributor

I think there are two pieces here.

Native Browser UI

From 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.

  • It's a per-extension option, similar to other options that are presented and handled by the extension. Chrome has some of these, but the vast majority of these options are ones that should not or cannot be delegated to the extension (such as permission-related settings).
  • The browser may not have perfect insight into which languages the extension supports. We can look at the messages file, but that may not give an accurate summary (due to e.g. differences in how we display languages for different language codes, whether the extension has each message in each language, etc). It would be a bad user experience if the user selects a new language because the browser thinks it should be supported and nothing changes.
  • There doesn't seem to be significant benefit to having this be a browser-provided option over an extension-controlled UI. I can see an argument that it helps because it would be in a consistent place, but I don't think that's very compelling -- the place it would be most likely to be is the chrome://extensions page (or other browser's analogous surfaces), and, at least in Chrome, relatively few users visit the settings pages. Allowing the extension to surface would likely increase visibility and, I think, be more intuitive (the user can go to the options page by right clicking the extension to configure it).
  • I think having the extension control it is more inline with other similar models, such as selecting your language for a particular site.

Allowing Other Languages in browser.i18n

This, 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 rdcronin added opposed: chrome Opposed by Chrome and removed follow-up: chrome Needs a response from a Chrome representative labels Apr 26, 2024
@hanguokai
Copy link
Member Author

hanguokai commented Apr 27, 2024

@rdcronin My reply is as follows. I wish you'd take a little more time to think about it.

Goal

The goal of this proposal is to enhance the browser.i18n API to support implementing the user language selection menu. This is not the goal of #274 , or at least the author of #274 explicitly believes that #258 and #274 do not conflict.

History

  1. To achieve this goal, the original idea of this proposal was to support passing a language parameter in the i18n.getMessage() method, i.e. the option 3 in the current proposal.
  2. In the same year (2022) that I made this proposal, I found out that Android 13 introduced a new feature: Per-app language preferences. I think it is conceptually more complete than my original idea. The relationship between extensions and the browser is similar to the relationship between native apps and the operating system. I believe the Android system has carefully considered this feature.
  3. Furthermore, in addition to providing APIs for developers to provide their own UI, as a general function, it would be better if the browser built-in provides this UI. I'll explain this later.

After the above iteration, this is what this proposal looks like now.

Use Case

The 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.

API

To 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 get/setDefaultLocale() methods. This avoids the need for extensions have to asynchronously initialize language resources every time before they can use it.

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 setDefaultLocale(), the code will be very simple:

// 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 UI

First 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:

  • all existing (and future) extensions will support this feature without any code change, and it is backward compatible.
  • developers no longer need to develop this UI themselves.
  • it is easy to test different languages when developing.

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);
}

It's a per-extension option, similar to other options that are presented and handled by the extension. Chrome has some of these, but the vast majority of these options are ones that should not or cannot be delegated to the extension (such as permission-related settings).

I18N is a general feature, not only for developers, but also for users. In this respect, the browser can play a bigger role.

The browser may not have perfect insight into which languages the extension supports. We can look at the messages file, but ...

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 browser.i18n.getMessage(), these are the languages that they can be supported. To avoid the problem you mention, we can allow the extension to opt-in or opt-out this feature.

There doesn't seem to be significant benefit to having this be a browser-provided option over an extension-controlled UI.

For this proposal, the browser's built-in UI is optional, and I don't require the browser to must support it.

I think having the extension control it is more inline with other similar models, such as selecting your language for a particular site.

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.

@hanguokai hanguokai added follow-up: chrome Needs a response from a Chrome representative and removed opposed: chrome Opposed by Chrome labels Apr 28, 2024
@hanguokai
Copy link
Member Author

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

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.

@rdcronin
Copy link
Contributor

rdcronin commented May 8, 2024

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 get/setDefaultLocale() (or CurrentLanguage as mentioned in the original comment), allowing an extension to set the default it wants the browser to use for that extension. For that subset of functionality, I'm supportive on the Chrome side.

@rdcronin rdcronin added supportive: chrome Supportive from Chrome and removed follow-up: chrome Needs a response from a Chrome representative labels May 8, 2024
@hanguokai
Copy link
Member Author

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.

@xPaw
Copy link

xPaw commented Jun 14, 2024

If getCurrentLanguage is added, its format will be supported by Intl APIs, so we can use the various formatters in correct language Intl.NumberFormat(browser.i18n.getCurrentLanguage()) as opposed to Intl.NumberFormat(undefined).

i18n.getMessage variant that accepts a language code would also be good to have, when you want to force something to be in a different language (e.g. to match strings to be in the page's language for extensions that add elements to pages).

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)

@carlosjeurissen
Copy link
Contributor

@xPaw currently the extension language will always match the browser language which can be fetched using i18n.getUILanuage. Thus you can already pass this language to the Intl formatters.

@hanguokai
Copy link
Member Author

If getCurrentLanguage is added, its format will be supported by Intl APIs

I think yes, because both this api and Intl api use a string with a BCP 47 language tag. If the developer never called i18n.setCurrentLanguage(lang), i18n.getCurrentLanguage() returning undefined or the current language that used by getMessage() is to be discussed. I will write a proposal later.

currently the extension language will always match the browser language

I think this is not true. For example, the language of the browser UI is English,

  1. if the extension only supports Chinese (so getMessage() always return Chinese), the extension is displayed in Chinese.
  2. if the extension supports French(default) and Japanese, the extension is displayed in French.
  3. if the extension setCurrentLanguage(lang) to Spanish, the extension is displayed in Spanish even though it supports English.

@carlosjeurissen
Copy link
Contributor

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 _ instead of - as delimiter.

This gives motivation to already have an i18n.getCurrentLanguage() method independent of being able to change the current language dynamically. @Rob--W I could add this to #569 ? Unless you believe it should be separate.

3. if the extension `setCurrentLanguage(lang)` to Spanish, the extension is displayed in Spanish even though it supports English.

With the above comment I was referring to the current state of affairs. Not a hypothetical future situation in which i18n.setCurrentLanguage would be available.

@hanguokai
Copy link
Member Author

This gives motivation to already have an i18n.getCurrentLanguage() method independent of being able to change the current language dynamically.

Yes, i18n.getCurrentLanguage() is useful on its own. I would like it to be defined in the current proposal, that is also affected by i18n.setCurrentLanguage(lang). After all, all browsers are supportive for this proposal, so they all will be implemented finally. If you want to separate it, I can also write a separate proposal for it.

@carlosjeurissen
Copy link
Contributor

Yes, i18n.getCurrentLanguage() is useful on its own. I would like it to be defined in the current proposal, that is also affected by i18n.setCurrentLanguage(lang). After all, all browsers are supportive for this proposal, so they all will be implemented finally. If you want to separate it, I can also write a separate proposal for it.

It should indeed probably be separate from #569.

My suggestion would be to create a joined proposal for the methods i18n.getCurrentLanguage() and i18n.getAvailableLanguages as they seem to also have quite some overlap implementation-wise. i18n.getAvailableLanguages would also be useful for #274. I could work on drafting such proposal. However, considering proposals need to be backed by browsers I would love to hear input on this @Rob--W.

@hanguokai
Copy link
Member Author

My suggestion would be to create a joined proposal for the methods i18n.getCurrentLanguage() and i18n.getAvailableLanguages.

Thanks for suggestion. However, in this proposal (#comment-0), it already contains i18n.getAllLanguages() (no matter what its ultimate name is), because the purpose is to create a language-select menu for users.

@carlosjeurissen
Copy link
Contributor

Thanks for suggestion. However, in this proposal (#comment-0), it already contains i18n.getAllLanguages() (no matter what its ultimate name is), because the purpose is to create a language-select menu for users.

The point I was trying to make is that i18n.getAllLanguages() or i18n.getAvailableLanguages() just like i18n.getCurrentLanguage() also makes sense to implement separately. It comes down to the question if we want to include everything in one big proposal or have separate implementable proposals present. If we want to break it down into multiple proposals having i18n.getCurrentLanguage() and i18n.getAllLanguages() in one proposal makes most sense to me due to them being very related.

@hanguokai
Copy link
Member Author

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.

@hanguokai
Copy link
Member Author

I just create a formal version of the proposal at #641

@erosman
Copy link

erosman commented Aug 10, 2024

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 default_locale. Everything else would remain as it was and browser uses the new "default_locale".

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 Locale

Switching locale would be applied when refreshing the page, without the need for a live-replacement or additional change listeners.

Missing Locale

In case a locale is set that is not available, the next fallback is used without breaking errors.

i18n.getAllLanguages()

i18n.getAllLanguages() method as in #258 (comment) would be very useful for creating the necessary UI for the users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
i18n-tracker Group bringing to attention of Internationalization, or tracked by i18n but not needing response. proposal Proposal for a change or new feature supportive: chrome Supportive from Chrome supportive: firefox Supportive from Firefox supportive: safari Supportive from Safari topic: localization
Projects
None yet
Development

No branches or pull requests