Init Language Picker
This commit is contained in:
parent
754bb02c06
commit
89e66da351
25 changed files with 610 additions and 61 deletions
|
@ -5823,6 +5823,42 @@
|
||||||
"messageformat": "You will no longer be able to share or view stories. Story updates you have recently shared will also be deleted.",
|
"messageformat": "You will no longer be able to share or view stories. Story updates you have recently shared will also be deleted.",
|
||||||
"description": "Confirmation modal body for disabling stories"
|
"description": "Confirmation modal body for disabling stories"
|
||||||
},
|
},
|
||||||
|
"icu:Preferences__Language__Label": {
|
||||||
|
"messageformat": "Language",
|
||||||
|
"description": "Language setting label"
|
||||||
|
},
|
||||||
|
"icu:Preferences__Language__ModalTitle": {
|
||||||
|
"messageformat": "Language",
|
||||||
|
"description": "Language setting modal title"
|
||||||
|
},
|
||||||
|
"icu:Preferences__Language__SystemLanguage": {
|
||||||
|
"messageformat": "System Language",
|
||||||
|
"description": "Option for system language"
|
||||||
|
},
|
||||||
|
"icu:Preferences__Language__SearchLanguages": {
|
||||||
|
"messageformat": "Search languages",
|
||||||
|
"description": "Placholder for language preference search box"
|
||||||
|
},
|
||||||
|
"icu:Preferences__Language__NoResults": {
|
||||||
|
"messageformat": "No results for “{searchTerm}”",
|
||||||
|
"description": "When no results are found for language preference search"
|
||||||
|
},
|
||||||
|
"icu:Preferences__LanguageModal__Set": {
|
||||||
|
"messageformat": "Set",
|
||||||
|
"description": "Button to set language preference"
|
||||||
|
},
|
||||||
|
"icu:Preferences__LanguageModal__Restart__Title": {
|
||||||
|
"messageformat": "Restart Signal to apply",
|
||||||
|
"description": "Title for restart Signal modal to apply language changes"
|
||||||
|
},
|
||||||
|
"icu:Preferences__LanguageModal__Restart__Description": {
|
||||||
|
"messageformat": "To change the language, the app needs to restart.",
|
||||||
|
"description": "Description for restart Signal modal to apply language changes"
|
||||||
|
},
|
||||||
|
"icu:Preferences__LanguageModal__Restart__Button": {
|
||||||
|
"messageformat": "Restart",
|
||||||
|
"description": "Button to restart Signal to apply language changes"
|
||||||
|
},
|
||||||
"icu:DialogUpdate--version-available": {
|
"icu:DialogUpdate--version-available": {
|
||||||
"messageformat": "Update to version {version} available",
|
"messageformat": "Update to version {version} available",
|
||||||
"description": "Tooltip for new update available"
|
"description": "Tooltip for new update available"
|
||||||
|
|
|
@ -26,6 +26,7 @@ function getLocaleMessages(locale: string): LocaleMessagesType {
|
||||||
export type LocaleDirection = 'ltr' | 'rtl';
|
export type LocaleDirection = 'ltr' | 'rtl';
|
||||||
|
|
||||||
export type LocaleType = {
|
export type LocaleType = {
|
||||||
|
availableLocales: Array<string>;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
name: string;
|
name: string;
|
||||||
direction: LocaleDirection;
|
direction: LocaleDirection;
|
||||||
|
@ -65,6 +66,7 @@ function getLocaleDirection(
|
||||||
}
|
}
|
||||||
|
|
||||||
function finalize(
|
function finalize(
|
||||||
|
availableLocales: Array<string>,
|
||||||
messages: LocaleMessagesType,
|
messages: LocaleMessagesType,
|
||||||
backupMessages: LocaleMessagesType,
|
backupMessages: LocaleMessagesType,
|
||||||
localeName: string,
|
localeName: string,
|
||||||
|
@ -80,6 +82,7 @@ function finalize(
|
||||||
logger.info(`locale: Text info direction for ${localeName}: ${direction}`);
|
logger.info(`locale: Text info direction for ${localeName}: ${direction}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
availableLocales,
|
||||||
i18n,
|
i18n,
|
||||||
name: localeName,
|
name: localeName,
|
||||||
direction,
|
direction,
|
||||||
|
@ -99,10 +102,12 @@ export function _getAvailableLocales(): Array<string> {
|
||||||
|
|
||||||
export function load({
|
export function load({
|
||||||
preferredSystemLocales,
|
preferredSystemLocales,
|
||||||
|
localeOverride,
|
||||||
hourCyclePreference,
|
hourCyclePreference,
|
||||||
logger,
|
logger,
|
||||||
}: {
|
}: {
|
||||||
preferredSystemLocales: Array<string>;
|
preferredSystemLocales: Array<string>;
|
||||||
|
localeOverride: string | null;
|
||||||
hourCyclePreference: HourCyclePreference;
|
hourCyclePreference: HourCyclePreference;
|
||||||
logger: LoggerType;
|
logger: LoggerType;
|
||||||
}): LocaleType {
|
}): LocaleType {
|
||||||
|
@ -117,10 +122,11 @@ export function load({
|
||||||
const availableLocales = _getAvailableLocales();
|
const availableLocales = _getAvailableLocales();
|
||||||
|
|
||||||
logger.info('locale: Supported locales:', availableLocales.join(', '));
|
logger.info('locale: Supported locales:', availableLocales.join(', '));
|
||||||
logger.info('locale: Preferred locales: ', preferredSystemLocales.join(', '));
|
logger.info('locale: Preferred locales:', preferredSystemLocales.join(', '));
|
||||||
|
logger.info('locale: Locale Override:', localeOverride);
|
||||||
|
|
||||||
const matchedLocale = LocaleMatcher.match(
|
const matchedLocale = LocaleMatcher.match(
|
||||||
preferredSystemLocales,
|
localeOverride != null ? [localeOverride] : preferredSystemLocales,
|
||||||
availableLocales,
|
availableLocales,
|
||||||
'en',
|
'en',
|
||||||
{ algorithm: 'best fit' }
|
{ algorithm: 'best fit' }
|
||||||
|
@ -132,6 +138,7 @@ export function load({
|
||||||
const englishMessages = getLocaleMessages('en');
|
const englishMessages = getLocaleMessages('en');
|
||||||
|
|
||||||
return finalize(
|
return finalize(
|
||||||
|
availableLocales,
|
||||||
matchedLocaleMessages,
|
matchedLocaleMessages,
|
||||||
englishMessages,
|
englishMessages,
|
||||||
matchedLocale,
|
matchedLocale,
|
||||||
|
|
28
app/main.ts
28
app/main.ts
|
@ -359,6 +359,26 @@ async function getBackgroundColor(
|
||||||
throw missingCaseError(theme);
|
throw missingCaseError(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getLocaleOverrideSetting(): Promise<string | null> {
|
||||||
|
const fastValue = ephemeralConfig.get('localeOverride');
|
||||||
|
// eslint-disable-next-line eqeqeq -- Checking for null explicitly
|
||||||
|
if (typeof fastValue === 'string' || fastValue === null) {
|
||||||
|
getLogger().info('got fast localeOverride setting', fastValue);
|
||||||
|
return fastValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await sql.sqlCall('getItemById', 'localeOverride');
|
||||||
|
|
||||||
|
// Default to `null` if setting doesn't exist yet
|
||||||
|
const slowValue = typeof json?.value === 'string' ? json.value : null;
|
||||||
|
|
||||||
|
ephemeralConfig.set('localeOverride', slowValue);
|
||||||
|
|
||||||
|
getLogger().info('got slow localeOverride setting', slowValue);
|
||||||
|
|
||||||
|
return slowValue;
|
||||||
|
}
|
||||||
|
|
||||||
let systemTrayService: SystemTrayService | undefined;
|
let systemTrayService: SystemTrayService | undefined;
|
||||||
const systemTraySettingCache = new SystemTraySettingCache(
|
const systemTraySettingCache = new SystemTraySettingCache(
|
||||||
sql,
|
sql,
|
||||||
|
@ -1782,11 +1802,15 @@ app.on('ready', async () => {
|
||||||
// Write buffered information into newly created logger.
|
// Write buffered information into newly created logger.
|
||||||
consoleLogger.writeBufferInto(logger);
|
consoleLogger.writeBufferInto(logger);
|
||||||
|
|
||||||
|
sqlInitPromise = initializeSQL(userDataPath);
|
||||||
|
|
||||||
if (!resolvedTranslationsLocale) {
|
if (!resolvedTranslationsLocale) {
|
||||||
preferredSystemLocales = resolveCanonicalLocales(
|
preferredSystemLocales = resolveCanonicalLocales(
|
||||||
loadPreferredSystemLocales()
|
loadPreferredSystemLocales()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const localeOverride = await getLocaleOverrideSetting();
|
||||||
|
|
||||||
const hourCyclePreference = getHourCyclePreference();
|
const hourCyclePreference = getHourCyclePreference();
|
||||||
logger.info(`app.ready: hour cycle preference: ${hourCyclePreference}`);
|
logger.info(`app.ready: hour cycle preference: ${hourCyclePreference}`);
|
||||||
|
|
||||||
|
@ -1797,13 +1821,12 @@ app.on('ready', async () => {
|
||||||
);
|
);
|
||||||
resolvedTranslationsLocale = loadLocale({
|
resolvedTranslationsLocale = loadLocale({
|
||||||
preferredSystemLocales,
|
preferredSystemLocales,
|
||||||
|
localeOverride,
|
||||||
hourCyclePreference,
|
hourCyclePreference,
|
||||||
logger: getLogger(),
|
logger: getLogger(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlInitPromise = initializeSQL(userDataPath);
|
|
||||||
|
|
||||||
// First run: configure Signal to minimize to tray. Additionally, on Windows
|
// First run: configure Signal to minimize to tray. Additionally, on Windows
|
||||||
// enable auto-start with start-in-tray so that starting from a Desktop icon
|
// enable auto-start with start-in-tray so that starting from a Desktop icon
|
||||||
// would still show the window.
|
// would still show the window.
|
||||||
|
@ -2372,6 +2395,7 @@ ipc.on('get-config', async event => {
|
||||||
|
|
||||||
const parsed = rendererConfigSchema.safeParse({
|
const parsed = rendererConfigSchema.safeParse({
|
||||||
name: packageJson.productName,
|
name: packageJson.productName,
|
||||||
|
availableLocales: getResolvedMessagesLocale().availableLocales,
|
||||||
resolvedTranslationsLocale: getResolvedMessagesLocale().name,
|
resolvedTranslationsLocale: getResolvedMessagesLocale().name,
|
||||||
resolvedTranslationsLocaleDirection: getResolvedMessagesLocale().direction,
|
resolvedTranslationsLocaleDirection: getResolvedMessagesLocale().direction,
|
||||||
hourCyclePreference: getResolvedMessagesLocale().hourCyclePreference,
|
hourCyclePreference: getResolvedMessagesLocale().hourCyclePreference,
|
||||||
|
|
1
images/icons/v3/globe/globe.svg
Normal file
1
images/icons/v3/globe/globe.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none"><g clip-path="url(#a)"><path fill="#000" fill-rule="evenodd" d="M10 19.063A9.062 9.062 0 1 0 10 .938a9.062 9.062 0 0 0 0 18.125Zm0-16.667c-.148 0-.376.077-.67.41-.294.33-.592.851-.857 1.557-.235.627-.433 1.37-.58 2.2h4.215a12.632 12.632 0 0 0-.581-2.2c-.265-.706-.563-1.227-.856-1.558-.295-.332-.523-.41-.671-.41Zm2.304 5.625H7.696c-.06.63-.092 1.293-.092 1.979 0 .686.032 1.349.092 1.98h4.608c.06-.631.092-1.294.092-1.98 0-.686-.032-1.349-.092-1.98Zm1.464 3.958a22.564 22.564 0 0 0 0-3.958h3.576c.17.63.26 1.294.26 1.979s-.09 1.348-.26 1.98h-3.576Zm-1.66 1.459H7.893c.147.829.345 1.572.58 2.198.265.707.563 1.227.856 1.559.295.332.523.41.671.41.148 0 .376-.078.67-.41.294-.332.592-.852.857-1.559.235-.626.433-1.37.58-2.198Zm.304 3.775c.178-.325.337-.683.48-1.064a14.4 14.4 0 0 0 .695-2.712h3.198a7.628 7.628 0 0 1-4.373 3.777Zm0-14.427a7.627 7.627 0 0 1 4.373 3.776h-3.198a14.4 14.4 0 0 0-.695-2.71 8.425 8.425 0 0 0-.48-1.065ZM7.587 17.215a8.437 8.437 0 0 1-.48-1.065 14.404 14.404 0 0 1-.694-2.712H3.215a7.627 7.627 0 0 0 4.372 3.777ZM6.232 11.98a22.572 22.572 0 0 1 0-3.958H2.656A7.614 7.614 0 0 0 2.396 10c0 .685.09 1.348.26 1.98h3.576Zm.181-5.416c.165-1.01.4-1.928.695-2.712a8.2 8.2 0 0 1 .48-1.064 7.627 7.627 0 0 0-4.373 3.776h3.198Z" clip-rule="evenodd"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h20v20H0z"/></clipPath></defs></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -21,15 +21,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
|
&--with-back-button .module-Modal__title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__headerTitle {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-block: 16px 1em;
|
padding-block: 16px 1em;
|
||||||
padding-inline: 16px;
|
padding-inline: 16px;
|
||||||
|
|
||||||
&--with-back-button .module-Modal__title {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
|
|
|
@ -194,6 +194,13 @@
|
||||||
padding-block: 4px;
|
padding-block: 4px;
|
||||||
padding-inline: 24px;
|
padding-inline: 24px;
|
||||||
|
|
||||||
|
&--icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-inline-end: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
&--key {
|
&--key {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-inline-end: 20px;
|
padding-inline-end: 20px;
|
||||||
|
@ -292,3 +299,112 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageIcon {
|
||||||
|
@include light-theme {
|
||||||
|
@include color-svg('../images/icons/v3/globe/globe.svg', $color-gray-75);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
@include color-svg('../images/icons/v3/globe/globe.svg', $color-gray-15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageButton {
|
||||||
|
text-transform: capitalize;
|
||||||
|
@include button-reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal {
|
||||||
|
height: 560px;
|
||||||
|
.module-Modal__body {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal__Title {
|
||||||
|
@include font-body-1-bold;
|
||||||
|
margin-inline: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal__NoResults {
|
||||||
|
@include font-body-1;
|
||||||
|
margin: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal__Item {
|
||||||
|
@include button-reset;
|
||||||
|
width: 100%;
|
||||||
|
padding-block: 2px;
|
||||||
|
padding-inline: 8px;
|
||||||
|
&:hover {
|
||||||
|
.Preferences__LanguageModal__Item__Inner {
|
||||||
|
@include light-theme {
|
||||||
|
background-color: $color-black-alpha-06;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
background-color: $color-white-alpha-06;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
.Preferences__LanguageModal__Item__Inner {
|
||||||
|
@include keyboard-mode {
|
||||||
|
background-color: $color-black-alpha-06;
|
||||||
|
box-shadow: 0 0 0 2px $color-ultramarine;
|
||||||
|
}
|
||||||
|
@include dark-keyboard-mode {
|
||||||
|
background-color: $color-white-alpha-06;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal__Item__Inner {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-block: 5px;
|
||||||
|
padding-inline: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal__Item__Label {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal__Item__Current {
|
||||||
|
display: block;
|
||||||
|
text-transform: capitalize;
|
||||||
|
@include font-body-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal__Item__Check {
|
||||||
|
display: flex;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: $color-ultramarine;
|
||||||
|
@include rounded-corners;
|
||||||
|
&::after {
|
||||||
|
@include color-svg('../images/icons/v3/check/check.svg', $color-white);
|
||||||
|
content: '';
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Preferences__LanguageModal__Item__Matching {
|
||||||
|
display: block;
|
||||||
|
text-transform: capitalize;
|
||||||
|
@include font-body-2;
|
||||||
|
@include light-theme {
|
||||||
|
color: $color-gray-60;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
color: $color-gray-25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -972,7 +972,7 @@ export async function startApp(): Promise<void> {
|
||||||
// This one should always be last - it could restart the app
|
// This one should always be last - it could restart the app
|
||||||
if (window.isBeforeVersion(lastVersion, 'v5.30.0-alpha')) {
|
if (window.isBeforeVersion(lastVersion, 'v5.30.0-alpha')) {
|
||||||
await deleteAllLogs();
|
await deleteAllLogs();
|
||||||
window.IPC.restart();
|
window.SignalContext.restartApp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ type PropsType = {
|
||||||
hasFooterDivider?: boolean;
|
hasFooterDivider?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
modalFooter?: JSX.Element;
|
modalFooter?: JSX.Element;
|
||||||
|
modalHeaderChildren?: ReactNode;
|
||||||
moduleClassName?: string;
|
moduleClassName?: string;
|
||||||
onBackButtonClick?: () => unknown;
|
onBackButtonClick?: () => unknown;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
@ -52,6 +53,7 @@ export function Modal({
|
||||||
hasXButton,
|
hasXButton,
|
||||||
i18n,
|
i18n,
|
||||||
modalFooter,
|
modalFooter,
|
||||||
|
modalHeaderChildren,
|
||||||
moduleClassName,
|
moduleClassName,
|
||||||
noMouseClose,
|
noMouseClose,
|
||||||
onBackButtonClick,
|
onBackButtonClick,
|
||||||
|
@ -122,6 +124,7 @@ export function Modal({
|
||||||
hasXButton={hasXButton}
|
hasXButton={hasXButton}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
modalFooter={modalFooter}
|
modalFooter={modalFooter}
|
||||||
|
modalHeaderChildren={modalHeaderChildren}
|
||||||
moduleClassName={moduleClassName}
|
moduleClassName={moduleClassName}
|
||||||
onBackButtonClick={onBackButtonClick}
|
onBackButtonClick={onBackButtonClick}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
|
@ -162,6 +165,7 @@ export function ModalPage({
|
||||||
hasXButton,
|
hasXButton,
|
||||||
i18n,
|
i18n,
|
||||||
modalFooter,
|
modalFooter,
|
||||||
|
modalHeaderChildren,
|
||||||
moduleClassName,
|
moduleClassName,
|
||||||
onBackButtonClick,
|
onBackButtonClick,
|
||||||
onClose,
|
onClose,
|
||||||
|
@ -179,7 +183,9 @@ export function ModalPage({
|
||||||
const [scrolledToBottom, setScrolledToBottom] = useState(false);
|
const [scrolledToBottom, setScrolledToBottom] = useState(false);
|
||||||
const [hasOverflow, setHasOverflow] = useState(false);
|
const [hasOverflow, setHasOverflow] = useState(false);
|
||||||
|
|
||||||
const hasHeader = Boolean(hasXButton || title || onBackButtonClick);
|
const hasHeader = Boolean(
|
||||||
|
hasXButton || title || modalHeaderChildren || onBackButtonClick
|
||||||
|
);
|
||||||
const getClassName = getClassNamesFor(BASE_CLASS_NAME, moduleClassName);
|
const getClassName = getClassNamesFor(BASE_CLASS_NAME, moduleClassName);
|
||||||
|
|
||||||
useScrollObserver(bodyRef, bodyInnerRef, scroll => {
|
useScrollObserver(bodyRef, bodyInnerRef, scroll => {
|
||||||
|
@ -216,37 +222,40 @@ export function ModalPage({
|
||||||
: null
|
: null
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{onBackButtonClick && (
|
<div className={getClassName('__headerTitle')}>
|
||||||
<button
|
{onBackButtonClick && (
|
||||||
aria-label={i18n('icu:back')}
|
<button
|
||||||
className={getClassName('__back-button')}
|
aria-label={i18n('icu:back')}
|
||||||
onClick={onBackButtonClick}
|
className={getClassName('__back-button')}
|
||||||
tabIndex={0}
|
onClick={onBackButtonClick}
|
||||||
type="button"
|
tabIndex={0}
|
||||||
/>
|
type="button"
|
||||||
)}
|
/>
|
||||||
{title && (
|
)}
|
||||||
<h1
|
{title && (
|
||||||
className={classNames(
|
<h1
|
||||||
getClassName('__title'),
|
className={classNames(
|
||||||
hasXButton ? getClassName('__title--with-x-button') : null
|
getClassName('__title'),
|
||||||
)}
|
hasXButton ? getClassName('__title--with-x-button') : null
|
||||||
>
|
)}
|
||||||
{title}
|
>
|
||||||
</h1>
|
{title}
|
||||||
)}
|
</h1>
|
||||||
{hasXButton && !title && (
|
)}
|
||||||
<div className={getClassName('__title')} />
|
{hasXButton && !title && (
|
||||||
)}
|
<div className={getClassName('__title')} />
|
||||||
{hasXButton && (
|
)}
|
||||||
<button
|
{hasXButton && (
|
||||||
aria-label={i18n('icu:close')}
|
<button
|
||||||
className={getClassName('__close-button')}
|
aria-label={i18n('icu:close')}
|
||||||
onClick={onClose}
|
className={getClassName('__close-button')}
|
||||||
tabIndex={0}
|
onClick={onClose}
|
||||||
type="button"
|
tabIndex={0}
|
||||||
/>
|
type="button"
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{modalHeaderChildren}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -68,6 +68,7 @@ export default {
|
||||||
label: 'Logitech Webcam (4e72:9058)',
|
label: 'Logitech Webcam (4e72:9058)',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
availableLocales: ['en'],
|
||||||
availableMicrophones,
|
availableMicrophones,
|
||||||
availableSpeakers,
|
availableSpeakers,
|
||||||
blockedCount: 0,
|
blockedCount: 0,
|
||||||
|
@ -108,7 +109,10 @@ export default {
|
||||||
isSystemTraySupported: true,
|
isSystemTraySupported: true,
|
||||||
isMinimizeToAndStartInSystemTraySupported: true,
|
isMinimizeToAndStartInSystemTraySupported: true,
|
||||||
lastSyncTime: Date.now(),
|
lastSyncTime: Date.now(),
|
||||||
|
localeOverride: null,
|
||||||
notificationContent: 'name',
|
notificationContent: 'name',
|
||||||
|
preferredSystemLocales: ['en'],
|
||||||
|
resolvedLocale: 'en',
|
||||||
selectedCamera:
|
selectedCamera:
|
||||||
'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c',
|
'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c',
|
||||||
selectedMicrophone: availableMicrophones[0],
|
selectedMicrophone: availableMicrophones[0],
|
||||||
|
@ -143,6 +147,7 @@ export default {
|
||||||
onIncomingCallNotificationsChange: action(
|
onIncomingCallNotificationsChange: action(
|
||||||
'onIncomingCallNotificationsChange'
|
'onIncomingCallNotificationsChange'
|
||||||
),
|
),
|
||||||
|
onLocaleChange: action('onLocaleChange'),
|
||||||
onLastSyncTimeChange: action('onLastSyncTimeChange'),
|
onLastSyncTimeChange: action('onLastSyncTimeChange'),
|
||||||
onMediaCameraPermissionsChange: action('onMediaCameraPermissionsChange'),
|
onMediaCameraPermissionsChange: action('onMediaCameraPermissionsChange'),
|
||||||
onMediaPermissionsChange: action('onMediaPermissionsChange'),
|
onMediaPermissionsChange: action('onMediaPermissionsChange'),
|
||||||
|
|
|
@ -10,9 +10,10 @@ import React, {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { noop } from 'lodash';
|
import { noop, partition } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
|
import * as LocaleMatcher from '@formatjs/intl-localematcher';
|
||||||
|
|
||||||
import type { MediaDeviceSettings } from '../types/Calling';
|
import type { MediaDeviceSettings } from '../types/Calling';
|
||||||
import type {
|
import type {
|
||||||
|
@ -59,6 +60,9 @@ import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||||
import { useUniqueId } from '../hooks/useUniqueId';
|
import { useUniqueId } from '../hooks/useUniqueId';
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
import { focusableSelectors } from '../util/focusableSelectors';
|
import { focusableSelectors } from '../util/focusableSelectors';
|
||||||
|
import { Modal } from './Modal';
|
||||||
|
import { SearchInput } from './SearchInput';
|
||||||
|
import { removeDiacritics } from '../util/removeDiacritics';
|
||||||
|
|
||||||
type CheckboxChangeHandlerType = (value: boolean) => unknown;
|
type CheckboxChangeHandlerType = (value: boolean) => unknown;
|
||||||
type SelectChangeHandlerType<T = string | number> = (value: T) => unknown;
|
type SelectChangeHandlerType<T = string | number> = (value: T) => unknown;
|
||||||
|
@ -103,6 +107,12 @@ export type PropsDataType = {
|
||||||
whoCanSeeMe: PhoneNumberSharingMode;
|
whoCanSeeMe: PhoneNumberSharingMode;
|
||||||
zoomFactor: ZoomFactorType;
|
zoomFactor: ZoomFactorType;
|
||||||
|
|
||||||
|
// Localization
|
||||||
|
availableLocales: ReadonlyArray<string>;
|
||||||
|
localeOverride: string | null;
|
||||||
|
preferredSystemLocales: ReadonlyArray<string>;
|
||||||
|
resolvedLocale: string;
|
||||||
|
|
||||||
// Other props
|
// Other props
|
||||||
hasCustomTitleBar: boolean;
|
hasCustomTitleBar: boolean;
|
||||||
initialSpellCheckSetting: boolean;
|
initialSpellCheckSetting: boolean;
|
||||||
|
@ -161,6 +171,7 @@ type PropsFunctionType = {
|
||||||
onHideMenuBarChange: CheckboxChangeHandlerType;
|
onHideMenuBarChange: CheckboxChangeHandlerType;
|
||||||
onIncomingCallNotificationsChange: CheckboxChangeHandlerType;
|
onIncomingCallNotificationsChange: CheckboxChangeHandlerType;
|
||||||
onLastSyncTimeChange: (time: number) => unknown;
|
onLastSyncTimeChange: (time: number) => unknown;
|
||||||
|
onLocaleChange: (locale: string | null) => void;
|
||||||
onMediaCameraPermissionsChange: CheckboxChangeHandlerType;
|
onMediaCameraPermissionsChange: CheckboxChangeHandlerType;
|
||||||
onMediaPermissionsChange: CheckboxChangeHandlerType;
|
onMediaPermissionsChange: CheckboxChangeHandlerType;
|
||||||
onMessageAudioChange: CheckboxChangeHandlerType;
|
onMessageAudioChange: CheckboxChangeHandlerType;
|
||||||
|
@ -204,6 +215,46 @@ enum Page {
|
||||||
PNP = 'PNP',
|
PNP = 'PNP',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum LanguageDialog {
|
||||||
|
Selection,
|
||||||
|
Confirmation,
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocaleLanguagesWithMultipleRegions(
|
||||||
|
locales: ReadonlyArray<string>
|
||||||
|
): Set<string> {
|
||||||
|
const result = new Set<string>();
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const locale of locales) {
|
||||||
|
const { language } = new Intl.Locale(locale);
|
||||||
|
if (seen.has(language)) {
|
||||||
|
result.add(language);
|
||||||
|
} else {
|
||||||
|
seen.add(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = new Map<string, string>();
|
||||||
|
|
||||||
|
function getLanguageLabel(ofLocale: string, inLocale: string) {
|
||||||
|
const key = `${ofLocale}:${inLocale}`;
|
||||||
|
const cached = cache.get(key);
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
const value =
|
||||||
|
new Intl.DisplayNames(inLocale, {
|
||||||
|
type: 'language',
|
||||||
|
fallback: 'code',
|
||||||
|
style: 'long',
|
||||||
|
languageDisplay: 'standard',
|
||||||
|
}).of(ofLocale) ?? '';
|
||||||
|
cache.set(key, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_ZOOM_FACTORS = [
|
const DEFAULT_ZOOM_FACTORS = [
|
||||||
{
|
{
|
||||||
text: '75%',
|
text: '75%',
|
||||||
|
@ -230,6 +281,7 @@ const DEFAULT_ZOOM_FACTORS = [
|
||||||
export function Preferences({
|
export function Preferences({
|
||||||
addCustomColor,
|
addCustomColor,
|
||||||
availableCameras,
|
availableCameras,
|
||||||
|
availableLocales,
|
||||||
availableMicrophones,
|
availableMicrophones,
|
||||||
availableSpeakers,
|
availableSpeakers,
|
||||||
blockedCount,
|
blockedCount,
|
||||||
|
@ -289,6 +341,7 @@ export function Preferences({
|
||||||
onHideMenuBarChange,
|
onHideMenuBarChange,
|
||||||
onIncomingCallNotificationsChange,
|
onIncomingCallNotificationsChange,
|
||||||
onLastSyncTimeChange,
|
onLastSyncTimeChange,
|
||||||
|
onLocaleChange,
|
||||||
onMediaCameraPermissionsChange,
|
onMediaCameraPermissionsChange,
|
||||||
onMediaPermissionsChange,
|
onMediaPermissionsChange,
|
||||||
onMessageAudioChange,
|
onMessageAudioChange,
|
||||||
|
@ -309,16 +362,19 @@ export function Preferences({
|
||||||
onWhoCanSeeMeChange,
|
onWhoCanSeeMeChange,
|
||||||
onWhoCanFindMeChange,
|
onWhoCanFindMeChange,
|
||||||
onZoomFactorChange,
|
onZoomFactorChange,
|
||||||
|
preferredSystemLocales,
|
||||||
removeCustomColor,
|
removeCustomColor,
|
||||||
removeCustomColorOnConversations,
|
removeCustomColorOnConversations,
|
||||||
resetAllChatColors,
|
resetAllChatColors,
|
||||||
resetDefaultChatColor,
|
resetDefaultChatColor,
|
||||||
|
resolvedLocale,
|
||||||
selectedCamera,
|
selectedCamera,
|
||||||
selectedMicrophone,
|
selectedMicrophone,
|
||||||
selectedSpeaker,
|
selectedSpeaker,
|
||||||
sentMediaQualitySetting,
|
sentMediaQualitySetting,
|
||||||
setGlobalDefaultConversationColor,
|
setGlobalDefaultConversationColor,
|
||||||
shouldShowStoriesSettings,
|
shouldShowStoriesSettings,
|
||||||
|
localeOverride,
|
||||||
themeSetting,
|
themeSetting,
|
||||||
universalExpireTimer = DurationInSeconds.ZERO,
|
universalExpireTimer = DurationInSeconds.ZERO,
|
||||||
whoCanFindMe,
|
whoCanFindMe,
|
||||||
|
@ -328,6 +384,7 @@ export function Preferences({
|
||||||
const storiesId = useUniqueId();
|
const storiesId = useUniqueId();
|
||||||
const themeSelectId = useUniqueId();
|
const themeSelectId = useUniqueId();
|
||||||
const zoomSelectId = useUniqueId();
|
const zoomSelectId = useUniqueId();
|
||||||
|
const languageId = useUniqueId();
|
||||||
|
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
const [confirmStoriesOff, setConfirmStoriesOff] = useState(false);
|
const [confirmStoriesOff, setConfirmStoriesOff] = useState(false);
|
||||||
|
@ -336,13 +393,31 @@ export function Preferences({
|
||||||
const [nowSyncing, setNowSyncing] = useState(false);
|
const [nowSyncing, setNowSyncing] = useState(false);
|
||||||
const [showDisappearingTimerDialog, setShowDisappearingTimerDialog] =
|
const [showDisappearingTimerDialog, setShowDisappearingTimerDialog] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [languageDialog, setLanguageDialog] = useState<LanguageDialog | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [selectedLanguageLocale, setSelectedLanguageLocale] = useState<
|
||||||
|
string | null
|
||||||
|
>(localeOverride);
|
||||||
|
const [languageSearchInput, setLanguageSearchInput] = useState('');
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
function closeLanguageDialog() {
|
||||||
|
setLanguageDialog(null);
|
||||||
|
setSelectedLanguageLocale(localeOverride);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
doneRendering();
|
doneRendering();
|
||||||
}, [doneRendering]);
|
}, [doneRendering]);
|
||||||
|
|
||||||
useEscapeHandling(closeSettings);
|
useEscapeHandling(() => {
|
||||||
|
if (languageDialog != null) {
|
||||||
|
closeLanguageDialog();
|
||||||
|
} else {
|
||||||
|
closeSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const onZoomSelectChange = useCallback(
|
const onZoomSelectChange = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
|
@ -395,6 +470,82 @@ export function Preferences({
|
||||||
[onSelectedSpeakerChange, availableSpeakers]
|
[onSelectedSpeakerChange, availableSpeakers]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const localeSearchOptions = useMemo(() => {
|
||||||
|
const collator = new Intl.Collator(resolvedLocale, { usage: 'sort' });
|
||||||
|
|
||||||
|
const languagesWithMultipleRegions =
|
||||||
|
getLocaleLanguagesWithMultipleRegions(availableLocales);
|
||||||
|
|
||||||
|
const availableLocalesOptions = availableLocales
|
||||||
|
.map(locale => {
|
||||||
|
const { language } = new Intl.Locale(locale);
|
||||||
|
const displayLocale = languagesWithMultipleRegions.has(language)
|
||||||
|
? locale
|
||||||
|
: language;
|
||||||
|
const currentLocaleLabel = getLanguageLabel(
|
||||||
|
displayLocale,
|
||||||
|
resolvedLocale
|
||||||
|
);
|
||||||
|
const matchingLocaleLabel = getLanguageLabel(displayLocale, locale);
|
||||||
|
return { locale, currentLocaleLabel, matchingLocaleLabel };
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
return collator.compare(a.currentLocaleLabel, b.currentLocaleLabel);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [localeOverrideMatches, localeOverrideNonMatches] = partition(
|
||||||
|
availableLocalesOptions,
|
||||||
|
option => {
|
||||||
|
return option.locale === localeOverride;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const preferredSystemLocaleMatch = LocaleMatcher.match(
|
||||||
|
preferredSystemLocales as Array<string>, // bad types
|
||||||
|
availableLocales as Array<string>, // bad types
|
||||||
|
'en',
|
||||||
|
{ algorithm: 'best fit' }
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...localeOverrideMatches,
|
||||||
|
{
|
||||||
|
locale: null,
|
||||||
|
currentLocaleLabel: i18n('icu:Preferences__Language__SystemLanguage'),
|
||||||
|
matchingLocaleLabel: getLanguageLabel(
|
||||||
|
preferredSystemLocaleMatch,
|
||||||
|
preferredSystemLocaleMatch
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...localeOverrideNonMatches,
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
i18n,
|
||||||
|
availableLocales,
|
||||||
|
resolvedLocale,
|
||||||
|
localeOverride,
|
||||||
|
preferredSystemLocales,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const localeSearchResults = useMemo(() => {
|
||||||
|
return localeSearchOptions.filter(option => {
|
||||||
|
const input = removeDiacritics(languageSearchInput.trim().toLowerCase());
|
||||||
|
|
||||||
|
if (input === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMatch(value: string) {
|
||||||
|
return removeDiacritics(value.toLowerCase()).includes(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
isMatch(option.currentLocaleLabel) ||
|
||||||
|
(option.matchingLocaleLabel && isMatch(option.matchingLocaleLabel))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [localeSearchOptions, languageSearchInput]);
|
||||||
|
|
||||||
let settings: JSX.Element | undefined;
|
let settings: JSX.Element | undefined;
|
||||||
if (page === Page.General) {
|
if (page === Page.General) {
|
||||||
settings = (
|
settings = (
|
||||||
|
@ -504,6 +655,129 @@ export function Preferences({
|
||||||
</div>
|
</div>
|
||||||
<SettingsRow>
|
<SettingsRow>
|
||||||
<Control
|
<Control
|
||||||
|
icon="Preferences__LanguageIcon"
|
||||||
|
left={i18n('icu:Preferences__Language__Label')}
|
||||||
|
right={
|
||||||
|
<span
|
||||||
|
className="Preferences__LanguageButton"
|
||||||
|
lang={localeOverride ?? resolvedLocale}
|
||||||
|
>
|
||||||
|
{localeOverride != null
|
||||||
|
? getLanguageLabel(localeOverride, resolvedLocale)
|
||||||
|
: i18n('icu:Preferences__Language__SystemLanguage')}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setLanguageDialog(LanguageDialog.Selection);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{languageDialog === LanguageDialog.Selection && (
|
||||||
|
<Modal
|
||||||
|
i18n={i18n}
|
||||||
|
modalName="Preferences__LanguageModal"
|
||||||
|
moduleClassName="Preferences__LanguageModal"
|
||||||
|
padded={false}
|
||||||
|
onClose={closeLanguageDialog}
|
||||||
|
title={i18n('icu:Preferences__Language__ModalTitle')}
|
||||||
|
modalHeaderChildren={
|
||||||
|
<SearchInput
|
||||||
|
i18n={i18n}
|
||||||
|
value={languageSearchInput}
|
||||||
|
placeholder={i18n(
|
||||||
|
'icu:Preferences__Language__SearchLanguages'
|
||||||
|
)}
|
||||||
|
moduleClassName="Preferences__LanguageModal__SearchInput"
|
||||||
|
onChange={event => {
|
||||||
|
setLanguageSearchInput(event.currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
modalFooter={
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant={ButtonVariant.Secondary}
|
||||||
|
onClick={closeLanguageDialog}
|
||||||
|
>
|
||||||
|
{i18n('icu:cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={ButtonVariant.Primary}
|
||||||
|
disabled={selectedLanguageLocale === localeOverride}
|
||||||
|
onClick={() => {
|
||||||
|
setLanguageDialog(LanguageDialog.Confirmation);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__LanguageModal__Set')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{localeSearchResults.length === 0 && (
|
||||||
|
<div className="Preferences__LanguageModal__NoResults">
|
||||||
|
{i18n('icu:Preferences__Language__NoResults', {
|
||||||
|
searchTerm: languageSearchInput.trim(),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{localeSearchResults.map(option => {
|
||||||
|
const id = `${languageId}:${option.locale ?? 'system'}`;
|
||||||
|
const isSelected = option.locale === selectedLanguageLocale;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={id}
|
||||||
|
type="button"
|
||||||
|
className="Preferences__LanguageModal__Item"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedLanguageLocale(option.locale);
|
||||||
|
}}
|
||||||
|
aria-pressed={isSelected}
|
||||||
|
>
|
||||||
|
<span className="Preferences__LanguageModal__Item__Inner">
|
||||||
|
<span className="Preferences__LanguageModal__Item__Label">
|
||||||
|
<span className="Preferences__LanguageModal__Item__Current">
|
||||||
|
{option.currentLocaleLabel}
|
||||||
|
</span>
|
||||||
|
{option.matchingLocaleLabel != null && (
|
||||||
|
<span
|
||||||
|
lang={option.locale ?? resolvedLocale}
|
||||||
|
className="Preferences__LanguageModal__Item__Matching"
|
||||||
|
>
|
||||||
|
{option.matchingLocaleLabel}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{isSelected && (
|
||||||
|
<span className="Preferences__LanguageModal__Item__Check" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
{languageDialog === LanguageDialog.Confirmation && (
|
||||||
|
<ConfirmationDialog
|
||||||
|
dialogName="Preferences__Language"
|
||||||
|
i18n={i18n}
|
||||||
|
title={i18n('icu:Preferences__LanguageModal__Restart__Title')}
|
||||||
|
onCancel={closeLanguageDialog}
|
||||||
|
onClose={closeLanguageDialog}
|
||||||
|
cancelText={i18n('icu:cancel')}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
text: i18n('icu:Preferences__LanguageModal__Restart__Button'),
|
||||||
|
style: 'affirmative',
|
||||||
|
action: () => {
|
||||||
|
onLocaleChange(selectedLanguageLocale);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__LanguageModal__Restart__Description')}
|
||||||
|
</ConfirmationDialog>
|
||||||
|
)}
|
||||||
|
<Control
|
||||||
|
icon
|
||||||
left={
|
left={
|
||||||
<label htmlFor={themeSelectId}>
|
<label htmlFor={themeSelectId}>
|
||||||
{i18n('icu:Preferences--theme')}
|
{i18n('icu:Preferences--theme')}
|
||||||
|
@ -532,6 +806,7 @@ export function Preferences({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Control
|
<Control
|
||||||
|
icon
|
||||||
left={i18n('icu:showChatColorEditor')}
|
left={i18n('icu:showChatColorEditor')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPage(Page.ChatColor);
|
setPage(Page.ChatColor);
|
||||||
|
@ -548,6 +823,7 @@ export function Preferences({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Control
|
<Control
|
||||||
|
icon
|
||||||
left={
|
left={
|
||||||
<label htmlFor={zoomSelectId}>
|
<label htmlFor={zoomSelectId}>
|
||||||
{i18n('icu:Preferences--zoom')}
|
{i18n('icu:Preferences--zoom')}
|
||||||
|
@ -1307,6 +1583,7 @@ export function Preferences({
|
||||||
>
|
>
|
||||||
{i18n('icu:Preferences__button--notifications')}
|
{i18n('icu:Preferences__button--notifications')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames({
|
className={classNames({
|
||||||
|
@ -1346,16 +1623,27 @@ function SettingsRow({
|
||||||
}
|
}
|
||||||
|
|
||||||
function Control({
|
function Control({
|
||||||
|
icon,
|
||||||
left,
|
left,
|
||||||
onClick,
|
onClick,
|
||||||
right,
|
right,
|
||||||
}: {
|
}: {
|
||||||
|
/** A className or `true` to leave room for icon */
|
||||||
|
icon?: string | true;
|
||||||
left: ReactNode;
|
left: ReactNode;
|
||||||
onClick?: () => unknown;
|
onClick?: () => unknown;
|
||||||
right: ReactNode;
|
right: ReactNode;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
|
{icon && (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'Preferences__control--icon',
|
||||||
|
icon === true ? null : icon
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="Preferences__control--key">{left}</div>
|
<div className="Preferences__control--key">{left}</div>
|
||||||
<div className="Preferences__control--value">{right}</div>
|
<div className="Preferences__control--value">{right}</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -19,6 +19,7 @@ const EPHEMERAL_NAME_MAP = new Map([
|
||||||
['spellCheck', 'spell-check'],
|
['spellCheck', 'spell-check'],
|
||||||
['systemTraySetting', 'system-tray-setting'],
|
['systemTraySetting', 'system-tray-setting'],
|
||||||
['themeSetting', 'theme-setting'],
|
['themeSetting', 'theme-setting'],
|
||||||
|
['localeOverride', 'localeOverride'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type ResponseQueueEntry = Readonly<{
|
type ResponseQueueEntry = Readonly<{
|
||||||
|
@ -79,6 +80,9 @@ export class SettingsChannel extends EventEmitter {
|
||||||
isEphemeral: true,
|
isEphemeral: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.installSetting('localeOverride', {
|
||||||
|
isEphemeral: true,
|
||||||
|
});
|
||||||
this.installSetting('notificationSetting');
|
this.installSetting('notificationSetting');
|
||||||
this.installSetting('notificationDrawAttention');
|
this.installSetting('notificationDrawAttention');
|
||||||
this.installSetting('audioMessage');
|
this.installSetting('audioMessage');
|
||||||
|
|
|
@ -32,5 +32,5 @@ export async function deleteAllData(): Promise<void> {
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
window.IPC.restart();
|
window.SignalContext.restartApp();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ describe('locale', async () => {
|
||||||
) {
|
) {
|
||||||
const actualLocale = await load({
|
const actualLocale = await load({
|
||||||
preferredSystemLocales,
|
preferredSystemLocales,
|
||||||
|
localeOverride: null,
|
||||||
hourCyclePreference: HourCyclePreference.UnknownPreference,
|
hourCyclePreference: HourCyclePreference.UnknownPreference,
|
||||||
logger,
|
logger,
|
||||||
});
|
});
|
||||||
|
|
|
@ -199,6 +199,7 @@ const PLATFORMS = [
|
||||||
describe('createTemplate', () => {
|
describe('createTemplate', () => {
|
||||||
const { i18n } = loadLocale({
|
const { i18n } = loadLocale({
|
||||||
preferredSystemLocales: ['en'],
|
preferredSystemLocales: ['en'],
|
||||||
|
localeOverride: null,
|
||||||
hourCyclePreference: HourCyclePreference.UnknownPreference,
|
hourCyclePreference: HourCyclePreference.UnknownPreference,
|
||||||
logger: {
|
logger: {
|
||||||
fatal: stub().throwsArg(0),
|
fatal: stub().throwsArg(0),
|
||||||
|
|
|
@ -46,6 +46,7 @@ export const rendererConfigSchema = z.object({
|
||||||
installPath: configRequiredStringSchema,
|
installPath: configRequiredStringSchema,
|
||||||
osRelease: configRequiredStringSchema,
|
osRelease: configRequiredStringSchema,
|
||||||
osVersion: configRequiredStringSchema,
|
osVersion: configRequiredStringSchema,
|
||||||
|
availableLocales: z.array(configRequiredStringSchema),
|
||||||
resolvedTranslationsLocale: configRequiredStringSchema,
|
resolvedTranslationsLocale: configRequiredStringSchema,
|
||||||
resolvedTranslationsLocaleDirection: z.enum(['ltr', 'rtl']),
|
resolvedTranslationsLocaleDirection: z.enum(['ltr', 'rtl']),
|
||||||
hourCyclePreference: HourCyclePreferenceSchema,
|
hourCyclePreference: HourCyclePreferenceSchema,
|
||||||
|
|
1
ts/types/Storage.d.ts
vendored
1
ts/types/Storage.d.ts
vendored
|
@ -56,6 +56,7 @@ export type StorageAccessType = {
|
||||||
'call-system-notification': boolean;
|
'call-system-notification': boolean;
|
||||||
'hide-menu-bar': boolean;
|
'hide-menu-bar': boolean;
|
||||||
'incoming-call-notification': boolean;
|
'incoming-call-notification': boolean;
|
||||||
|
localeOverride: string | null;
|
||||||
'notification-draw-attention': boolean;
|
'notification-draw-attention': boolean;
|
||||||
'notification-setting': NotificationSettingType;
|
'notification-setting': NotificationSettingType;
|
||||||
'read-receipt-setting': boolean;
|
'read-receipt-setting': boolean;
|
||||||
|
|
|
@ -61,6 +61,7 @@ export type IPCEventsValuesType = {
|
||||||
hideMenuBar: boolean | undefined;
|
hideMenuBar: boolean | undefined;
|
||||||
incomingCallNotification: boolean;
|
incomingCallNotification: boolean;
|
||||||
lastSyncTime: number | undefined;
|
lastSyncTime: number | undefined;
|
||||||
|
localeOverride: string | null;
|
||||||
notificationDrawAttention: boolean;
|
notificationDrawAttention: boolean;
|
||||||
notificationSetting: NotificationSettingType;
|
notificationSetting: NotificationSettingType;
|
||||||
preferredAudioInputDevice: AudioDevice | undefined;
|
preferredAudioInputDevice: AudioDevice | undefined;
|
||||||
|
@ -372,6 +373,12 @@ export function createIPCEvents(
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getLocaleOverride: () => {
|
||||||
|
return window.storage.get('localeOverride') ?? null;
|
||||||
|
},
|
||||||
|
setLocaleOverride: async (locale: string | null) => {
|
||||||
|
await window.storage.put('localeOverride', locale);
|
||||||
|
},
|
||||||
getNotificationSetting: () =>
|
getNotificationSetting: () =>
|
||||||
window.storage.get('notification-setting', 'message'),
|
window.storage.get('notification-setting', 'message'),
|
||||||
setNotificationSetting: (value: 'message' | 'name' | 'count' | 'off') =>
|
setNotificationSetting: (value: 'message' | 'name' | 'count' | 'off') =>
|
||||||
|
|
1
ts/window.d.ts
vendored
1
ts/window.d.ts
vendored
|
@ -74,7 +74,6 @@ export type IPCType = {
|
||||||
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
|
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
|
||||||
readyForUpdates: () => void;
|
readyForUpdates: () => void;
|
||||||
removeSetupMenuItems: () => unknown;
|
removeSetupMenuItems: () => unknown;
|
||||||
restart: () => void;
|
|
||||||
setAutoHideMenuBar: (value: boolean) => void;
|
setAutoHideMenuBar: (value: boolean) => void;
|
||||||
setAutoLaunch: (value: boolean) => Promise<void>;
|
setAutoLaunch: (value: boolean) => Promise<void>;
|
||||||
setBadge: (badge: number | 'marked-unread') => void;
|
setBadge: (badge: number | 'marked-unread') => void;
|
||||||
|
|
|
@ -41,6 +41,7 @@ export type MinimalSignalContextType = {
|
||||||
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
|
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
|
||||||
getAppInstance: () => string | undefined;
|
getAppInstance: () => string | undefined;
|
||||||
getEnvironment: () => string;
|
getEnvironment: () => string;
|
||||||
|
getI18nAvailableLocales: () => ReadonlyArray<string>;
|
||||||
getI18nLocale: LocalizerType['getLocale'];
|
getI18nLocale: LocalizerType['getLocale'];
|
||||||
getI18nLocaleMessages: LocalizerType['getLocaleMessages'];
|
getI18nLocaleMessages: LocalizerType['getLocaleMessages'];
|
||||||
getResolvedMessagesLocaleDirection: () => LocaleDirection;
|
getResolvedMessagesLocaleDirection: () => LocaleDirection;
|
||||||
|
@ -53,6 +54,7 @@ export type MinimalSignalContextType = {
|
||||||
getPath: (name: 'userData' | 'home' | 'install') => string;
|
getPath: (name: 'userData' | 'home' | 'install') => string;
|
||||||
getVersion: () => string;
|
getVersion: () => string;
|
||||||
nativeThemeListener: NativeThemeType;
|
nativeThemeListener: NativeThemeType;
|
||||||
|
restartApp: () => void;
|
||||||
Settings: {
|
Settings: {
|
||||||
themeSetting: SettingType<IPCEventsValuesType['themeSetting']>;
|
themeSetting: SettingType<IPCEventsValuesType['themeSetting']>;
|
||||||
waitForChange: () => Promise<void>;
|
waitForChange: () => Promise<void>;
|
||||||
|
|
|
@ -99,10 +99,6 @@ const IPC: IPCType = {
|
||||||
}),
|
}),
|
||||||
readyForUpdates: () => ipc.send('ready-for-updates'),
|
readyForUpdates: () => ipc.send('ready-for-updates'),
|
||||||
removeSetupMenuItems: () => ipc.send('remove-setup-menu-items'),
|
removeSetupMenuItems: () => ipc.send('remove-setup-menu-items'),
|
||||||
restart: () => {
|
|
||||||
log.info('restart');
|
|
||||||
ipc.send('restart');
|
|
||||||
},
|
|
||||||
setAutoHideMenuBar: autoHide => ipc.send('set-auto-hide-menu-bar', autoHide),
|
setAutoHideMenuBar: autoHide => ipc.send('set-auto-hide-menu-bar', autoHide),
|
||||||
setAutoLaunch: value => ipc.invoke('set-auto-launch', value),
|
setAutoLaunch: value => ipc.invoke('set-auto-launch', value),
|
||||||
setBadge: badge => ipc.send('set-badge', badge),
|
setBadge: badge => ipc.send('set-badge', badge),
|
||||||
|
|
|
@ -40,6 +40,7 @@ export const MinimalSignalContext: MinimalSignalContextType = {
|
||||||
async getMenuOptions(): Promise<MenuOptionsType> {
|
async getMenuOptions(): Promise<MenuOptionsType> {
|
||||||
return ipcRenderer.invoke('getMenuOptions');
|
return ipcRenderer.invoke('getMenuOptions');
|
||||||
},
|
},
|
||||||
|
getI18nAvailableLocales: () => config.availableLocales,
|
||||||
getI18nLocale: () => config.resolvedTranslationsLocale,
|
getI18nLocale: () => config.resolvedTranslationsLocale,
|
||||||
getI18nLocaleMessages: () => localeMessages,
|
getI18nLocaleMessages: () => localeMessages,
|
||||||
|
|
||||||
|
@ -48,8 +49,8 @@ export const MinimalSignalContext: MinimalSignalContextType = {
|
||||||
config.resolvedTranslationsLocaleDirection,
|
config.resolvedTranslationsLocaleDirection,
|
||||||
getHourCyclePreference: () => config.hourCyclePreference,
|
getHourCyclePreference: () => config.hourCyclePreference,
|
||||||
getPreferredSystemLocales: () => config.preferredSystemLocales,
|
getPreferredSystemLocales: () => config.preferredSystemLocales,
|
||||||
|
|
||||||
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
|
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
|
||||||
|
restartApp: () => ipcRenderer.send('restart'),
|
||||||
OS: {
|
OS: {
|
||||||
getClassName: () => ipcRenderer.sendSync('OS.getClassName'),
|
getClassName: () => ipcRenderer.sendSync('OS.getClassName'),
|
||||||
hasCustomTitleBar: () => hasCustomTitleBar,
|
hasCustomTitleBar: () => hasCustomTitleBar,
|
||||||
|
|
|
@ -50,6 +50,7 @@ installSetting('hasStoriesDisabled');
|
||||||
installSetting('hideMenuBar');
|
installSetting('hideMenuBar');
|
||||||
installSetting('incomingCallNotification');
|
installSetting('incomingCallNotification');
|
||||||
installSetting('lastSyncTime');
|
installSetting('lastSyncTime');
|
||||||
|
installSetting('localeOverride');
|
||||||
installSetting('notificationDrawAttention');
|
installSetting('notificationDrawAttention');
|
||||||
installSetting('notificationSetting');
|
installSetting('notificationSetting');
|
||||||
installSetting('spellCheck');
|
installSetting('spellCheck');
|
||||||
|
|
|
@ -20,6 +20,7 @@ SettingsWindowProps.onRender(
|
||||||
({
|
({
|
||||||
addCustomColor,
|
addCustomColor,
|
||||||
availableCameras,
|
availableCameras,
|
||||||
|
availableLocales,
|
||||||
availableMicrophones,
|
availableMicrophones,
|
||||||
availableSpeakers,
|
availableSpeakers,
|
||||||
blockedCount,
|
blockedCount,
|
||||||
|
@ -78,6 +79,7 @@ SettingsWindowProps.onRender(
|
||||||
onHideMenuBarChange,
|
onHideMenuBarChange,
|
||||||
onIncomingCallNotificationsChange,
|
onIncomingCallNotificationsChange,
|
||||||
onLastSyncTimeChange,
|
onLastSyncTimeChange,
|
||||||
|
onLocaleChange,
|
||||||
onMediaCameraPermissionsChange,
|
onMediaCameraPermissionsChange,
|
||||||
onMediaPermissionsChange,
|
onMediaPermissionsChange,
|
||||||
onMessageAudioChange,
|
onMessageAudioChange,
|
||||||
|
@ -98,16 +100,19 @@ SettingsWindowProps.onRender(
|
||||||
onWhoCanFindMeChange,
|
onWhoCanFindMeChange,
|
||||||
onWhoCanSeeMeChange,
|
onWhoCanSeeMeChange,
|
||||||
onZoomFactorChange,
|
onZoomFactorChange,
|
||||||
|
preferredSystemLocales,
|
||||||
removeCustomColor,
|
removeCustomColor,
|
||||||
removeCustomColorOnConversations,
|
removeCustomColorOnConversations,
|
||||||
resetAllChatColors,
|
resetAllChatColors,
|
||||||
resetDefaultChatColor,
|
resetDefaultChatColor,
|
||||||
|
resolvedLocale,
|
||||||
selectedCamera,
|
selectedCamera,
|
||||||
selectedMicrophone,
|
selectedMicrophone,
|
||||||
selectedSpeaker,
|
selectedSpeaker,
|
||||||
sentMediaQualitySetting,
|
sentMediaQualitySetting,
|
||||||
setGlobalDefaultConversationColor,
|
setGlobalDefaultConversationColor,
|
||||||
shouldShowStoriesSettings,
|
shouldShowStoriesSettings,
|
||||||
|
localeOverride,
|
||||||
themeSetting,
|
themeSetting,
|
||||||
universalExpireTimer,
|
universalExpireTimer,
|
||||||
whoCanFindMe,
|
whoCanFindMe,
|
||||||
|
@ -118,6 +123,7 @@ SettingsWindowProps.onRender(
|
||||||
<Preferences
|
<Preferences
|
||||||
addCustomColor={addCustomColor}
|
addCustomColor={addCustomColor}
|
||||||
availableCameras={availableCameras}
|
availableCameras={availableCameras}
|
||||||
|
availableLocales={availableLocales}
|
||||||
availableMicrophones={availableMicrophones}
|
availableMicrophones={availableMicrophones}
|
||||||
availableSpeakers={availableSpeakers}
|
availableSpeakers={availableSpeakers}
|
||||||
blockedCount={blockedCount}
|
blockedCount={blockedCount}
|
||||||
|
@ -167,6 +173,7 @@ SettingsWindowProps.onRender(
|
||||||
isSyncSupported={isSyncSupported}
|
isSyncSupported={isSyncSupported}
|
||||||
isSystemTraySupported={isSystemTraySupported}
|
isSystemTraySupported={isSystemTraySupported}
|
||||||
lastSyncTime={lastSyncTime}
|
lastSyncTime={lastSyncTime}
|
||||||
|
localeOverride={localeOverride}
|
||||||
makeSyncRequest={makeSyncRequest}
|
makeSyncRequest={makeSyncRequest}
|
||||||
notificationContent={notificationContent}
|
notificationContent={notificationContent}
|
||||||
onAudioNotificationsChange={onAudioNotificationsChange}
|
onAudioNotificationsChange={onAudioNotificationsChange}
|
||||||
|
@ -179,6 +186,7 @@ SettingsWindowProps.onRender(
|
||||||
onHideMenuBarChange={onHideMenuBarChange}
|
onHideMenuBarChange={onHideMenuBarChange}
|
||||||
onIncomingCallNotificationsChange={onIncomingCallNotificationsChange}
|
onIncomingCallNotificationsChange={onIncomingCallNotificationsChange}
|
||||||
onLastSyncTimeChange={onLastSyncTimeChange}
|
onLastSyncTimeChange={onLastSyncTimeChange}
|
||||||
|
onLocaleChange={onLocaleChange}
|
||||||
onMediaCameraPermissionsChange={onMediaCameraPermissionsChange}
|
onMediaCameraPermissionsChange={onMediaCameraPermissionsChange}
|
||||||
onMediaPermissionsChange={onMediaPermissionsChange}
|
onMediaPermissionsChange={onMediaPermissionsChange}
|
||||||
onMessageAudioChange={onMessageAudioChange}
|
onMessageAudioChange={onMessageAudioChange}
|
||||||
|
@ -201,10 +209,12 @@ SettingsWindowProps.onRender(
|
||||||
onWhoCanFindMeChange={onWhoCanFindMeChange}
|
onWhoCanFindMeChange={onWhoCanFindMeChange}
|
||||||
onWhoCanSeeMeChange={onWhoCanSeeMeChange}
|
onWhoCanSeeMeChange={onWhoCanSeeMeChange}
|
||||||
onZoomFactorChange={onZoomFactorChange}
|
onZoomFactorChange={onZoomFactorChange}
|
||||||
|
preferredSystemLocales={preferredSystemLocales}
|
||||||
removeCustomColorOnConversations={removeCustomColorOnConversations}
|
removeCustomColorOnConversations={removeCustomColorOnConversations}
|
||||||
removeCustomColor={removeCustomColor}
|
removeCustomColor={removeCustomColor}
|
||||||
resetAllChatColors={resetAllChatColors}
|
resetAllChatColors={resetAllChatColors}
|
||||||
resetDefaultChatColor={resetDefaultChatColor}
|
resetDefaultChatColor={resetDefaultChatColor}
|
||||||
|
resolvedLocale={resolvedLocale}
|
||||||
selectedCamera={selectedCamera}
|
selectedCamera={selectedCamera}
|
||||||
selectedMicrophone={selectedMicrophone}
|
selectedMicrophone={selectedMicrophone}
|
||||||
selectedSpeaker={selectedSpeaker}
|
selectedSpeaker={selectedSpeaker}
|
||||||
|
|
|
@ -46,6 +46,7 @@ const settingSpellCheck = createSetting('spellCheck');
|
||||||
const settingTextFormatting = createSetting('textFormatting');
|
const settingTextFormatting = createSetting('textFormatting');
|
||||||
const settingTheme = createSetting('themeSetting');
|
const settingTheme = createSetting('themeSetting');
|
||||||
const settingSystemTraySetting = createSetting('systemTraySetting');
|
const settingSystemTraySetting = createSetting('systemTraySetting');
|
||||||
|
const settingLocaleOverride = createSetting('localeOverride');
|
||||||
|
|
||||||
const settingLastSyncTime = createSetting('lastSyncTime');
|
const settingLastSyncTime = createSetting('lastSyncTime');
|
||||||
|
|
||||||
|
@ -169,6 +170,7 @@ async function renderPreferences() {
|
||||||
selectedMicrophone,
|
selectedMicrophone,
|
||||||
selectedSpeaker,
|
selectedSpeaker,
|
||||||
sentMediaQualitySetting,
|
sentMediaQualitySetting,
|
||||||
|
localeOverride,
|
||||||
systemTraySetting,
|
systemTraySetting,
|
||||||
themeSetting,
|
themeSetting,
|
||||||
universalExpireTimer,
|
universalExpireTimer,
|
||||||
|
@ -210,6 +212,7 @@ async function renderPreferences() {
|
||||||
selectedMicrophone: settingAudioInput.getValue(),
|
selectedMicrophone: settingAudioInput.getValue(),
|
||||||
selectedSpeaker: settingAudioOutput.getValue(),
|
selectedSpeaker: settingAudioOutput.getValue(),
|
||||||
sentMediaQualitySetting: settingSentMediaQuality.getValue(),
|
sentMediaQualitySetting: settingSentMediaQuality.getValue(),
|
||||||
|
localeOverride: settingLocaleOverride.getValue(),
|
||||||
systemTraySetting: settingSystemTraySetting.getValue(),
|
systemTraySetting: settingSystemTraySetting.getValue(),
|
||||||
themeSetting: settingTheme.getValue(),
|
themeSetting: settingTheme.getValue(),
|
||||||
universalExpireTimer: settingUniversalExpireTimer.getValue(),
|
universalExpireTimer: settingUniversalExpireTimer.getValue(),
|
||||||
|
@ -236,9 +239,15 @@ async function renderPreferences() {
|
||||||
settingUniversalExpireTimer.setValue
|
settingUniversalExpireTimer.setValue
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const availableLocales = MinimalSignalContext.getI18nAvailableLocales();
|
||||||
|
const resolvedLocale = MinimalSignalContext.getI18nLocale();
|
||||||
|
const preferredSystemLocales =
|
||||||
|
MinimalSignalContext.getPreferredSystemLocales();
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
// Settings
|
// Settings
|
||||||
availableCameras,
|
availableCameras,
|
||||||
|
availableLocales,
|
||||||
availableMicrophones,
|
availableMicrophones,
|
||||||
availableSpeakers,
|
availableSpeakers,
|
||||||
blockedCount,
|
blockedCount,
|
||||||
|
@ -268,7 +277,10 @@ async function renderPreferences() {
|
||||||
hasTextFormatting,
|
hasTextFormatting,
|
||||||
hasTypingIndicators,
|
hasTypingIndicators,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
|
localeOverride,
|
||||||
notificationContent,
|
notificationContent,
|
||||||
|
preferredSystemLocales,
|
||||||
|
resolvedLocale,
|
||||||
selectedCamera,
|
selectedCamera,
|
||||||
selectedMicrophone,
|
selectedMicrophone,
|
||||||
selectedSpeaker,
|
selectedSpeaker,
|
||||||
|
@ -347,6 +359,10 @@ async function renderPreferences() {
|
||||||
settingIncomingCallNotification.setValue
|
settingIncomingCallNotification.setValue
|
||||||
),
|
),
|
||||||
onLastSyncTimeChange: attachRenderCallback(settingLastSyncTime.setValue),
|
onLastSyncTimeChange: attachRenderCallback(settingLastSyncTime.setValue),
|
||||||
|
onLocaleChange: async (locale: string | null) => {
|
||||||
|
await settingLocaleOverride.setValue(locale);
|
||||||
|
MinimalSignalContext.restartApp();
|
||||||
|
},
|
||||||
onMediaCameraPermissionsChange: attachRenderCallback(
|
onMediaCameraPermissionsChange: attachRenderCallback(
|
||||||
settingMediaCameraPermissions.setValue
|
settingMediaCameraPermissions.setValue
|
||||||
),
|
),
|
||||||
|
|
43
yarn.lock
43
yarn.lock
|
@ -1629,6 +1629,14 @@
|
||||||
"@formatjs/intl-localematcher" "0.2.32"
|
"@formatjs/intl-localematcher" "0.2.32"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
|
"@formatjs/ecma402-abstract@1.15.0":
|
||||||
|
version "1.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.15.0.tgz#0a285a5dc69889e15d53803bd5036272e23e5a18"
|
||||||
|
integrity sha512-7bAYAv0w4AIao9DNg0avfOLTCPE9woAgs6SpXuMq11IN3A+l+cq8ghczwqSZBM11myvPSJA7vLn72q0rJ0QK6Q==
|
||||||
|
dependencies:
|
||||||
|
"@formatjs/intl-localematcher" "0.2.32"
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/fast-memoize@1.2.1":
|
"@formatjs/fast-memoize@1.2.1":
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz#e6f5aee2e4fd0ca5edba6eba7668e2d855e0fc21"
|
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz#e6f5aee2e4fd0ca5edba6eba7668e2d855e0fc21"
|
||||||
|
@ -1684,13 +1692,13 @@
|
||||||
"@formatjs/icu-skeleton-parser" "1.3.18"
|
"@formatjs/icu-skeleton-parser" "1.3.18"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/icu-messageformat-parser@2.3.1":
|
"@formatjs/icu-messageformat-parser@2.4.0":
|
||||||
version "2.3.1"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.1.tgz#953080ea5c053bc73bdf55d0a524a3c3c133ae6b"
|
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.4.0.tgz#e165f3594c68416ce15f63793768251de2a85f88"
|
||||||
integrity sha512-knF2AkAKN4Upv4oIiKY4Wd/dLH68TNMPgV/tJMu/T6FP9aQwbv8fpj7U3lkyniPaNVxvia56Gxax8MKOjtxLSQ==
|
integrity sha512-6Dh5Z/gp4F/HovXXu/vmd0If5NbYLB5dZrmhWVNb+BOGOEU3wt7Z/83KY1dtd7IDhAnYHasbmKE1RbTE0J+3hw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.14.3"
|
"@formatjs/ecma402-abstract" "1.15.0"
|
||||||
"@formatjs/icu-skeleton-parser" "1.3.18"
|
"@formatjs/icu-skeleton-parser" "1.4.0"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/icu-skeleton-parser@1.3.13":
|
"@formatjs/icu-skeleton-parser@1.3.13":
|
||||||
|
@ -1717,6 +1725,14 @@
|
||||||
"@formatjs/ecma402-abstract" "1.11.4"
|
"@formatjs/ecma402-abstract" "1.11.4"
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
|
"@formatjs/icu-skeleton-parser@1.4.0":
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.4.0.tgz#96342eca7c4eef7a309875569e5da973db3465e6"
|
||||||
|
integrity sha512-Qq347VM616rVLkvN6QsKJELazRyNlbCiN47LdH0Mc5U7E2xV0vatiVhGqd3KFgbc055BvtnUXR7XX60dCGFuWg==
|
||||||
|
dependencies:
|
||||||
|
"@formatjs/ecma402-abstract" "1.15.0"
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@formatjs/intl-displaynames@6.1.3":
|
"@formatjs/intl-displaynames@6.1.3":
|
||||||
version "6.1.3"
|
version "6.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.1.3.tgz#c9d283db518cd721c0855e9854bfadb9ba304b6a"
|
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.1.3.tgz#c9d283db518cd721c0855e9854bfadb9ba304b6a"
|
||||||
|
@ -11789,13 +11805,13 @@ intl-messageformat@10.3.1:
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
intl-messageformat@^10.1.0:
|
intl-messageformat@^10.1.0:
|
||||||
version "10.3.4"
|
version "10.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.3.4.tgz#20f064c28b46fa6d352a4c4ba5e9bfc597af3eba"
|
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.3.5.tgz#f55684fc663e62616ad59d3a504ea0cac3f267b7"
|
||||||
integrity sha512-/FxUIrlbPtuykSNX85CB5sp2FjLVeTmdD7TfRkVFPft2n4FgcSlAcilFytYiFAEmPHc+0PvpLCIPXeaGFzIvOg==
|
integrity sha512-6kPkftF8Jg3XJCkGKa5OD+nYQ+qcSxF4ZkuDdXZ6KGG0VXn+iblJqRFyDdm9VvKcMyC0Km2+JlVQffFM52D0YA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formatjs/ecma402-abstract" "1.14.3"
|
"@formatjs/ecma402-abstract" "1.15.0"
|
||||||
"@formatjs/fast-memoize" "2.0.1"
|
"@formatjs/fast-memoize" "2.0.1"
|
||||||
"@formatjs/icu-messageformat-parser" "2.3.1"
|
"@formatjs/icu-messageformat-parser" "2.4.0"
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
intl-messageformat@^9.3.19:
|
intl-messageformat@^9.3.19:
|
||||||
|
@ -18836,6 +18852,11 @@ thread-stream@^2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
real-require "^0.2.0"
|
real-require "^0.2.0"
|
||||||
|
|
||||||
|
throttle-debounce@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb"
|
||||||
|
integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
|
||||||
|
|
||||||
through2-filter@^3.0.0:
|
through2-filter@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
|
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue