[Gekidou] Show alert message when server is unsupported (#6764)

* Show alert message when server is unsupported

* set server min required version to 5.26.2
This commit is contained in:
Elias Nahum
2022-11-18 12:18:11 +02:00
committed by GitHub
parent 6dd7fc9fc5
commit 2a6bb1ddc1
15 changed files with 199 additions and 151 deletions

View File

@@ -40,35 +40,55 @@ export async function storeConfig(serverUrl: string, config: ClientConfig | unde
if (!config) {
return [];
}
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const currentConfig = await getConfig(database);
const configsToUpdate: IdValue[] = [];
const configsToDelete: IdValue[] = [];
let k: keyof ClientConfig;
for (k in config) {
if (currentConfig?.[k] !== config[k]) {
configsToUpdate.push({
id: k,
value: config[k],
});
}
}
for (k in currentConfig) {
if (config[k] === undefined) {
configsToDelete.push({
id: k,
value: currentConfig[k],
});
}
}
try {
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const currentConfig = await getConfig(database);
const configsToUpdate: IdValue[] = [];
const configsToDelete: IdValue[] = [];
if (configsToDelete.length || configsToUpdate.length) {
return operator.handleConfigs({configs: configsToUpdate, configsToDelete, prepareRecordsOnly});
let k: keyof ClientConfig;
for (k in config) {
if (currentConfig?.[k] !== config[k]) {
configsToUpdate.push({
id: k,
value: config[k],
});
}
}
for (k in currentConfig) {
if (config[k] === undefined) {
configsToDelete.push({
id: k,
value: currentConfig[k],
});
}
}
if (configsToDelete.length || configsToUpdate.length) {
return operator.handleConfigs({configs: configsToUpdate, configsToDelete, prepareRecordsOnly});
}
} catch (error) {
logError('storeConfig', error);
}
return [];
}
export async function setLastServerVersionCheck(serverUrl: string, reset = false) {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
await operator.handleSystem({
systems: [{
id: SYSTEM_IDENTIFIERS.LAST_SERVER_VERSION_CHECK,
value: reset ? 0 : Date.now(),
}],
prepareRecordsOnly: false,
});
} catch (error) {
logError('setLastServerVersionCheck', error);
}
}
export async function dismissAnnouncement(serverUrl: string, announcementText: string) {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {setLastServerVersionCheck} from '@actions/local/systems';
import {switchToChannelById} from '@actions/remote/channel';
import {fetchConfigAndLicense} from '@actions/remote/systems';
import DatabaseManager from '@database/manager';
@@ -21,6 +22,9 @@ export async function appEntry(serverUrl: string, since = 0, isUpgrade = false)
if (!since) {
registerDeviceToken(serverUrl);
if (Object.keys(DatabaseManager.serverDatabases).length === 1) {
await setLastServerVersionCheck(serverUrl, true);
}
}
// clear lastUnreadChannelId

View File

@@ -4,47 +4,60 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {useEffect} from 'react';
import {useIntl} from 'react-intl';
import {map} from 'rxjs/operators';
import {IntlShape, useIntl} from 'react-intl';
import {distinctUntilChanged, map} from 'rxjs/operators';
import {SupportedServer} from '@constants';
import {observeConfigValue} from '@queries/servers/system';
import {setLastServerVersionCheck} from '@actions/local/systems';
import {useServerUrl} from '@context/server';
import DatabaseManager from '@database/manager';
import {queryServer} from '@queries/app/servers';
import {observeConfigValue, observeLastServerVersionCheck} from '@queries/servers/system';
import {observeCurrentUser} from '@queries/servers/user';
import {isMinimumServerVersion} from '@utils/helpers';
import {unsupportedServer} from '@utils/server';
import {isSupportedServer, unsupportedServer} from '@utils/server';
import {isSystemAdmin} from '@utils/user';
import type {WithDatabaseArgs} from '@typings/database/database';
type ServerVersionProps = WithDatabaseArgs & {
type ServerVersionProps = {
isAdmin: boolean;
lastChecked: number;
version?: string;
roles: string;
};
const ServerVersion = ({version, roles}: ServerVersionProps) => {
const VALIDATE_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
const handleUnsupportedServer = async (serverUrl: string, isAdmin: boolean, intl: IntlShape) => {
const appDatabase = DatabaseManager.appDatabase?.database;
if (appDatabase) {
const serverModel = await queryServer(appDatabase, serverUrl);
unsupportedServer(serverModel?.displayName || '', isAdmin, intl);
setLastServerVersionCheck(serverUrl);
}
};
const ServerVersion = ({isAdmin, lastChecked, version}: ServerVersionProps) => {
const intl = useIntl();
const serverUrl = useServerUrl();
useEffect(() => {
const serverVersion = version || '';
const shouldValidate = (Date.now() - lastChecked) >= VALIDATE_INTERVAL || !lastChecked;
if (serverVersion) {
const {MAJOR_VERSION, MIN_VERSION, PATCH_VERSION} = SupportedServer;
const isSupportedServer = isMinimumServerVersion(serverVersion, MAJOR_VERSION, MIN_VERSION, PATCH_VERSION);
if (!isSupportedServer) {
// Only display the Alert if the TOS does not need to show first
unsupportedServer(isSystemAdmin(roles), intl);
}
if (serverVersion && shouldValidate && !isSupportedServer(serverVersion)) {
// Only display the Alert if the TOS does not need to show first
handleUnsupportedServer(serverUrl, isAdmin, intl);
}
}, [version, roles]);
}, [version, isAdmin, lastChecked, serverUrl]);
return null;
};
const enahanced = withObservables([], ({database}: WithDatabaseArgs) => ({
lastChecked: observeLastServerVersionCheck(database),
version: observeConfigValue(database, 'Version'),
roles: observeCurrentUser(database).pipe(
map((user) => user?.roles),
isAdmin: observeCurrentUser(database).pipe(
map((user) => isSystemAdmin(user?.roles || '')),
distinctUntilChanged(),
),
}));

View File

@@ -59,6 +59,7 @@ export const SYSTEM_IDENTIFIERS = {
DATA_RETENTION_POLICIES: 'dataRetentionPolicies',
EXPANDED_LINKS: 'expandedLinks',
LAST_DISMISSED_BANNER: 'lastDismissedBanner',
LAST_SERVER_VERSION_CHECK: 'LastServerVersionCheck',
LICENSE: 'license',
ONLY_UNREADS: 'onlyUnreads',
PUSH_VERIFICATION_STATUS: 'pushVerificationStatus',

View File

@@ -1,12 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const FULL_VERSION = '6.0.0';
export const MAJOR_VERSION = 0;
export const MIN_VERSION = 0;
export const MIN_REQUIRED_VERSION = '5.26.2';
export const FULL_VERSION = '7.1.0';
export const MAJOR_VERSION = 7;
export const MIN_VERSION = 1;
export const PATCH_VERSION = 0;
export default {
MIN_REQUIRED_VERSION,
FULL_VERSION,
MAJOR_VERSION,
MIN_VERSION,

View File

@@ -8,6 +8,7 @@ import semver from 'semver';
import {autoUpdateTimezone} from '@actions/remote/user';
import LocalConfig from '@assets/config.json';
import {Events, Sso} from '@constants';
import {MIN_REQUIRED_VERSION} from '@constants/supported_server';
import DatabaseManager from '@database/manager';
import {DEFAULT_LOCALE, getTranslations, t} from '@i18n';
import {getServerCredentials} from '@init/credentials';
@@ -81,7 +82,7 @@ class GlobalEventHandler {
const translations = getTranslations(locale);
if (version) {
if (semver.valid(version) && semver.lt(version, LocalConfig.MinServerVersion)) {
if (semver.valid(version) && semver.lt(version, MIN_REQUIRED_VERSION)) {
Alert.alert(
translations[t('mobile.server_upgrade.title')],
translations[t('mobile.server_upgrade.description')],

View File

@@ -466,3 +466,10 @@ export const observeCanUploadFiles = (database: Database) => {
),
);
};
export const observeLastServerVersionCheck = (database: Database) => {
return querySystemValue(database, SYSTEM_IDENTIFIERS.LAST_SERVER_VERSION_CHECK).observeWithColumns(['value']).pipe(
switchMap((result) => (result.length ? result[0].observe() : of$({value: 0}))),
switchMap((model) => of$(parseInt(model.value, 10))),
);
};

View File

@@ -18,15 +18,14 @@ import ServerIcon from '@components/server_icon';
import TutorialHighlight from '@components/tutorial_highlight';
import TutorialSwipeLeft from '@components/tutorial_highlight/swipe_left';
import {Events} from '@constants';
import {PUSH_PROXY_RESPONSE_NOT_AVAILABLE, PUSH_PROXY_RESPONSE_UNKNOWN, PUSH_PROXY_STATUS_NOT_AVAILABLE, PUSH_PROXY_STATUS_UNKNOWN, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
import {PUSH_PROXY_STATUS_NOT_AVAILABLE, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
import {useTheme} from '@context/theme';
import DatabaseManager from '@database/manager';
import {subscribeServerUnreadAndMentions, UnreadObserverArgs} from '@database/subscription/unreads';
import {useIsTablet} from '@hooks/device';
import {queryServerByIdentifier} from '@queries/app/servers';
import {dismissBottomSheet} from '@screens/navigation';
import EphemeralStore from '@store/ephemeral_store';
import {alertPushProxyError, alertPushProxyUnknown} from '@utils/push_proxy';
import {canReceiveNotifications} from '@utils/push_proxy';
import {alertServerAlreadyConnected, alertServerError, alertServerLogout, alertServerRemove, editServer, loginToServer} from '@utils/server';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
@@ -251,19 +250,7 @@ const ServerItem = ({
return;
}
switch (result.canReceiveNotifications) {
case PUSH_PROXY_RESPONSE_NOT_AVAILABLE:
EphemeralStore.setPushProxyVerificationState(server.url, PUSH_PROXY_STATUS_NOT_AVAILABLE);
alertPushProxyError(intl);
break;
case PUSH_PROXY_RESPONSE_UNKNOWN:
EphemeralStore.setPushProxyVerificationState(server.url, PUSH_PROXY_STATUS_UNKNOWN);
alertPushProxyUnknown(intl);
break;
default:
EphemeralStore.setPushProxyVerificationState(server.url, PUSH_PROXY_STATUS_VERIFIED);
}
canReceiveNotifications(server.url, result.canReceiveNotifications as string, intl);
loginToServer(theme, server.url, displayName, data.config!, data.license!);
}, [server, theme, intl]);

View File

@@ -9,6 +9,7 @@ import {DeviceEventEmitter, Platform} from 'react-native';
import HWKeyboardEvent from 'react-native-hw-keyboard-event';
import {enableFreeze, enableScreens} from 'react-native-screens';
import ServerVersion from '@components/server_version';
import {Events, Screens} from '@constants';
import {useTheme} from '@context/theme';
import {findChannels, popToRoot} from '@screens/navigation';
@@ -96,55 +97,58 @@ export default function HomeScreen(props: HomeProps) {
}, [intl.locale]);
return (
<NavigationContainer
theme={{
dark: false,
colors: {
primary: theme.centerChannelColor,
background: 'transparent',
card: theme.centerChannelBg,
text: theme.centerChannelColor,
border: 'white',
notification: theme.mentionHighlightBg,
},
}}
>
<Tab.Navigator
screenOptions={{headerShown: false, lazy: true, unmountOnBlur: false}}
backBehavior='none'
tabBar={(tabProps: BottomTabBarProps) => (
<TabBar
{...tabProps}
theme={theme}
/>)}
<>
<NavigationContainer
theme={{
dark: false,
colors: {
primary: theme.centerChannelColor,
background: 'transparent',
card: theme.centerChannelBg,
text: theme.centerChannelColor,
border: 'white',
notification: theme.mentionHighlightBg,
},
}}
>
<Tab.Screen
name={Screens.HOME}
options={{title: 'Channel', unmountOnBlur: false, tabBarTestID: 'tab_bar.home.tab', freezeOnBlur: true}}
<Tab.Navigator
screenOptions={{headerShown: false, lazy: true, unmountOnBlur: false}}
backBehavior='none'
tabBar={(tabProps: BottomTabBarProps) => (
<TabBar
{...tabProps}
theme={theme}
/>)}
>
{() => <ChannelList {...props}/>}
</Tab.Screen>
<Tab.Screen
name={Screens.SEARCH}
component={Search}
options={{unmountOnBlur: false, lazy: true, tabBarTestID: 'tab_bar.search.tab', freezeOnBlur: true}}
/>
<Tab.Screen
name={Screens.MENTIONS}
component={RecentMentions}
options={{tabBarTestID: 'tab_bar.mentions.tab', lazy: true, unmountOnBlur: false, freezeOnBlur: true}}
/>
<Tab.Screen
name={Screens.SAVED_MESSAGES}
component={SavedMessages}
options={{unmountOnBlur: false, lazy: true, tabBarTestID: 'tab_bar.saved_messages.tab', freezeOnBlur: true}}
/>
<Tab.Screen
name={Screens.ACCOUNT}
component={Account}
options={{tabBarTestID: 'tab_bar.account.tab', lazy: true, unmountOnBlur: false, freezeOnBlur: true}}
/>
</Tab.Navigator>
</NavigationContainer>
<Tab.Screen
name={Screens.HOME}
options={{title: 'Channel', unmountOnBlur: false, tabBarTestID: 'tab_bar.home.tab', freezeOnBlur: true}}
>
{() => <ChannelList {...props}/>}
</Tab.Screen>
<Tab.Screen
name={Screens.SEARCH}
component={Search}
options={{unmountOnBlur: false, lazy: true, tabBarTestID: 'tab_bar.search.tab', freezeOnBlur: true}}
/>
<Tab.Screen
name={Screens.MENTIONS}
component={RecentMentions}
options={{tabBarTestID: 'tab_bar.mentions.tab', lazy: true, unmountOnBlur: false, freezeOnBlur: true}}
/>
<Tab.Screen
name={Screens.SAVED_MESSAGES}
component={SavedMessages}
options={{unmountOnBlur: false, lazy: true, tabBarTestID: 'tab_bar.saved_messages.tab', freezeOnBlur: true}}
/>
<Tab.Screen
name={Screens.ACCOUNT}
component={Account}
options={{tabBarTestID: 'tab_bar.account.tab', lazy: true, unmountOnBlur: false, freezeOnBlur: true}}
/>
</Tab.Navigator>
</NavigationContainer>
<ServerVersion/>
</>
);
}

View File

@@ -16,16 +16,14 @@ import LocalConfig from '@assets/config.json';
import ClientError from '@client/rest/error';
import AppVersion from '@components/app_version';
import {Screens, Launch} from '@constants';
import {PUSH_PROXY_RESPONSE_NOT_AVAILABLE, PUSH_PROXY_RESPONSE_UNKNOWN, PUSH_PROXY_STATUS_NOT_AVAILABLE, PUSH_PROXY_STATUS_UNKNOWN, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
import DatabaseManager from '@database/manager';
import {t} from '@i18n';
import NetworkManager from '@managers/network_manager';
import {queryServerByDisplayName, queryServerByIdentifier} from '@queries/app/servers';
import Background from '@screens/background';
import {dismissModal, goToScreen, loginAnimationOptions} from '@screens/navigation';
import EphemeralStore from '@store/ephemeral_store';
import {getErrorMessage} from '@utils/client_error';
import {alertPushProxyError, alertPushProxyUnknown} from '@utils/push_proxy';
import {canReceiveNotifications} from '@utils/push_proxy';
import {loginOptions} from '@utils/server';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {getServerUrlAfterRedirect, isValidUrl, sanitizeUrl} from '@utils/url';
@@ -270,19 +268,7 @@ const Server = ({
return;
}
switch (result.canReceiveNotifications) {
case PUSH_PROXY_RESPONSE_NOT_AVAILABLE:
EphemeralStore.setPushProxyVerificationState(serverUrl, PUSH_PROXY_STATUS_NOT_AVAILABLE);
alertPushProxyError(intl);
break;
case PUSH_PROXY_RESPONSE_UNKNOWN:
EphemeralStore.setPushProxyVerificationState(serverUrl, PUSH_PROXY_STATUS_UNKNOWN);
alertPushProxyUnknown(intl);
break;
default:
EphemeralStore.setPushProxyVerificationState(serverUrl, PUSH_PROXY_STATUS_VERIFIED);
}
canReceiveNotifications(serverUrl, result.canReceiveNotifications as string, intl);
const data = await fetchConfigAndLicense(serverUrl, true);
if (data.error) {
setButtonDisabled(true);

View File

@@ -4,6 +4,24 @@
import {IntlShape} from 'react-intl';
import {Alert} from 'react-native';
import {PUSH_PROXY_RESPONSE_NOT_AVAILABLE, PUSH_PROXY_RESPONSE_UNKNOWN, PUSH_PROXY_STATUS_NOT_AVAILABLE, PUSH_PROXY_STATUS_UNKNOWN, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
import EphemeralStore from '@store/ephemeral_store';
export function canReceiveNotifications(serverUrl: string, verification: string, intl: IntlShape) {
switch (verification) {
case PUSH_PROXY_RESPONSE_NOT_AVAILABLE:
EphemeralStore.setPushProxyVerificationState(serverUrl, PUSH_PROXY_STATUS_NOT_AVAILABLE);
alertPushProxyError(intl);
break;
case PUSH_PROXY_RESPONSE_UNKNOWN:
EphemeralStore.setPushProxyVerificationState(serverUrl, PUSH_PROXY_STATUS_UNKNOWN);
alertPushProxyUnknown(intl);
break;
default:
EphemeralStore.setPushProxyVerificationState(serverUrl, PUSH_PROXY_STATUS_VERIFIED);
}
}
export function alertPushProxyError(intl: IntlShape) {
Alert.alert(
intl.formatMessage({

View File

@@ -8,16 +8,21 @@ import CompassIcon from '@components/compass_icon';
import {Screens, Sso, SupportedServer, Launch} from '@constants';
import {dismissBottomSheet, showModal} from '@screens/navigation';
import {getErrorMessage} from '@utils/client_error';
import {isMinimumServerVersion} from '@utils/helpers';
import {changeOpacity} from '@utils/theme';
import {tryOpenURL} from '@utils/url';
import type ServersModel from '@typings/database/models/app/servers';
export function unsupportedServer(isSystemAdmin: boolean, intl: IntlShape) {
export function isSupportedServer(currentVersion: string) {
return isMinimumServerVersion(currentVersion, SupportedServer.MAJOR_VERSION, SupportedServer.MIN_VERSION, SupportedServer.PATCH_VERSION);
}
export function unsupportedServer(serverDisplayName: string, isSystemAdmin: boolean, intl: IntlShape, onPress?: () => void) {
if (isSystemAdmin) {
return unsupportedServerAdminAlert(intl);
return unsupportedServerAdminAlert(serverDisplayName, intl, onPress);
}
return unsupportedServerAlert(intl);
return unsupportedServerAlert(serverDisplayName, intl, onPress);
}
export function semverFromServerVersion(value: string) {
@@ -179,21 +184,22 @@ export function alertServerAlreadyConnected(intl: IntlShape) {
);
}
function unsupportedServerAdminAlert(intl: IntlShape) {
function unsupportedServerAdminAlert(serverDisplayName: string, intl: IntlShape, onPress?: () => void) {
const title = intl.formatMessage({id: 'mobile.server_upgrade.title', defaultMessage: 'Server upgrade required'});
const message = intl.formatMessage({
id: 'mobile.server_upgrade.alert_description',
defaultMessage: 'This server version is unsupported and users will be exposed to compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Upgrading to server version {serverVersion} or later is required.',
}, {serverVersion: SupportedServer.FULL_VERSION});
id: 'server_upgrade.alert_description',
defaultMessage: 'Your server, {serverDisplayName}, is running an unsupported server version. Users will be exposed to compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Upgrading to server version {supportedServerVersion} or later is required.',
}, {serverDisplayName, supportedServerVersion: SupportedServer.FULL_VERSION});
const cancel: AlertButton = {
text: intl.formatMessage({id: 'mobile.server_upgrade.dismiss', defaultMessage: 'Dismiss'}),
text: intl.formatMessage({id: 'server_upgrade.dismiss', defaultMessage: 'Dismiss'}),
style: 'default',
onPress,
};
const learnMore: AlertButton = {
text: intl.formatMessage({id: 'mobile.server_upgrade.learn_more', defaultMessage: 'Learn More'}),
text: intl.formatMessage({id: 'server_upgrade.learn_more', defaultMessage: 'Learn More'}),
style: 'cancel',
onPress: () => {
const url = 'https://docs.mattermost.com/administration/release-lifecycle.html';
@@ -213,17 +219,18 @@ function unsupportedServerAdminAlert(intl: IntlShape) {
Alert.alert(title, message, buttons, options);
}
function unsupportedServerAlert(intl: IntlShape) {
const title = intl.formatMessage({id: 'mobile.unsupported_server.title', defaultMessage: 'Unsupported server version'});
function unsupportedServerAlert(serverDisplayName: string, intl: IntlShape, onPress?: () => void) {
const title = intl.formatMessage({id: 'unsupported_server.title', defaultMessage: 'Unsupported server version'});
const message = intl.formatMessage({
id: 'mobile.unsupported_server.message',
defaultMessage: 'Attachments, link previews, reactions and embed data may not be displayed correctly. If this issue persists contact your System Administrator to upgrade your Mattermost server.',
});
id: 'unsupported_server.message',
defaultMessage: 'Your server, {serverDisplayName}, is running an unsupported server version. You may experience compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Please contact your System Administrator to upgrade your Mattermost server.',
}, {serverDisplayName});
const okButton: AlertButton = {
text: intl.formatMessage({id: 'mobile.unsupported_server.ok', defaultMessage: 'OK'}),
text: intl.formatMessage({id: 'mobile.server_upgrade.button', defaultMessage: 'OK'}),
style: 'default',
onPress,
};
const buttons: AlertButton[] = [okButton];

View File

@@ -12,13 +12,13 @@ describe('Unsupported Server Alert', () => {
it('should show the alert for sysadmin', () => {
const alert = jest.spyOn(Alert, 'alert');
unsupportedServer(true, intl);
unsupportedServer('Default Server', true, intl);
expect(alert?.mock?.calls?.[0]?.[2]?.length).toBe(2);
});
it('should show the alert for team admin / user', () => {
const alert = jest.spyOn(Alert, 'alert');
unsupportedServer(false, intl);
unsupportedServer('Default Server', false, intl);
expect(alert?.mock?.calls?.[0]?.[2]?.length).toBe(1);
});
});

View File

@@ -4,7 +4,6 @@
"TestServerUrl": "http://localhost:8065",
"DefaultTheme": "default",
"ShowErrorsList": false,
"MinServerVersion": "5.25.0",
"EELearnURL": "about.mattermost.com",
"TeamEditionLearnURL": "mattermost.org",
"AboutTeamURL": "http://www.mattermost.org/",

View File

@@ -564,11 +564,8 @@
"mobile.server_name.exists": "You are using this name for another server.",
"mobile.server_ping_failed": "Cannot connect to the server.",
"mobile.server_requires_client_certificate": "Server requires client certificate for authentication.",
"mobile.server_upgrade.alert_description": "This server version is unsupported and users will be exposed to compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Upgrading to server version {serverVersion} or later is required.",
"mobile.server_upgrade.button": "OK",
"mobile.server_upgrade.description": "\nA server upgrade is required to use the Mattermost app. Please ask your System Administrator for details.\n",
"mobile.server_upgrade.dismiss": "Dismiss",
"mobile.server_upgrade.learn_more": "Learn More",
"mobile.server_upgrade.title": "Server upgrade required",
"mobile.server_url.deeplink.emm.denied": "This app is controlled by an EMM and the DeepLink server url does not match the EMM allowed server",
"mobile.server_url.empty": "Please enter a valid server URL",
@@ -592,9 +589,6 @@
"mobile.system_message.update_channel_purpose_message.updated_from": "{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}",
"mobile.system_message.update_channel_purpose_message.updated_to": "{username} updated the channel purpose to: {newPurpose}",
"mobile.tos_link": "Terms of Service",
"mobile.unsupported_server.message": "Attachments, link previews, reactions and embed data may not be displayed correctly. If this issue persists contact your System Administrator to upgrade your Mattermost server.",
"mobile.unsupported_server.ok": "OK",
"mobile.unsupported_server.title": "Unsupported server version",
"mobile.user_list.deactivated": "Deactivated",
"mobile.write_storage_permission_denied_description": "Save files to your device. Open Settings to grant {applicationName} write access to files on this device.",
"modal.manual_status.auto_responder.message_": "Would you like to switch your status to \"{status}\" and disable Automatic Replies?",
@@ -741,6 +735,9 @@
"select_team.title": "Select a team",
"server_list.push_proxy_error": "Notifications cannot be received from this server because of its configuration. Contact your system admin.",
"server_list.push_proxy_unknown": "Notifications could not be received from this server because of its configuration. Log out and Log in again to retry.",
"server_upgrade.alert_description": "Your server, {serverDisplayName}, is running an unsupported server version. Users will be exposed to compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Upgrading to server version {supportedServerVersion} or later is required.",
"server_upgrade.dismiss": "Dismiss",
"server_upgrade.learn_more": "Learn More",
"server.logout.alert_description": "All associated data will be removed",
"server.logout.alert_title": "Are you sure you want to log out of {displayName}?",
"server.remove.alert_description": "This will remove it from your list of servers. All associated data will be removed",
@@ -830,6 +827,8 @@
"unreads.empty.paragraph": "Turn off the unread filter to show all your channels.",
"unreads.empty.show_all": "Show all",
"unreads.empty.title": "No more unreads",
"unsupported_server.message": "Your server, {serverDisplayName}, is running an unsupported server version. You may experience compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Please contact your System Administrator to upgrade your Mattermost server.",
"unsupported_server.title": "Unsupported server version",
"user_profile.custom_status": "Custom Status",
"user.edit_profile.email.auth_service": "Login occurs through {service}. Email cannot be updated. Email address used for notifications is {email}.",
"user.edit_profile.email.web_client": "Email must be updated using a web client or desktop application.",