[l10n] Add Infobar to adjust DevTools' locale

This CL adds an Infobar that allows the user to adjust DevTools'
locale. The Infobar is shown only under the following circumstances:

  1) It hasn't been shown before
  2) The localized DevTools experiment is enabled
  3) DevTools is configured to use a language other than
     'browserLanguage', which tries to match Chrome's locale
  4) Chrome's UI language is also supported by DevTools and the
     currently configured DevTools UI langauge differs from Chrome's.

Screenshot: https://imgur.com/a/GmazgOj

Bug: chromium:1163928
Change-Id: I83c956b534bdef5b5c7f555733d20fca613a1b33
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2944901
Commit-Queue: Simon Zünd <szuend@chromium.org>
Reviewed-by: Sigurd Schneider <sigurds@chromium.org>
This commit is contained in:
Simon Zünd
2021-06-09 09:18:54 +00:00
committed by Devtools-frontend LUCI CQ
parent 5089cc55b3
commit dd2f205a4c
6 changed files with 170 additions and 11 deletions
+26 -1
View File
@@ -29,8 +29,11 @@ export type DevToolsLocaleCreationOptions = {
*/
export class DevToolsLocale {
readonly locale: string;
readonly lookupClosestDevToolsLocale: (locale: string) => string;
private constructor(data: DevToolsLocaleData) {
this.lookupClosestDevToolsLocale = data.lookupClosestDevToolsLocale;
// TODO(crbug.com/1163928): Use constant once setting actually exists.
if (data.settingLanguage === 'browserLanguage') {
this.locale = data.navigatorLanguage || 'en-US';
@@ -38,7 +41,7 @@ export class DevToolsLocale {
this.locale = data.settingLanguage;
}
this.locale = data.lookupClosestDevToolsLocale(this.locale);
this.locale = this.lookupClosestDevToolsLocale(this.locale);
}
static instance(opts: DevToolsLocaleCreationOptions = {create: false}): DevToolsLocale {
@@ -57,4 +60,26 @@ export class DevToolsLocale {
// overwrite the locale.
(this.locale as DevToolsLocale['locale']) = 'en-US';
}
/**
* Returns true iff DevTools supports the language of the passed locale.
* Note that it doesn't have to be a one-to-one match, e.g. if DevTools supports
* 'de', then passing 'de-AT' will return true.
*/
languageIsSupportedByDevTools(localeString: string): boolean {
return localeLanguagesMatch(localeString, this.lookupClosestDevToolsLocale(localeString));
}
}
/**
* Returns true iff the two locales have matching languages. This means the
* passing 'de-AT' and 'de-DE' will return true, while 'de-DE' and 'en' will
* return false.
*/
export function localeLanguagesMatch(localeString1: string, localeString2: string): boolean {
// @ts-ignore TODO(crbug.com/1163928) Wait for Intl support.
const locale1 = new Intl.Locale(localeString1);
// @ts-ignore TODO(crbug.com/1163928) Wait for Intl support.
const locale2 = new Intl.Locale(localeString2);
return locale1.language === locale2.language;
}
+9
View File
@@ -11303,6 +11303,9 @@
"ui/legacy/InspectorView.ts | closeDrawer": {
"message": "Close drawer"
},
"ui/legacy/InspectorView.ts | devToolsLanguageMissmatch": {
"message": "DevTools is now available in {PH1}!"
},
"ui/legacy/InspectorView.ts | moreTools": {
"message": "More Tools"
},
@@ -11318,6 +11321,12 @@
"ui/legacy/InspectorView.ts | reloadDevtools": {
"message": "Reload DevTools"
},
"ui/legacy/InspectorView.ts | setToBrowserLanguage": {
"message": "Always match Chrome's language"
},
"ui/legacy/InspectorView.ts | setToSpecificLanguage": {
"message": "Switch DevTools to {PH1}"
},
"ui/legacy/ListWidget.ts | addString": {
"message": "Add"
},
+9
View File
@@ -11303,6 +11303,9 @@
"ui/legacy/InspectorView.ts | closeDrawer": {
"message": "Ĉĺôśê d́r̂áŵér̂"
},
"ui/legacy/InspectorView.ts | devToolsLanguageMissmatch": {
"message": "D̂év̂T́ôól̂ś îś n̂óŵ áv̂áîĺâb́l̂é îń {PH1}!"
},
"ui/legacy/InspectorView.ts | moreTools": {
"message": "M̂ór̂é T̂óôĺŝ"
},
@@ -11318,6 +11321,12 @@
"ui/legacy/InspectorView.ts | reloadDevtools": {
"message": "R̂él̂óâd́ D̂év̂T́ôól̂ś"
},
"ui/legacy/InspectorView.ts | setToBrowserLanguage": {
"message": "Âĺŵáŷś m̂át̂ćĥ Ćĥŕôḿê'ś l̂án̂ǵûáĝé"
},
"ui/legacy/InspectorView.ts | setToSpecificLanguage": {
"message": "Ŝẃît́ĉh́ D̂év̂T́ôól̂ś t̂ó {PH1}"
},
"ui/legacy/ListWidget.ts | addString": {
"message": "Âd́d̂"
},
+101 -9
View File
@@ -79,6 +79,26 @@ const UIStrings = {
*@description Text for context menu action to move a tab to the drawer
*/
moveToBottom: 'Move to bottom',
/**
* @description Text shown in a prompt to the user when DevTools is started and the
* currently selected DevTools locale does not match Chrome's locale.
* The placeholder is the current Chrome language.
* @example {German} PH1
*/
devToolsLanguageMissmatch: 'DevTools is now available in {PH1}!',
/**
* @description An option the user can select when we notice that DevTools
* is configured with a different locale than Chrome. This option means DevTools will
* always try and display the DevTools UI in the same language as Chrome.
*/
setToBrowserLanguage: 'Always match Chrome\'s language',
/**
* @description An option the user can select when DevTools notices that DevTools
* is configured with a different locale than Chrome. This option means DevTools UI
* will be switched to the language specified in the placeholder.
* @example {German} PH1
*/
setToSpecificLanguage: 'Switch DevTools to {PH1}',
};
const str_ = i18n.i18n.registerUIStrings('ui/legacy/InspectorView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -157,6 +177,12 @@ export class InspectorView extends VBox implements ViewLocationResolver {
const panelName = (event.data as string);
this.showPanel(panelName);
}
if (shouldShowLocaleInfobar()) {
const infobar = createLocaleInfobar();
infobar.setParentView(this);
this._attachInfobar(infobar);
}
}
static instance(opts: {
@@ -371,17 +397,12 @@ export class InspectorView extends VBox implements ViewLocationResolver {
{
text: i18nString(UIStrings.reloadDevtools),
highlight: true,
delegate: (): void => {
if (DockController.instance().canDock() && DockController.instance().dockSide() === State.Undocked) {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.setIsDocked(true, function() {});
}
Host.InspectorFrontendHost.InspectorFrontendHostInstance.reattach(() => window.location.reload());
},
delegate: (): void => reloadDevTools(),
dismiss: false,
},
]);
infobar.setParentView(this);
this._attachReloadRequiredInfobar(infobar);
this._attachInfobar(infobar);
this._reloadRequiredInfobar = infobar;
infobar.setCloseCallback(() => {
delete this._reloadRequiredInfobar;
@@ -389,14 +410,85 @@ export class InspectorView extends VBox implements ViewLocationResolver {
}
}
_attachReloadRequiredInfobar(infobar: Infobar): void {
_createInfoBarDiv(): void {
if (!this._infoBarDiv) {
this._infoBarDiv = (document.createElement('div') as HTMLDivElement);
this._infoBarDiv.classList.add('flex-none');
this.contentElement.insertBefore(this._infoBarDiv, this.contentElement.firstChild);
}
this._infoBarDiv.appendChild(infobar.element);
}
_attachInfobar(infobar: Infobar): void {
this._createInfoBarDiv();
this._infoBarDiv?.appendChild(infobar.element);
}
}
function getDisableLocaleInfoBarSetting(): Common.Settings.Setting<boolean> {
return Common.Settings.Settings.instance().createSetting('disableLocaleInfoBar', false);
}
function shouldShowLocaleInfobar(): boolean {
if (!Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.LOCALIZED_DEVTOOLS)) {
return false;
}
if (getDisableLocaleInfoBarSetting().get()) {
return false;
}
const languageSettingValue = Common.Settings.Settings.instance().moduleSetting<string>('language').get();
if (languageSettingValue === 'browserLanguage') {
return false;
}
// When the selected DevTools locale differs from the locale of the browser UI, we want to notify
// users only once, that they have the opportunity to adjust DevTools locale to match Chrome's locale.
return !i18n.DevToolsLocale.localeLanguagesMatch(navigator.language, languageSettingValue) &&
i18n.DevToolsLocale.DevToolsLocale.instance().languageIsSupportedByDevTools(navigator.language);
}
function createLocaleInfobar(): Infobar {
const devtoolsLocale = i18n.DevToolsLocale.DevToolsLocale.instance();
const closestSupportedLocale = devtoolsLocale.lookupClosestDevToolsLocale(navigator.language);
// @ts-ignore TODO(crbug.com/1163928) Wait for Intl support.
const locale = new Intl.Locale(closestSupportedLocale);
const closestSupportedLanguageInCurrentLocale =
new Intl.DisplayNames([devtoolsLocale.locale], {type: 'language'}).of(locale.language);
const languageSetting = Common.Settings.Settings.instance().moduleSetting<string>('language');
return new Infobar(
InfobarType.Info, i18nString(UIStrings.devToolsLanguageMissmatch, {PH1: closestSupportedLanguageInCurrentLocale}),
[
{
text: i18nString(UIStrings.setToBrowserLanguage),
highlight: true,
delegate: (): void => {
languageSetting.set('browserLanguage');
getDisableLocaleInfoBarSetting().set(true);
reloadDevTools();
},
dismiss: true,
},
{
text: i18nString(UIStrings.setToSpecificLanguage, {PH1: closestSupportedLanguageInCurrentLocale}),
highlight: true,
delegate: (): void => {
languageSetting.set(closestSupportedLocale);
getDisableLocaleInfoBarSetting().set(true);
reloadDevTools();
},
dismiss: true,
},
],
getDisableLocaleInfoBarSetting());
}
function reloadDevTools(): void {
if (DockController.instance().canDock() && DockController.instance().dockSide() === State.Undocked) {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.setIsDocked(true, function() {});
}
Host.InspectorFrontendHost.InspectorFrontendHostInstance.reattach(() => window.location.reload());
}
let actionDelegateInstance: ActionDelegate;
@@ -79,4 +79,28 @@ describe('DevToolsLocale', () => {
assert.strictEqual(devToolsLocale.locale, 'en-US');
});
});
describe('languageIsSupportedByDevTools', () => {
it('returns true if the locale is supported, false otherwise', () => {
const data: i18n.DevToolsLocale.DevToolsLocaleData = {
settingLanguage: 'zh-HK',
navigatorLanguage: '',
lookupClosestDevToolsLocale: () => 'zh',
};
const devToolsLocale = i18n.DevToolsLocale.DevToolsLocale.instance({create: true, data});
assert.isTrue(devToolsLocale.languageIsSupportedByDevTools('zh-HK'));
assert.isFalse(devToolsLocale.languageIsSupportedByDevTools('de-DE'));
});
});
});
describe('localeLanguagesMatch', () => {
it('returns true if the language part of a locale matches, false otherwise', () => {
assert.isTrue(i18n.DevToolsLocale.localeLanguagesMatch('de-DE', 'de-AT'));
assert.isTrue(i18n.DevToolsLocale.localeLanguagesMatch('de-DE', 'de'));
assert.isFalse(i18n.DevToolsLocale.localeLanguagesMatch('de', 'en'));
assert.isFalse(i18n.DevToolsLocale.localeLanguagesMatch('de-AT', 'en-US'));
});
});
@@ -55,7 +55,7 @@ describe('serialize/deserialize round-trip', () => {
describe('getLocalizedLanguageRegion', () => {
function createMockDevToolsLocale(locale: string): i18n.DevToolsLocale.DevToolsLocale {
return {locale, forceFallbackLocale: () => {}};
return {locale, forceFallbackLocale: () => {}} as i18n.DevToolsLocale.DevToolsLocale;
}
it('build the correct language/region string', () => {