2023-01-24 13:13:18 +00:00
|
|
|
import {detectCurrentLang, makeT, setAnonymousLocale} from 'app/client/lib/localization';
|
|
|
|
import {AppModel} from 'app/client/models/AppModel';
|
|
|
|
import {hoverTooltip} from 'app/client/ui/tooltips';
|
2023-02-23 07:50:34 +00:00
|
|
|
import {cssHoverCircle} from 'app/client/ui/TopBarCss';
|
|
|
|
import {theme} from 'app/client/ui2018/cssVars';
|
2023-01-24 13:13:18 +00:00
|
|
|
import {icon} from 'app/client/ui2018/icons';
|
|
|
|
import {menu, menuItem} from 'app/client/ui2018/menus';
|
|
|
|
import {getCountryCode} from 'app/common/Locales';
|
|
|
|
import {getGristConfig} from 'app/common/urlUtils';
|
|
|
|
import {dom, makeTestId, styled} from 'grainjs';
|
|
|
|
|
|
|
|
const testId = makeTestId('test-language-');
|
|
|
|
const t = makeT('LanguageMenu');
|
|
|
|
|
|
|
|
export function buildLanguageMenu(appModel: AppModel) {
|
|
|
|
// Get the list of languages from the config, or default to English.
|
|
|
|
const languages = getGristConfig().supportedLngs ?? ['en'];
|
|
|
|
// Get the current language (from user's preference, cookie or browser)
|
|
|
|
const userLanguage = detectCurrentLang();
|
|
|
|
|
|
|
|
if (appModel.currentValidUser) {
|
|
|
|
// For logged in users, we don't need to show the menu (they have a preference in their profile).
|
|
|
|
// But for tests we will show a hidden indicator.
|
|
|
|
return dom('input', {type: 'hidden'}, (testId(`current-` + userLanguage)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// When we switch language, we need to reload the page to get the new translations.
|
|
|
|
// This button is only for anonymous users, so we don't need to save the preference or wait for anything.
|
|
|
|
const changeLanguage = (lng: string) => {
|
|
|
|
setAnonymousLocale(lng);
|
|
|
|
window.location.reload();
|
|
|
|
};
|
2023-02-23 07:50:34 +00:00
|
|
|
const flagIcon = buildFlagIcon(userLanguage);
|
|
|
|
return cssFlagButton(
|
2023-01-24 13:13:18 +00:00
|
|
|
// Flag or emoji flag if we have it.
|
2023-02-23 07:50:34 +00:00
|
|
|
cssFlagIconWrapper(flagIcon),
|
2023-01-24 13:13:18 +00:00
|
|
|
// Expose for test the current language use.
|
|
|
|
testId(`current-` + userLanguage),
|
|
|
|
menu(
|
|
|
|
// Convert the list of languages we support to menu items.
|
|
|
|
() => languages.map((lng) => menuItem(() => changeLanguage(lng), [
|
|
|
|
// Try to convert the locale to nice name, fallback to locale itself.
|
|
|
|
cssFirstUpper(translateLocale(lng) ?? lng),
|
|
|
|
// If this is current language, mark it with a tick (by default we mark en).
|
|
|
|
userLanguage === lng ? cssWrapper(icon('Tick'), testId('selected')) : null,
|
|
|
|
testId(`lang-` + lng),
|
|
|
|
])),
|
|
|
|
{
|
|
|
|
placement: 'bottom-end',
|
|
|
|
}
|
|
|
|
),
|
|
|
|
hoverTooltip(t('Language'), {key: 'topBarBtnTooltip'}),
|
|
|
|
testId('button'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-02-23 07:50:34 +00:00
|
|
|
function buildFlagIcon(locale: string) {
|
2023-01-24 13:13:18 +00:00
|
|
|
const countryCode = getCountryCode(locale);
|
|
|
|
return [
|
2023-02-23 07:50:34 +00:00
|
|
|
// Try to show an icon of the country's flag. (The icon may not exist.)
|
|
|
|
!countryCode ? null : cssFlagIcon({
|
|
|
|
// Unfortunately, Windows doesn't support emoji flags, so we need to use SVG icons.
|
|
|
|
style: `background-image: url("icons/locales/${countryCode}.svg");`,
|
|
|
|
}, testId('button-icon')),
|
|
|
|
// Display a placeholder icon behind the one above, to act as a fallback.
|
|
|
|
cssPlaceholderFlagIcon('Flag'),
|
2023-01-24 13:13:18 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
export function translateLocale(locale: string) {
|
|
|
|
try {
|
|
|
|
locale = locale.replace("_", "-");
|
|
|
|
// This API might not be available in all browsers.
|
|
|
|
const languageNames = new Intl.DisplayNames([locale], {type: 'language'});
|
|
|
|
return languageNames.of(locale) || null;
|
|
|
|
} catch (err) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const cssWrapper = styled('div', `
|
|
|
|
margin-left: auto;
|
|
|
|
display: inline-block;
|
|
|
|
`);
|
|
|
|
|
2023-02-23 07:50:34 +00:00
|
|
|
const cssFirstUpper = styled('span', `
|
|
|
|
&::first-letter {
|
|
|
|
text-transform: capitalize;
|
|
|
|
}
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFlagButton = styled(cssHoverCircle, `
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
margin: 5px;
|
2023-01-24 13:13:18 +00:00
|
|
|
cursor: pointer;
|
|
|
|
`);
|
|
|
|
|
2023-02-23 07:50:34 +00:00
|
|
|
const cssFlagIconWrapper = styled('div', `
|
|
|
|
position: relative;
|
|
|
|
width: 16px;
|
|
|
|
height: 16px;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFlagIcon = styled('div', `
|
|
|
|
position: absolute;
|
2023-01-24 13:13:18 +00:00
|
|
|
width: 16px;
|
|
|
|
height: 16px;
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
background-position: center;
|
|
|
|
background-color: transparent;
|
|
|
|
background-size: contain;
|
2023-02-23 07:50:34 +00:00
|
|
|
z-index: 1;
|
2023-01-24 13:13:18 +00:00
|
|
|
`);
|
|
|
|
|
2023-02-23 07:50:34 +00:00
|
|
|
const cssPlaceholderFlagIcon = styled(icon, `
|
|
|
|
position: absolute;
|
|
|
|
--icon-color: ${theme.topBarButtonPrimaryFg};
|
2023-01-24 13:13:18 +00:00
|
|
|
`);
|