w3c / webextensions

Charter and administrivia for the WebExtensions Community Group (WECG)
Other
581 stars 50 forks source link

Per-extension language preferences #258

Open hanguokai opened 1 year ago

hanguokai commented 1 year ago

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

carlosjeurissen commented 1 month ago

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('@@uilocale') 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 https://github.com/w3c/webextensions/pull/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 commented 1 month ago

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 commented 1 month ago

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 https://github.com/w3c/webextensions/issues/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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 4 weeks ago

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