diff --git a/apps/meteor/app/apps/client/communication/websockets.js b/apps/meteor/app/apps/client/communication/websockets.js index 67f03e0df1ee..32f79be7b271 100644 --- a/apps/meteor/app/apps/client/communication/websockets.js +++ b/apps/meteor/app/apps/client/communication/websockets.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Emitter } from '@rocket.chat/emitter'; import { slashCommands, APIClient } from '../../../utils'; -import { CachedCollectionManager } from '../../../ui-cached-collection'; +import { CachedCollectionManager } from '../../../ui-cached-collection/client'; import { loadButtons } from '../../../ui-message/client/ActionButtonSyncer'; export const AppEvents = Object.freeze({ diff --git a/apps/meteor/app/apps/client/orchestrator.ts b/apps/meteor/app/apps/client/orchestrator.ts index 86b958d8775d..11abbae6ff47 100644 --- a/apps/meteor/app/apps/client/orchestrator.ts +++ b/apps/meteor/app/apps/client/orchestrator.ts @@ -12,7 +12,7 @@ import type { AppScreenshot, AppRequestFilter, Pagination, IRestResponse, Serial import type { App } from '../../../client/views/admin/apps/types'; import { dispatchToastMessage } from '../../../client/lib/toast'; import { settings } from '../../settings/client'; -import { CachedCollectionManager } from '../../ui-cached-collection'; +import { CachedCollectionManager } from '../../ui-cached-collection/client'; import { createDeferredValue } from '../lib/misc/DeferredValue'; import type { // IAppFromMarketplace, diff --git a/apps/meteor/app/apps/server/communication/rest.js b/apps/meteor/app/apps/server/communication/rest.js index cd3435ccb5a8..464261006e3c 100644 --- a/apps/meteor/app/apps/server/communication/rest.js +++ b/apps/meteor/app/apps/server/communication/rest.js @@ -12,6 +12,7 @@ import { Apps } from '../orchestrator'; import { formatAppInstanceForRest } from '../../lib/misc/formatAppInstanceForRest'; import { actionButtonsHandler } from './endpoints/actionButtonsHandler'; import { fetch } from '../../../../server/lib/http/fetch'; +import { notifyAppInstall } from '../marketplace/appInstall'; const rocketChatVersion = Info.version; const appsEngineVersionForMarketplace = Info.marketplaceApiVersion.replace(/-.*/g, ''); @@ -273,6 +274,8 @@ export class AppsRestApi { info.status = aff.getApp().getStatus(); + notifyAppInstall(orchestrator.getMarketplaceUrl(), 'install', info); + return API.v1.success({ app: info, implemented: aff.getImplementedInferfaces(), @@ -558,6 +561,8 @@ export class AppsRestApi { info.status = aff.getApp().getStatus(); + notifyAppInstall(orchestrator.getMarketplaceUrl(), 'update', info); + return API.v1.success({ app: info, implemented: aff.getImplementedInferfaces(), @@ -578,6 +583,8 @@ export class AppsRestApi { const info = prl.getInfo(); info.status = prl.getStatus(); + notifyAppInstall(orchestrator.getMarketplaceUrl(), 'uninstall', info); + return API.v1.success({ app: info }); }, }, diff --git a/apps/meteor/app/apps/server/marketplace/appInstall.ts b/apps/meteor/app/apps/server/marketplace/appInstall.ts new file mode 100644 index 000000000000..a18b8f57eae2 --- /dev/null +++ b/apps/meteor/app/apps/server/marketplace/appInstall.ts @@ -0,0 +1,46 @@ +import type { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata'; + +import { getWorkspaceAccessToken } from '../../../cloud/server'; +import { settings } from '../../../settings/server'; +import { Info } from '../../../utils/server'; + +export type installAction = 'install' | 'update' | 'uninstall'; + +export async function notifyAppInstall(marketplaceBaseUrl: string, action: installAction, appInfo: IAppInfo): Promise { + const headers: { Authorization?: string } = {}; + + try { + const token = await getWorkspaceAccessToken(); + headers.Authorization = `Bearer ${token}`; + + // eslint-disable-next-line no-empty + } catch {} + + let siteUrl = ''; + try { + siteUrl = settings.get('Site_Url'); + + // eslint-disable-next-line no-empty + } catch {} + + const data = { + action, + appName: appInfo.name, + appSlug: appInfo.nameSlug, + appVersion: appInfo.version, + rocketChatVersion: Info.version, + engineVersion: Info.marketplaceApiVersion, + siteUrl, + }; + + const pendingSentUrl = `${marketplaceBaseUrl}/v1/apps/${appInfo.id}/install`; + + try { + HTTP.post(pendingSentUrl, { + headers, + data, + }); + + // eslint-disable-next-line no-empty + } catch {} +} diff --git a/apps/meteor/app/authorization/client/hasPermission.ts b/apps/meteor/app/authorization/client/hasPermission.ts index a0d6a2fe63b4..cac0c20f3f1f 100644 --- a/apps/meteor/app/authorization/client/hasPermission.ts +++ b/apps/meteor/app/authorization/client/hasPermission.ts @@ -5,7 +5,13 @@ import { ChatPermissions } from './lib/ChatPermissions'; import * as Models from '../../models/client'; import { AuthorizationUtils } from '../lib/AuthorizationUtils'; -const isValidScope = (scope: IRole['scope']): boolean => typeof scope === 'string' && scope in Models; +const isValidScope = (scope: unknown): scope is keyof typeof Models => typeof scope === 'string' && scope in Models; + +const hasIsUserInRole = ( + model: unknown, +): model is { + isUserInRole: (this: any, uid: IUser['_id'], roleId: IRole['_id'], scope: string | undefined) => boolean; +} => typeof model === 'object' && model !== null && typeof (model as { isUserInRole?: unknown }).isUserInRole === 'function'; const createPermissionValidator = (quantifier: (predicate: (permissionId: IPermission['_id']) => boolean) => boolean) => @@ -21,7 +27,7 @@ const createPermissionValidator = } } - const permission: IPermission | null = ChatPermissions.findOne(permissionId, { + const permission = ChatPermissions.findOne(permissionId, { fields: { roles: 1 }, }); const roles = permission?.roles ?? []; @@ -34,8 +40,13 @@ const createPermissionValidator = return false; } - const model = Models[roleScope as keyof typeof Models]; - return model.isUserInRole?.(userId, roleId, scope); + const model = Models[roleScope]; + + if (hasIsUserInRole(model)) { + return model.isUserInRole(userId, roleId, scope); + } + + return undefined; }); }); }; diff --git a/apps/meteor/app/authorization/client/startup.js b/apps/meteor/app/authorization/client/startup.js index 6a7d79cb79cc..d004e6920518 100644 --- a/apps/meteor/app/authorization/client/startup.js +++ b/apps/meteor/app/authorization/client/startup.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { CachedCollectionManager } from '../../ui-cached-collection'; +import { CachedCollectionManager } from '../../ui-cached-collection/client'; import { APIClient } from '../../utils/client/lib/RestApiClient'; import { Roles } from '../../models/client'; import { rolesStreamer } from './lib/streamer'; diff --git a/apps/meteor/app/authorization/lib/index.js b/apps/meteor/app/authorization/lib/index.ts similarity index 55% rename from apps/meteor/app/authorization/lib/index.js rename to apps/meteor/app/authorization/lib/index.ts index 043e5a4beda2..8e2a37a64138 100644 --- a/apps/meteor/app/authorization/lib/index.js +++ b/apps/meteor/app/authorization/lib/index.ts @@ -1,10 +1,12 @@ -export const getSettingPermissionId = function (settingId) { +import type { ISetting } from '@rocket.chat/core-typings'; + +export const getSettingPermissionId = function (settingId: ISetting['_id']) { // setting-based permissions return `change-setting-${settingId}`; }; export const CONSTANTS = { SETTINGS_LEVEL: 'settings', -}; +} as const; export { AuthorizationUtils } from './AuthorizationUtils'; diff --git a/apps/meteor/app/authorization/server/functions/upsertPermissions.ts b/apps/meteor/app/authorization/server/functions/upsertPermissions.ts index 3be452c5ca58..48ee4745742c 100644 --- a/apps/meteor/app/authorization/server/functions/upsertPermissions.ts +++ b/apps/meteor/app/authorization/server/functions/upsertPermissions.ts @@ -269,12 +269,12 @@ export const upsertPermissions = async (): Promise => { }, ): Promise { const permissionId = getSettingPermissionId(setting._id); - const permission: Omit = { + const permission: Omit = { level: CONSTANTS.SETTINGS_LEVEL as 'settings' | undefined, // copy those setting-properties which are needed to properly publish the setting-based permissions settingId: setting._id, group: setting.group, - section: setting.section, + section: setting.section ?? undefined, sorter: setting.sorter, roles: [], }; diff --git a/apps/meteor/app/custom-sounds/client/lib/CustomSounds.js b/apps/meteor/app/custom-sounds/client/lib/CustomSounds.js index 233235447ce8..4f5c185ce4be 100644 --- a/apps/meteor/app/custom-sounds/client/lib/CustomSounds.js +++ b/apps/meteor/app/custom-sounds/client/lib/CustomSounds.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; import _ from 'underscore'; -import { CachedCollectionManager } from '../../../ui-cached-collection'; +import { CachedCollectionManager } from '../../../ui-cached-collection/client'; import { getURL } from '../../../utils/client'; const getCustomSoundId = (sound) => `custom-sound-${sound}`; diff --git a/apps/meteor/app/custom-sounds/client/notifications/deleteCustomSound.js b/apps/meteor/app/custom-sounds/client/notifications/deleteCustomSound.js index 7472fd1f834d..51aafda75c48 100644 --- a/apps/meteor/app/custom-sounds/client/notifications/deleteCustomSound.js +++ b/apps/meteor/app/custom-sounds/client/notifications/deleteCustomSound.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { CachedCollectionManager } from '../../../ui-cached-collection'; +import { CachedCollectionManager } from '../../../ui-cached-collection/client'; import { Notifications } from '../../../notifications'; import { CustomSounds } from '../lib/CustomSounds'; diff --git a/apps/meteor/app/custom-sounds/client/notifications/updateCustomSound.js b/apps/meteor/app/custom-sounds/client/notifications/updateCustomSound.js index 4005efb3aaf0..68c75204ef62 100644 --- a/apps/meteor/app/custom-sounds/client/notifications/updateCustomSound.js +++ b/apps/meteor/app/custom-sounds/client/notifications/updateCustomSound.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { CachedCollectionManager } from '../../../ui-cached-collection'; +import { CachedCollectionManager } from '../../../ui-cached-collection/client'; import { Notifications } from '../../../notifications'; import { CustomSounds } from '../lib/CustomSounds'; diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.ts b/apps/meteor/app/e2e/client/rocketchat.e2e.ts index 50a5fb107310..fcc36ee07120 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.ts +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.ts @@ -8,7 +8,7 @@ import type { ReactiveVar as ReactiveVarType } from 'meteor/reactive-var'; import { EJSON } from 'meteor/ejson'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Emitter } from '@rocket.chat/emitter'; -import type { IE2EEMessage, IMessage, IRoom } from '@rocket.chat/core-typings'; +import type { IE2EEMessage, IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; import { isE2EEMessage } from '@rocket.chat/core-typings'; import { getMessageUrlRegex } from '../../../lib/getMessageUrlRegex'; @@ -447,16 +447,16 @@ class E2E extends Emitter { }); } - async decryptSubscription(rid: IRoom['_id']): Promise { - const e2eRoom = await this.getInstanceByRoomId(rid); - this.log('decryptSubscription ->', rid); + async decryptSubscription(subscriptionId: ISubscription['_id']): Promise { + const e2eRoom = await this.getInstanceByRoomId(subscriptionId); + this.log('decryptSubscription ->', subscriptionId); e2eRoom?.decryptSubscription(); } async decryptSubscriptions(): Promise { Subscriptions.find({ encrypted: true, - }).forEach((room: IRoom) => this.decryptSubscription(room._id)); + }).forEach((subscription) => this.decryptSubscription(subscription._id)); } openAlert(config: Omit): void { diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js index d01717912f3b..de4fc6c0c3b3 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js @@ -63,14 +63,14 @@ export function getMentions(message) { const incGroupMentions = (rid, roomType, excludeUserId, unreadCount) => { const incUnreadByGroup = ['all_messages', 'group_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount); - const incUnread = roomType === 'd' || incUnreadByGroup ? 1 : 0; + const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0; Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(rid, excludeUserId, 1, incUnread); }; const incUserMentions = (rid, roomType, uids, unreadCount) => { const incUnreadByUser = ['all_messages', 'user_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount); - const incUnread = roomType === 'd' || incUnreadByUser ? 1 : 0; + const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0; Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(rid, uids, 1, incUnread); }; @@ -86,6 +86,26 @@ const getUserIdsFromHighlights = (rid, message) => { .map(({ u: { _id: uid } }) => uid); }; +/* + * {IRoom['t']} roomType - The type of the room + * @returns {string} - The setting value for unread count + */ +const getUnreadSettingCount = (roomType) => { + let unreadSetting = 'Unread_Count'; + switch (roomType) { + case 'd': { + unreadSetting = 'Unread_Count_DM'; + break; + } + case 'l': { + unreadSetting = 'Unread_Count_Omni'; + break; + } + } + + return settings.get(unreadSetting); +}; + export async function updateUsersSubscriptions(message, room) { // Don't increase unread counter on thread messages if (room != null && !message.tmid) { @@ -93,8 +113,7 @@ export async function updateUsersSubscriptions(message, room) { const userIds = new Set(mentionIds); - const unreadSetting = room.t === 'd' ? 'Unread_Count_DM' : 'Unread_Count'; - const unreadCount = settings.get(unreadSetting); + const unreadCount = getUnreadSettingCount(room.t); getUserIdsFromHighlights(room._id, message).forEach((uid) => userIds.add(uid)); diff --git a/apps/meteor/app/lib/server/startup/settings.ts b/apps/meteor/app/lib/server/startup/settings.ts index 2a3b25cd9771..4e45541acdce 100644 --- a/apps/meteor/app/lib/server/startup/settings.ts +++ b/apps/meteor/app/lib/server/startup/settings.ts @@ -945,6 +945,20 @@ settingsRegistry.addGroup('General', function () { ], public: true, }); + this.add('Unread_Count_Omni', 'all_messages', { + type: 'select', + values: [ + { + key: 'all_messages', + i18nLabel: 'All_messages', + }, + { + key: 'mentions_only', + i18nLabel: 'Mentions_only', + }, + ], + public: true, + }); this.add('DeepLink_Url', 'https://go.rocket.chat', { type: 'string', diff --git a/apps/meteor/app/models/client/index.ts b/apps/meteor/app/models/client/index.ts index 70229d8b2bc3..172a71364a81 100644 --- a/apps/meteor/app/models/client/index.ts +++ b/apps/meteor/app/models/client/index.ts @@ -1,13 +1,9 @@ -import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; - -import { Base } from './models/_Base'; +import { Base } from './models/Base'; import Avatars from './models/Avatars'; import Uploads from './models/Uploads'; import UserDataFiles from './models/UserDataFiles'; import { Roles } from './models/Roles'; -import { Subscriptions as subscriptions } from './models/Subscriptions'; -import { Users as users } from './models/Users'; +import { Users } from './models/Users'; import { CachedChannelList } from './models/CachedChannelList'; import { CachedChatRoom } from './models/CachedChatRoom'; import { CachedChatSubscription } from './models/CachedChatSubscription'; @@ -23,14 +19,17 @@ import { WebdavAccounts } from './models/WebdavAccounts'; import CustomSounds from './models/CustomSounds'; import EmojiCustom from './models/EmojiCustom'; -/** @deprecated */ -const Users = _.extend({}, users, Meteor.users); -/** @deprecated */ -const Subscriptions = _.extend({}, subscriptions, ChatSubscription); -/** @deprecated */ -const Messages = _.extend({}, ChatMessage) as typeof ChatMessage; -/** @deprecated */ -const Rooms = _.extend({}, ChatRoom) as typeof ChatRoom; +// overwrite Meteor.users collection so records on it don't get erased whenever the client reconnects to websocket +Meteor.users = Users as typeof Meteor.users; +Meteor.user = () => { + const uid = Meteor.userId(); + + if (!uid) { + return null; + } + + return (Users.findOne({ _id: uid }) ?? null) as Meteor.User | null; +}; export { Base, @@ -38,23 +37,30 @@ export { Uploads, UserDataFiles, Roles, - Subscriptions, - Users, - Messages, CachedChannelList, CachedChatRoom, CachedChatSubscription, CachedUserList, - ChatRoom, RoomRoles, UserAndRoom, UserRoles, AuthzCachedCollection, ChatPermissions, - ChatMessage, - ChatSubscription, - Rooms, CustomSounds, EmojiCustom, WebdavAccounts, + /** @deprecated */ + Users, + /** @deprecated */ + ChatRoom as Rooms, + /** @deprecated */ + ChatRoom, + /** @deprecated */ + ChatSubscription, + /** @deprecated */ + ChatSubscription as Subscriptions, + /** @deprecated */ + ChatMessage, + /** @deprecated */ + ChatMessage as Messages, }; diff --git a/apps/meteor/app/models/client/models/Avatars.js b/apps/meteor/app/models/client/models/Avatars.ts similarity index 80% rename from apps/meteor/app/models/client/models/Avatars.js rename to apps/meteor/app/models/client/models/Avatars.ts index 73a35965754a..0dcd410da60e 100644 --- a/apps/meteor/app/models/client/models/Avatars.js +++ b/apps/meteor/app/models/client/models/Avatars.ts @@ -1,4 +1,4 @@ -import { Base } from './_Base'; +import { Base } from './Base'; export class Avatars extends Base { constructor() { diff --git a/apps/meteor/app/models/client/models/Base.ts b/apps/meteor/app/models/client/models/Base.ts new file mode 100644 index 000000000000..20d3bdf14523 --- /dev/null +++ b/apps/meteor/app/models/client/models/Base.ts @@ -0,0 +1,64 @@ +import { check } from 'meteor/check'; +import { Mongo } from 'meteor/mongo'; + +export abstract class Base { + private model: Mongo.Collection; + + protected _baseName() { + return 'rocketchat_'; + } + + protected _initModel(name: string) { + check(name, String); + this.model = new Mongo.Collection(this._baseName() + name); + return this.model; + } + + find(...args: Parameters['find']>) { + return this.model.find(...args); + } + + findOne(...args: Parameters['findOne']>) { + return this.model.findOne(...args); + } + + insert(...args: Parameters['insert']>) { + return this.model.insert(...args); + } + + update(...args: Parameters['update']>) { + return this.model.update(...args); + } + + upsert(...args: Parameters['upsert']>) { + return this.model.upsert(...args); + } + + remove(...args: Parameters['remove']>) { + return this.model.remove(...args); + } + + allow(...args: Parameters['allow']>) { + return this.model.allow(...args); + } + + deny(...args: Parameters['deny']>) { + return this.model.deny(...args); + } + + ensureIndex() { + // do nothing + } + + dropIndex() { + // do nothing + } + + tryEnsureIndex() { + // do nothing + } + + tryDropIndex() { + // do nothing + } +} diff --git a/apps/meteor/app/models/client/models/CachedChannelList.js b/apps/meteor/app/models/client/models/CachedChannelList.js deleted file mode 100644 index 1a34d88e6aeb..000000000000 --- a/apps/meteor/app/models/client/models/CachedChannelList.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -export const CachedChannelList = new Mongo.Collection(null); diff --git a/apps/meteor/app/models/client/models/CachedChannelList.ts b/apps/meteor/app/models/client/models/CachedChannelList.ts new file mode 100644 index 000000000000..2ec0979e6c5e --- /dev/null +++ b/apps/meteor/app/models/client/models/CachedChannelList.ts @@ -0,0 +1,4 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import { Mongo } from 'meteor/mongo'; + +export const CachedChannelList = new Mongo.Collection(null); diff --git a/apps/meteor/app/models/client/models/CachedChatRoom.js b/apps/meteor/app/models/client/models/CachedChatRoom.js deleted file mode 100644 index 6ed43bc15749..000000000000 --- a/apps/meteor/app/models/client/models/CachedChatRoom.js +++ /dev/null @@ -1,3 +0,0 @@ -import { CachedCollection } from '../../../ui-cached-collection'; - -export const CachedChatRoom = new CachedCollection({ name: 'rooms' }); diff --git a/apps/meteor/app/models/client/models/CachedChatRoom.ts b/apps/meteor/app/models/client/models/CachedChatRoom.ts new file mode 100644 index 000000000000..2ade35f6fee9 --- /dev/null +++ b/apps/meteor/app/models/client/models/CachedChatRoom.ts @@ -0,0 +1,111 @@ +import type { IOmnichannelRoom, IRoom, IRoomWithRetentionPolicy } from '@rocket.chat/core-typings'; + +import { ChatSubscription } from './ChatSubscription'; +import { CachedCollection } from '../../../ui-cached-collection/client'; + +class CachedChatRoom extends CachedCollection { + constructor() { + super({ name: 'rooms' }); + } + + protected handleLoadFromServer(record: IRoom) { + return this.mergeWithSubscription(record); + } + + protected handleReceived(record: IRoom) { + return this.mergeWithSubscription(record); + } + + protected handleSync(record: IRoom) { + return this.mergeWithSubscription(record); + } + + private mergeWithSubscription(room: IRoom): IRoom { + const sub = ChatSubscription.findOne({ rid: room._id }); + if (!sub) { + return room; + } + + ChatSubscription.update( + { + rid: room._id, + }, + { + $set: { + encrypted: room.encrypted, + description: room.description, + cl: room.cl, + topic: room.topic, + announcement: room.announcement, + broadcast: room.broadcast, + archived: room.archived, + avatarETag: room.avatarETag, + retention: (room as IRoomWithRetentionPolicy | undefined)?.retention, + uids: room.uids, + usernames: room.usernames, + usersCount: room.usersCount, + lastMessage: room.lastMessage, + streamingOptions: room.streamingOptions, + teamId: room.teamId, + teamMain: room.teamMain, + v: (room as IOmnichannelRoom | undefined)?.v, + transcriptRequest: (room as IOmnichannelRoom | undefined)?.transcriptRequest, + servedBy: (room as IOmnichannelRoom | undefined)?.servedBy, + onHold: (room as IOmnichannelRoom | undefined)?.onHold, + tags: (room as IOmnichannelRoom | undefined)?.tags, + closedAt: (room as IOmnichannelRoom | undefined)?.closedAt, + metrics: (room as IOmnichannelRoom | undefined)?.metrics, + muted: room.muted, + waitingResponse: (room as IOmnichannelRoom | undefined)?.waitingResponse, + responseBy: (room as IOmnichannelRoom | undefined)?.responseBy, + priorityId: (room as IOmnichannelRoom | undefined)?.priorityId, + livechatData: (room as IOmnichannelRoom | undefined)?.livechatData, + departmentId: (room as IOmnichannelRoom | undefined)?.departmentId, + ts: room.ts, + source: (room as IOmnichannelRoom | undefined)?.source, + queuedAt: (room as IOmnichannelRoom | undefined)?.queuedAt, + federated: room.federated, + ...(() => { + const name = room.name || sub.name; + const fname = room.fname || sub.fname || name; + return { + lowerCaseName: String(!room.prid ? name : fname).toLowerCase(), + lowerCaseFName: String(fname).toLowerCase(), + }; + })(), + }, + }, + ); + + ChatSubscription.update( + { + rid: room._id, + lm: { $lt: room.lm }, + }, + { + $set: { + lm: room.lm, + }, + }, + ); + + return room; + } + + protected deserializeFromCache(record: unknown) { + const deserialized = super.deserializeFromCache(record); + + if (deserialized?.lastMessage?._updatedAt) { + deserialized.lastMessage._updatedAt = new Date(deserialized.lastMessage._updatedAt); + } + + return deserialized; + } +} + +const instance = new CachedChatRoom(); + +export { + /** @deprecated */ + instance as CachedChatRoom, +}; diff --git a/apps/meteor/app/models/client/models/CachedChatSubscription.js b/apps/meteor/app/models/client/models/CachedChatSubscription.js deleted file mode 100644 index a237624ba113..000000000000 --- a/apps/meteor/app/models/client/models/CachedChatSubscription.js +++ /dev/null @@ -1,3 +0,0 @@ -import { CachedCollection } from '../../../ui-cached-collection'; - -export const CachedChatSubscription = new CachedCollection({ name: 'subscriptions' }); diff --git a/apps/meteor/app/models/client/models/CachedChatSubscription.ts b/apps/meteor/app/models/client/models/CachedChatSubscription.ts new file mode 100644 index 000000000000..8f66d4f8b2cc --- /dev/null +++ b/apps/meteor/app/models/client/models/CachedChatSubscription.ts @@ -0,0 +1,139 @@ +import type { IOmnichannelRoom, IRoomWithRetentionPolicy } from '@rocket.chat/core-typings'; + +import { CachedCollection } from '../../../ui-cached-collection/client'; +import type { SubscriptionWithRoom } from '../../../../client/definitions/SubscriptionWithRoom'; +import { ChatRoom } from './ChatRoom'; +import { CachedChatRoom } from './CachedChatRoom'; + +class CachedChatSubscription extends CachedCollection { + constructor() { + super({ name: 'subscriptions' }); + } + + protected handleLoadFromServer(record: SubscriptionWithRoom) { + return this.mergeWithRoom(record); + } + + protected handleReceived(record: SubscriptionWithRoom, action: 'changed' | 'removed') { + const newRecord = this.mergeWithRoom(record); + + if (action === 'removed') { + ChatRoom.remove(newRecord.rid); + CachedChatRoom.save(); + } + + return newRecord; + } + + protected handleSync(record: SubscriptionWithRoom) { + return this.mergeWithRoom(record); + } + + private mergeWithRoom(subscription: SubscriptionWithRoom): SubscriptionWithRoom { + const options = { + fields: { + lm: 1, + lastMessage: 1, + uids: 1, + streamingOptions: 1, + usernames: 1, + usersCount: 1, + topic: 1, + encrypted: 1, + description: 1, + announcement: 1, + broadcast: 1, + archived: 1, + avatarETag: 1, + retention: 1, + teamId: 1, + teamMain: 1, + msgs: 1, + onHold: 1, + metrics: 1, + muted: 1, + servedBy: 1, + ts: 1, + waitingResponse: 1, + v: 1, + transcriptRequest: 1, + tags: 1, + closedAt: 1, + responseBy: 1, + priorityId: 1, + livechatData: 1, + departmentId: 1, + source: 1, + queuedAt: 1, + federated: 1, + }, + }; + + const room = ChatRoom.findOne({ _id: subscription.rid }, options); + + const lastRoomUpdate = room?.lm || subscription.ts || subscription._updatedAt; + + return { + ...subscription, + ...(() => { + const { name } = subscription; + const fname = subscription.fname || name; + return { + lowerCaseName: String(!subscription.prid ? name : fname).toLowerCase(), + lowerCaseFName: String(fname).toLowerCase(), + }; + })(), + encrypted: room?.encrypted, + description: room?.description, + cl: room?.cl, + topic: room?.topic, + announcement: room?.announcement, + broadcast: room?.broadcast, + archived: room?.archived, + avatarETag: room?.avatarETag, + retention: (room as IRoomWithRetentionPolicy | undefined)?.retention, + lastMessage: room?.lastMessage, + streamingOptions: room?.streamingOptions, + teamId: room?.teamId, + teamMain: room?.teamMain, + uids: room?.uids, + usernames: room?.usernames, + usersCount: room?.usersCount ?? 0, + v: (room as IOmnichannelRoom | undefined)?.v, + transcriptRequest: (room as IOmnichannelRoom | undefined)?.transcriptRequest, + servedBy: (room as IOmnichannelRoom | undefined)?.servedBy, + onHold: (room as IOmnichannelRoom | undefined)?.onHold, + tags: (room as IOmnichannelRoom | undefined)?.tags, + closedAt: (room as IOmnichannelRoom | undefined)?.closedAt, + metrics: (room as IOmnichannelRoom | undefined)?.metrics, + muted: room?.muted, + waitingResponse: (room as IOmnichannelRoom | undefined)?.waitingResponse, + responseBy: (room as IOmnichannelRoom | undefined)?.responseBy, + priorityId: (room as IOmnichannelRoom | undefined)?.priorityId, + livechatData: (room as IOmnichannelRoom | undefined)?.livechatData, + departmentId: (room as IOmnichannelRoom | undefined)?.departmentId, + ts: room?.ts ?? subscription.ts, + source: (room as IOmnichannelRoom | undefined)?.source, + queuedAt: (room as IOmnichannelRoom | undefined)?.queuedAt, + federated: room?.federated, + lm: subscription.lr ? new Date(Math.max(subscription.lr.getTime(), lastRoomUpdate.getTime())) : lastRoomUpdate, + }; + } + + protected deserializeFromCache(record: unknown) { + const deserialized = super.deserializeFromCache(record); + + if (deserialized?.lastMessage?._updatedAt) { + deserialized.lastMessage._updatedAt = new Date(deserialized.lastMessage._updatedAt); + } + + return deserialized; + } +} + +const instance = new CachedChatSubscription(); + +export { + /** @deprecated */ + instance as CachedChatSubscription, +}; diff --git a/apps/meteor/app/models/client/models/CachedUserList.js b/apps/meteor/app/models/client/models/CachedUserList.ts similarity index 100% rename from apps/meteor/app/models/client/models/CachedUserList.js rename to apps/meteor/app/models/client/models/CachedUserList.ts diff --git a/apps/meteor/app/models/client/models/ChatPermissions.js b/apps/meteor/app/models/client/models/ChatPermissions.js deleted file mode 100644 index e50ce6f66ba3..000000000000 --- a/apps/meteor/app/models/client/models/ChatPermissions.js +++ /dev/null @@ -1,8 +0,0 @@ -import { CachedCollection } from '../../../ui-cached-collection'; - -export const AuthzCachedCollection = new CachedCollection({ - name: 'permissions', - eventType: 'onLogged', -}); - -export const ChatPermissions = AuthzCachedCollection.collection; diff --git a/apps/meteor/app/models/client/models/ChatPermissions.ts b/apps/meteor/app/models/client/models/ChatPermissions.ts new file mode 100644 index 000000000000..8f1c7b18c060 --- /dev/null +++ b/apps/meteor/app/models/client/models/ChatPermissions.ts @@ -0,0 +1,10 @@ +import type { IPermission } from '@rocket.chat/core-typings'; + +import { CachedCollection } from '../../../ui-cached-collection/client'; + +export const AuthzCachedCollection = new CachedCollection({ + name: 'permissions', + eventType: 'onLogged', +}); + +export const ChatPermissions = AuthzCachedCollection.collection; diff --git a/apps/meteor/app/models/client/models/ChatRoom.js b/apps/meteor/app/models/client/models/ChatRoom.js deleted file mode 100644 index a8b849a07e2f..000000000000 --- a/apps/meteor/app/models/client/models/ChatRoom.js +++ /dev/null @@ -1,12 +0,0 @@ -import { CachedChatRoom } from './CachedChatRoom'; - -/** @type {import('meteor/mongo').Mongo.Collection} */ -export const ChatRoom = CachedChatRoom.collection; - -ChatRoom.setReactionsInLastMessage = function (roomId, lastMessage) { - return this.update({ _id: roomId }, { $set: { lastMessage } }); -}; - -ChatRoom.unsetReactionsInLastMessage = function (roomId) { - return this.update({ _id: roomId }, { $unset: { lastMessage: { reactions: 1 } } }); -}; diff --git a/apps/meteor/app/models/client/models/ChatRoom.ts b/apps/meteor/app/models/client/models/ChatRoom.ts new file mode 100644 index 000000000000..00528233daed --- /dev/null +++ b/apps/meteor/app/models/client/models/ChatRoom.ts @@ -0,0 +1,13 @@ +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; + +import { CachedChatRoom } from './CachedChatRoom'; + +/** @deprecated */ +export const ChatRoom = Object.assign(CachedChatRoom.collection, { + setReactionsInLastMessage(this: typeof CachedChatRoom.collection, roomId: IRoom['_id'], lastMessage: IMessage) { + return this.update({ _id: roomId }, { $set: { lastMessage } }); + }, + unsetReactionsInLastMessage(this: typeof CachedChatRoom.collection, roomId: IRoom['_id']) { + return this.update({ _id: roomId }, { $unset: { 'lastMessage.reactions': 1 as const } }); + }, +}); diff --git a/apps/meteor/app/models/client/models/ChatSubscription.js b/apps/meteor/app/models/client/models/ChatSubscription.js deleted file mode 100644 index 964f622db495..000000000000 --- a/apps/meteor/app/models/client/models/ChatSubscription.js +++ /dev/null @@ -1,3 +0,0 @@ -import { CachedChatSubscription } from './CachedChatSubscription'; - -export const ChatSubscription = CachedChatSubscription.collection; diff --git a/apps/meteor/app/models/client/models/ChatSubscription.ts b/apps/meteor/app/models/client/models/ChatSubscription.ts new file mode 100644 index 000000000000..759dff278bc2 --- /dev/null +++ b/apps/meteor/app/models/client/models/ChatSubscription.ts @@ -0,0 +1,56 @@ +import mem from 'mem'; +import type { IRole, IRoom, IUser } from '@rocket.chat/core-typings'; +import type { Filter } from 'mongodb'; +import { Meteor } from 'meteor/meteor'; + +import { CachedChatSubscription } from './CachedChatSubscription'; +import { isTruthy } from '../../../../lib/isTruthy'; + +/** @deprecated */ +export const ChatSubscription = Object.assign(CachedChatSubscription.collection, { + isUserInRole: mem( + function (this: typeof CachedChatSubscription.collection, _uid: IUser['_id'], roleId: IRole['_id'], rid?: IRoom['_id']) { + if (!rid) { + return false; + } + + const query = { + rid, + }; + + const subscription = this.findOne(query, { fields: { roles: 1 } }); + + return subscription && Array.isArray(subscription.roles) && subscription.roles.includes(roleId); + }, + { maxAge: 1000, cacheKey: JSON.stringify }, + ), + + findUsersInRoles: mem( + function (this: typeof CachedChatSubscription.collection, roles: IRole['_id'][] | IRole['_id'], scope?: string, options?: any) { + roles = Array.isArray(roles) ? roles : [roles]; + + const query: Filter = { + roles: { $in: roles }, + }; + + if (scope) { + query.rid = scope; + } + + const subscriptions = this.find(query).fetch(); + + const uids = subscriptions + .map((subscription) => { + if (typeof subscription.u !== 'undefined' && typeof subscription.u._id !== 'undefined') { + return subscription.u._id; + } + + return undefined; + }) + .filter(isTruthy); + + return Meteor.users.find({ _id: { $in: uids } }, options); + }, + { maxAge: 1000, cacheKey: JSON.stringify }, + ), +}); diff --git a/apps/meteor/app/models/client/models/CustomSounds.js b/apps/meteor/app/models/client/models/CustomSounds.js deleted file mode 100644 index e708f7e52fca..000000000000 --- a/apps/meteor/app/models/client/models/CustomSounds.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Base } from './_Base'; - -export class CustomSounds extends Base { - constructor() { - super(); - this._initModel('custom_sounds'); - } -} - -export default new CustomSounds(); diff --git a/apps/meteor/app/models/client/models/CustomSounds.ts b/apps/meteor/app/models/client/models/CustomSounds.ts new file mode 100644 index 000000000000..55ff711d3633 --- /dev/null +++ b/apps/meteor/app/models/client/models/CustomSounds.ts @@ -0,0 +1,12 @@ +import type { ICustomSound } from '@rocket.chat/core-typings'; + +import { Base } from './Base'; + +export class CustomSounds extends Base { + constructor() { + super(); + this._initModel('custom_sounds'); + } +} + +export default new CustomSounds(); diff --git a/apps/meteor/app/models/client/models/EmojiCustom.js b/apps/meteor/app/models/client/models/EmojiCustom.js deleted file mode 100644 index 13757b6d70ac..000000000000 --- a/apps/meteor/app/models/client/models/EmojiCustom.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Base } from './_Base'; - -export class EmojiCustom extends Base { - constructor() { - super(); - this._initModel('custom_emoji'); - } - - // find - findByNameOrAlias(name, options) { - const query = { - $or: [{ name }, { aliases: name }], - }; - - return this.find(query, options); - } -} - -export default new EmojiCustom(); diff --git a/apps/meteor/app/models/client/models/EmojiCustom.ts b/apps/meteor/app/models/client/models/EmojiCustom.ts new file mode 100644 index 000000000000..0650253446d3 --- /dev/null +++ b/apps/meteor/app/models/client/models/EmojiCustom.ts @@ -0,0 +1,22 @@ +import type { ICustomEmojiDescriptor } from '@rocket.chat/core-typings'; +import type { Mongo } from 'meteor/mongo'; + +import { Base } from './Base'; + +export class EmojiCustom extends Base { + constructor() { + super(); + this._initModel('custom_emoji'); + } + + // find + findByNameOrAlias(name: ICustomEmojiDescriptor['name'], options?: Mongo.Options) { + const query = { + $or: [{ name }, { aliases: name }], + }; + + return this.find(query, options); + } +} + +export default new EmojiCustom(); diff --git a/apps/meteor/app/models/client/models/FederationPeers.js b/apps/meteor/app/models/client/models/FederationPeers.js deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/apps/meteor/app/models/client/models/Roles.js b/apps/meteor/app/models/client/models/Roles.js deleted file mode 100644 index d195e413cb85..000000000000 --- a/apps/meteor/app/models/client/models/Roles.js +++ /dev/null @@ -1,38 +0,0 @@ -import { Mongo } from 'meteor/mongo'; -import { ReactiveVar } from 'meteor/reactive-var'; - -import * as Models from '..'; - -const Roles = Object.assign(new Mongo.Collection(null), { - /** - * @param {IRole['_id']} roleId - * @param {IRoom['_id']} scope the value for the role scope (room id) - * @param {any} options - */ - findUsersInRole(roleId, scope, options) { - const role = this.findOne(roleId); - const roleScope = (role && role.scope) || 'Users'; - const model = Models[roleScope]; - return model && model.findUsersInRoles && model.findUsersInRoles(roleId, scope, options); - }, - - /** - * @param {string} userId - * @param {IRole['_id'][]} roles the list of role ids - * @param {IRoom['_id']} scope the value for the role scope (room id) - * @param {boolean} ignoreSubscriptions ignore the subscription role scope - */ - isUserInRoles(userId, roles, scope, ignoreSubscriptions = false) { - roles = [].concat(roles); - return roles.some((roleId) => { - const role = this.findOne(roleId); - const roleScope = ignoreSubscriptions ? 'Users' : (role && role.scope) || 'Users'; - const model = Models[roleScope]; - return model && model.isUserInRole && model.isUserInRole(userId, roleId, scope); - }); - }, - - ready: new ReactiveVar(false), -}); - -export { Roles }; diff --git a/apps/meteor/app/models/client/models/Roles.ts b/apps/meteor/app/models/client/models/Roles.ts new file mode 100644 index 000000000000..00626eff0a99 --- /dev/null +++ b/apps/meteor/app/models/client/models/Roles.ts @@ -0,0 +1,52 @@ +import type { IRole, IRoom, IUser } from '@rocket.chat/core-typings'; +import { Mongo } from 'meteor/mongo'; +import { ReactiveVar } from 'meteor/reactive-var'; + +import { ChatSubscription } from './ChatSubscription'; +import { Users } from './Users'; + +class RolesCollection extends Mongo.Collection { + ready = new ReactiveVar(false); + + constructor() { + super(null); + } + + findUsersInRole(roleId: IRole['_id'], scope: IRoom['_id'], options: any) { + const role = this.findOne(roleId); + const roleScope = role?.scope || 'Users'; + + switch (roleScope) { + case 'Subscriptions': + return ChatSubscription.findUsersInRoles(roleId, scope, options); + + case 'Users': + return Users.findUsersInRoles(roleId, scope, options); + + default: + return undefined; + } + } + + isUserInRoles(userId: IUser['_id'], roles: IRole['_id'][] | IRole['_id'], scope?: string, ignoreSubscriptions = false) { + roles = Array.isArray(roles) ? roles : [roles]; + return roles.some((roleId) => { + const role = this.findOne(roleId); + const roleScope = ignoreSubscriptions ? 'Users' : role?.scope || 'Users'; + + switch (roleScope) { + case 'Subscriptions': + return ChatSubscription.isUserInRole(userId, roleId, scope); + + case 'Users': + return Users.isUserInRole(userId, roleId); + + default: + return false; + } + }); + } +} + +/** @deprecated */ +export const Roles = new RolesCollection(); diff --git a/apps/meteor/app/models/client/models/RoomRoles.js b/apps/meteor/app/models/client/models/RoomRoles.js deleted file mode 100644 index 4085638913bd..000000000000 --- a/apps/meteor/app/models/client/models/RoomRoles.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -export const RoomRoles = new Mongo.Collection(null); diff --git a/apps/meteor/app/models/client/models/RoomRoles.ts b/apps/meteor/app/models/client/models/RoomRoles.ts new file mode 100644 index 000000000000..21cb440b656d --- /dev/null +++ b/apps/meteor/app/models/client/models/RoomRoles.ts @@ -0,0 +1,5 @@ +import { Mongo } from 'meteor/mongo'; +import type { ISubscription } from '@rocket.chat/core-typings'; + +/** @deprecated */ +export const RoomRoles = new Mongo.Collection>(null); diff --git a/apps/meteor/app/models/client/models/Subscriptions.js b/apps/meteor/app/models/client/models/Subscriptions.js deleted file mode 100644 index 7e6785ce828a..000000000000 --- a/apps/meteor/app/models/client/models/Subscriptions.js +++ /dev/null @@ -1,64 +0,0 @@ -import _ from 'underscore'; -import mem from 'mem'; - -import { Users } from '..'; - -const Subscriptions = {}; - -Object.assign(Subscriptions, { - isUserInRole: mem( - /** - * @param {string} userId - * @param {IRole['_id']} roleId - * @param {string} roomId - */ - function (userId, roleId, roomId) { - if (roomId == null) { - return false; - } - - const query = { - rid: roomId, - }; - - const subscription = this.findOne(query, { fields: { roles: 1 } }); - - return subscription && Array.isArray(subscription.roles) && subscription.roles.includes(roleId); - }, - { maxAge: 1000, cacheKey: JSON.stringify }, - ), - - findUsersInRoles: mem( - /** - * @param {IRole['_id'][]} roles the list of role ids - * @param {string} scope the value for the role scope (room id) - * @param {any} options - */ - function (roles, scope, options) { - roles = [].concat(roles); - - const query = { - roles: { $in: roles }, - }; - - if (scope) { - query.rid = scope; - } - - const subscriptions = this.find(query).fetch(); - - const users = _.compact( - _.map(subscriptions, function (subscription) { - if (typeof subscription.u !== 'undefined' && typeof subscription.u._id !== 'undefined') { - return subscription.u._id; - } - }), - ); - - return Users.find({ _id: { $in: users } }, options); - }, - { maxAge: 1000, cacheKey: JSON.stringify }, - ), -}); - -export { Subscriptions }; diff --git a/apps/meteor/app/models/client/models/Uploads.js b/apps/meteor/app/models/client/models/Uploads.js deleted file mode 100644 index 2c3ee0285d98..000000000000 --- a/apps/meteor/app/models/client/models/Uploads.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Base } from './_Base'; - -export class Uploads extends Base { - constructor() { - super(); - this._initModel('uploads'); - } -} - -export default new Uploads(); diff --git a/apps/meteor/app/models/client/models/Uploads.ts b/apps/meteor/app/models/client/models/Uploads.ts new file mode 100644 index 000000000000..d57725841a8b --- /dev/null +++ b/apps/meteor/app/models/client/models/Uploads.ts @@ -0,0 +1,12 @@ +import type { IUpload } from '@rocket.chat/core-typings'; + +import { Base } from './Base'; + +export class Uploads extends Base { + constructor() { + super(); + this._initModel('uploads'); + } +} + +export default new Uploads(); diff --git a/apps/meteor/app/models/client/models/UserAndRoom.js b/apps/meteor/app/models/client/models/UserAndRoom.ts similarity index 100% rename from apps/meteor/app/models/client/models/UserAndRoom.js rename to apps/meteor/app/models/client/models/UserAndRoom.ts diff --git a/apps/meteor/app/models/client/models/UserDataFiles.js b/apps/meteor/app/models/client/models/UserDataFiles.js deleted file mode 100644 index 93c1b1d44720..000000000000 --- a/apps/meteor/app/models/client/models/UserDataFiles.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Base } from './_Base'; - -export class UserDataFiles extends Base { - constructor() { - super(); - this._initModel('userDataFiles'); - } -} - -export default new UserDataFiles(); diff --git a/apps/meteor/app/models/client/models/UserDataFiles.ts b/apps/meteor/app/models/client/models/UserDataFiles.ts new file mode 100644 index 000000000000..c1371ae38e67 --- /dev/null +++ b/apps/meteor/app/models/client/models/UserDataFiles.ts @@ -0,0 +1,12 @@ +import type { IUserDataFile } from '@rocket.chat/core-typings'; + +import { Base } from './Base'; + +export class UserDataFiles extends Base { + constructor() { + super(); + this._initModel('userDataFiles'); + } +} + +export default new UserDataFiles(); diff --git a/apps/meteor/app/models/client/models/UserRoles.js b/apps/meteor/app/models/client/models/UserRoles.js deleted file mode 100644 index de71e371567b..000000000000 --- a/apps/meteor/app/models/client/models/UserRoles.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -export const UserRoles = new Mongo.Collection(null); diff --git a/apps/meteor/app/models/client/models/UserRoles.ts b/apps/meteor/app/models/client/models/UserRoles.ts new file mode 100644 index 000000000000..c3592fe51d12 --- /dev/null +++ b/apps/meteor/app/models/client/models/UserRoles.ts @@ -0,0 +1,9 @@ +import { Mongo } from 'meteor/mongo'; +import type { IRocketChatRecord, IRole } from '@rocket.chat/core-typings'; + +/** @deprecated */ +export const UserRoles = new Mongo.Collection< + IRocketChatRecord & { + roles?: IRole['_id'][]; + } +>(null); diff --git a/apps/meteor/app/models/client/models/Users.js b/apps/meteor/app/models/client/models/Users.js deleted file mode 100644 index c853d21407c4..000000000000 --- a/apps/meteor/app/models/client/models/Users.js +++ /dev/null @@ -1,40 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Mongo } from 'meteor/mongo'; - -export const Users = { - findOneById(userId, options = {}) { - const query = { - _id: userId, - }; - - return this.findOne(query, options); - }, - - /** - * @param {string} userId - * @param {IRole['_id']} roleId - */ - isUserInRole(userId, roleId) { - const user = this.findOneById(userId, { fields: { roles: 1 } }); - return user && Array.isArray(user.roles) && user.roles.includes(roleId); - }, - - /** - * @param {IRole['_id'][]} roles the list of role ids - * @param {string} scope the value for the scope (room id) - * @param {any} options - */ - findUsersInRoles(roles, _scope, options) { - roles = [].concat(roles); - - const query = { - roles: { $in: roles }, - }; - - return this.find(query, options); - }, -}; - -// overwrite Meteor.users collection so records on it don't get erased whenever the client reconnects to websocket -Meteor.users = new Mongo.Collection(null); -Meteor.user = () => Meteor.users.findOne({ _id: Meteor.userId() }); diff --git a/apps/meteor/app/models/client/models/Users.ts b/apps/meteor/app/models/client/models/Users.ts new file mode 100644 index 000000000000..bf97e85b437c --- /dev/null +++ b/apps/meteor/app/models/client/models/Users.ts @@ -0,0 +1,34 @@ +import { Mongo } from 'meteor/mongo'; +import type { IRole, IUser } from '@rocket.chat/core-typings'; + +class UsersCollection extends Mongo.Collection { + constructor() { + super(null); + } + + findOneById(uid: IUser['_id'], options: Omit, 'limit'> = {}) { + const query: Mongo.Selector = { + _id: uid, + }; + + return this.findOne(query, options); + } + + isUserInRole(uid: IUser['_id'], roleId: IRole['_id']) { + const user = this.findOneById(uid, { fields: { roles: 1 } }); + return user && Array.isArray(user.roles) && user.roles.includes(roleId); + } + + findUsersInRoles(roles: IRole['_id'][] | IRole['_id'], _scope: string, options: any) { + roles = Array.isArray(roles) ? roles : [roles]; + + const query: Mongo.Selector = { + roles: { $in: roles }, + }; + + return this.find(query, options); + } +} + +/** @deprecated */ +export const Users = new UsersCollection(); diff --git a/apps/meteor/app/models/client/models/WebdavAccounts.js b/apps/meteor/app/models/client/models/WebdavAccounts.js deleted file mode 100644 index 7b0d22006ccb..000000000000 --- a/apps/meteor/app/models/client/models/WebdavAccounts.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -export const WebdavAccounts = new Mongo.Collection(null); diff --git a/apps/meteor/app/models/client/models/WebdavAccounts.ts b/apps/meteor/app/models/client/models/WebdavAccounts.ts new file mode 100644 index 000000000000..fbc1092cd86e --- /dev/null +++ b/apps/meteor/app/models/client/models/WebdavAccounts.ts @@ -0,0 +1,9 @@ +import { Mongo } from 'meteor/mongo'; + +/** @deprecated */ +export const WebdavAccounts = new Mongo.Collection<{ + _id: string; + name: string; + username: string; + serverURL: string; +}>(null); diff --git a/apps/meteor/app/models/client/models/_Base.js b/apps/meteor/app/models/client/models/_Base.js deleted file mode 100644 index 4c81d84ae863..000000000000 --- a/apps/meteor/app/models/client/models/_Base.js +++ /dev/null @@ -1,54 +0,0 @@ -import { check } from 'meteor/check'; -import { Mongo } from 'meteor/mongo'; - -export class Base { - _baseName() { - return 'rocketchat_'; - } - - _initModel(name) { - check(name, String); - this.model = new Mongo.Collection(this._baseName() + name); - return this.model; - } - - find(...args) { - return this.model.find.apply(this.model, args); - } - - findOne(...args) { - return this.model.findOne.apply(this.model, args); - } - - insert(...args) { - return this.model.insert.apply(this.model, args); - } - - update(...args) { - return this.model.update.apply(this.model, args); - } - - upsert(...args) { - return this.model.upsert.apply(this.model, args); - } - - remove(...args) { - return this.model.remove.apply(this.model, args); - } - - allow(...args) { - return this.model.allow.apply(this.model, args); - } - - deny(...args) { - return this.model.deny.apply(this.model, args); - } - - ensureIndex() {} - - dropIndex() {} - - tryEnsureIndex() {} - - tryDropIndex() {} -} diff --git a/apps/meteor/app/settings/client/lib/settings.ts b/apps/meteor/app/settings/client/lib/settings.ts index 23a460228ccf..01f851589b91 100644 --- a/apps/meteor/app/settings/client/lib/settings.ts +++ b/apps/meteor/app/settings/client/lib/settings.ts @@ -6,9 +6,9 @@ import { PublicSettingsCachedCollection } from '../../../../client/lib/settings/ import { SettingsBase } from '../../lib/settings'; class Settings extends SettingsBase { - cachedCollection = PublicSettingsCachedCollection.get(); + cachedCollection = PublicSettingsCachedCollection; - collection = PublicSettingsCachedCollection.get().collection; + collection = PublicSettingsCachedCollection.collection; dict = new ReactiveDict('settings'); diff --git a/apps/meteor/app/slashcommands-open/client/client.ts b/apps/meteor/app/slashcommands-open/client/client.ts index c2622f00dfbe..f3436b823ecd 100644 --- a/apps/meteor/app/slashcommands-open/client/client.ts +++ b/apps/meteor/app/slashcommands-open/client/client.ts @@ -1,4 +1,6 @@ +import type { RoomType, ISubscription } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; +import type { Mongo } from 'meteor/mongo'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; @@ -8,7 +10,7 @@ import { Subscriptions, ChatSubscription } from '../../models/client'; slashCommands.add({ command: 'open', callback: function Open(_command, params): void { - const dict: Record = { + const dict: Record = { '#': ['c', 'p'], '@': ['d'], }; @@ -16,7 +18,7 @@ slashCommands.add({ const room = params.trim().replace(/#|@/, ''); const type = dict[params.trim()[0]] || []; - const query = { + const query: Mongo.Selector = { name: room, ...(type && { t: { $in: type } }), }; @@ -35,6 +37,9 @@ slashCommands.add({ return; } const subscription = Subscriptions.findOne(query); + if (!subscription) { + return; + } roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); }); }, diff --git a/apps/meteor/app/ui-cached-collection/client/index.js b/apps/meteor/app/ui-cached-collection/client/index.js deleted file mode 100644 index f95870e1a83f..000000000000 --- a/apps/meteor/app/ui-cached-collection/client/index.js +++ /dev/null @@ -1 +0,0 @@ -export { CachedCollection, CachedCollectionManager } from './models/CachedCollection'; diff --git a/apps/meteor/app/ui-cached-collection/client/index.ts b/apps/meteor/app/ui-cached-collection/client/index.ts new file mode 100644 index 000000000000..45776cc4d22c --- /dev/null +++ b/apps/meteor/app/ui-cached-collection/client/index.ts @@ -0,0 +1,2 @@ +export { CachedCollection } from './models/CachedCollection'; +export { CachedCollectionManager } from './models/CachedCollectionManager'; diff --git a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.js b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.js deleted file mode 100644 index cbc9b0fc5b3e..000000000000 --- a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.js +++ /dev/null @@ -1,398 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; -import { Mongo } from 'meteor/mongo'; -import { Accounts } from 'meteor/accounts-base'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Tracker } from 'meteor/tracker'; -import localforage from 'localforage'; -import _ from 'underscore'; -import { Emitter } from '@rocket.chat/emitter'; - -import { callbacks } from '../../../../lib/callbacks'; -import Notifications from '../../../notifications/client/lib/Notifications'; -import { getConfig } from '../../../../client/lib/utils/getConfig'; -import { call } from '../../../../client/lib/utils/call'; -import { omit } from '../../../../lib/utils/omit'; - -const wrap = - (fn) => - (...args) => - new Promise((resolve, reject) => { - fn(...args, (err, result) => { - if (err) { - return reject(err); - } - return resolve(result); - }); - }); - -const localforageGetItem = wrap(localforage.getItem); - -class CachedCollectionManagerClass extends Emitter { - constructor() { - super(); - this.items = []; - this._syncEnabled = false; - this.logged = false; - - const { _unstoreLoginToken } = Accounts; - Accounts._unstoreLoginToken = (...args) => { - _unstoreLoginToken.apply(Accounts, args); - this.clearAllCacheOnLogout(); - }; - - // Wait 1s to start or the code will run before the connection and - // on first connection the `reconnect` callbacks will run - - Tracker.autorun(() => { - const [WAITING_FIRST_CONNECTION, WAITING_FIRST_DISCONNECTION, LISTENING_RECONNECTIONS] = [0, 1, 2]; - this.step = this.step || WAITING_FIRST_CONNECTION; - const { connected } = Meteor.status(); - switch (this.step) { - case WAITING_FIRST_CONNECTION: - return !connected || this.step++; - case WAITING_FIRST_DISCONNECTION: - return connected || this.step++; - case LISTENING_RECONNECTIONS: - return connected && this.emit('reconnect'); - } - }); - - Tracker.autorun(() => { - const uid = Meteor.userId(); - this.logged = uid !== null; - if (this.logged) { - this.emit('login', uid); - } - }); - } - - register(cachedCollection) { - this.items.push(cachedCollection); - } - - clearAllCache() { - for (const item of this.items) { - item.clearCache(); - } - } - - clearAllCacheOnLogout() { - for (const item of this.items) { - item.clearCacheOnLogout(); - } - } - - countQueries() { - for (const item of this.items) { - item.countQueries(); - } - } - - set syncEnabled(value) { - check(value, Boolean); - this._syncEnabled = value; - } - - get syncEnabled() { - return this._syncEnabled; - } - - onReconnect(cb) { - this.on('reconnect', cb); - } - - onLogin(cb) { - this.on('login', cb); - if (this.logged) { - cb(); - } - } -} - -export const CachedCollectionManager = new CachedCollectionManagerClass(); - -const debug = (name) => - [getConfig(`debugCachedCollection-${name}`), getConfig('debugCachedCollection'), getConfig('debug')].includes('true'); - -const nullLog = function () {}; - -const log = (...args) => console.log(`CachedCollection ${this.name} =>`, ...args); - -export class CachedCollection extends Emitter { - constructor({ - collection = new Mongo.Collection(null), - name, - methodName = `${name}/get`, - syncMethodName = `${name}/get`, - eventName = `${name}-changed`, - eventType = 'onUser', - userRelated = true, - listenChangesForLoggedUsersOnly = false, - useSync = true, - version = 17, - maxCacheTime = 60 * 60 * 24 * 30, - onSyncData = (/* action, record */) => {}, - }) { - super(); - this.collection = collection; - - this.ready = new ReactiveVar(false); - this.name = name; - this.methodName = methodName; - this.syncMethodName = syncMethodName; - this.eventName = eventName; - this.eventType = eventType; - this.useSync = useSync; - this.listenChangesForLoggedUsersOnly = listenChangesForLoggedUsersOnly; - this.version = version; - this.userRelated = userRelated; - this.updatedAt = new Date(0); - this.maxCacheTime = maxCacheTime; - this.onSyncData = onSyncData; - this.log = debug(name) ? log : nullLog; - - CachedCollectionManager.register(this); - if (!userRelated) { - this.init(); - return; - } - CachedCollectionManager.onLogin(() => { - this.init(); - }); - } - - countQueries() { - this.log(`${Object.keys(this.collection._collection.queries).length} queries`); - } - - getToken() { - if (this.userRelated === false) { - return undefined; - } - - return Accounts._storedLoginToken(); - } - - async loadFromCache() { - const data = await localforageGetItem(this.name); - if (!data) { - return false; - } - if (data.version < this.version || data.token !== this.getToken()) { - return false; - } - if (data.records.length <= 0) { - return false; - } - - if (new Date() - data.updatedAt >= 1000 * this.maxCacheTime) { - return false; - } - - this.log(`${data.records.length} records loaded from cache`); - - data.records.forEach((record) => { - callbacks.run(`cachedCollection-loadFromCache-${this.name}`, record); - // this.collection.direct.insert(record); - - if (!record._updatedAt) { - return; - } - const _updatedAt = new Date(record._updatedAt); - record._updatedAt = _updatedAt; - - if (record.lastMessage && typeof record.lastMessage._updatedAt === 'string') { - record.lastMessage._updatedAt = new Date(record.lastMessage._updatedAt); - } - - if (_updatedAt > this.updatedAt) { - this.updatedAt = _updatedAt; - } - }); - - this.collection._collection._docs._map = new Map( - data.records.map((record) => [this.collection._collection._docs._idStringify(record._id), record]), - ); - - this.updatedAt = data.updatedAt || this.updatedAt; - - Object.values(this.collection._collection.queries).forEach((query) => this.collection._collection._recomputeResults(query)); - - return true; - } - - async loadFromServer() { - const startTime = new Date(); - const lastTime = this.updatedAt; - const data = await call(this.methodName); - this.log(`${data.length} records loaded from server`); - data.forEach((record) => { - const newRecord = callbacks.run(`cachedCollection-loadFromServer-${this.name}`, record, 'changed'); - this.collection.direct.upsert({ _id: newRecord._id }, omit(newRecord, '_id')); - callbacks.run(`cachedCollection-after-loadFromServer-${this.name}`, record, 'changed'); - - this.onSyncData('changed', newRecord); - - if (newRecord._updatedAt && newRecord._updatedAt > this.updatedAt) { - this.updatedAt = newRecord._updatedAt; - } - }); - this.updatedAt = this.updatedAt === lastTime ? startTime : this.updatedAt; - } - - async loadFromServerAndPopulate() { - await this.loadFromServer(); - this.save(); - } - - save = _.debounce(() => { - this.log('saving cache'); - const data = this.collection.find().fetch(); - localforage.setItem(this.name, { - updatedAt: this.updatedAt, - version: this.version, - token: this.getToken(), - records: data, - }); - this.log('saving cache (done)'); - }, 1000); - - clearCacheOnLogout() { - if (this.userRelated === true) { - this.clearCache(); - } - } - - clearCache() { - this.log('clearing cache'); - localforage.removeItem(this.name); - this.collection.remove({}); - } - - removeRoomFromCacheWhenUserLeaves(roomId, ChatRoom, CachedChatRoom) { - ChatRoom.remove(roomId); - CachedChatRoom.save(); - } - - async setupListener(eventType, eventName) { - const { ChatRoom, CachedChatRoom } = await import('../../../models'); - Notifications[eventType || this.eventType](eventName || this.eventName, (t, record) => { - this.log('record received', t, record); - const newRecord = callbacks.run(`cachedCollection-received-${this.name}`, record, t); - if (t === 'removed') { - let room; - if (this.eventName === 'subscriptions-changed') { - room = ChatRoom.findOne(newRecord.rid); - if (room) { - this.removeRoomFromCacheWhenUserLeaves(room._id, ChatRoom, CachedChatRoom); - } - } else { - room = this.collection.findOne({ - _id: newRecord._id, - }); - } - this.collection.remove(newRecord._id); - } else { - const { _id, ...recordData } = newRecord; - this.collection.direct.upsert({ _id }, recordData); - } - callbacks.run(`cachedCollection-after-received-${this.name}`, record, t); - this.save(); - }); - } - - trySync(delay = 10) { - clearTimeout(this.tm); - // Wait for an empty queue to load data again and sync - this.tm = setTimeout(async () => { - if (!(await this.sync())) { - return this.trySync(delay); - } - this.save(); - }, delay); - } - - async sync() { - if (!this.updatedAt || this.updatedAt.valueOf() === 0 || Meteor.connection._outstandingMethodBlocks.length !== 0) { - return false; - } - - const startTime = new Date(); - const lastTime = this.updatedAt; - - this.log(`syncing from ${this.updatedAt}`); - - const data = await call(this.syncMethodName, this.updatedAt); - let changes = []; - - if (data.update && data.update.length > 0) { - this.log(`${data.update.length} records updated in sync`); - changes.push(...data.update); - } - - if (data.remove && data.remove.length > 0) { - this.log(`${data.remove.length} records removed in sync`); - changes.push(...data.remove); - } - - changes = changes.sort((a, b) => { - const valueA = a._updatedAt || a._deletedAt; - const valueB = b._updatedAt || b._deletedAt; - - if (valueA < valueB) { - return -1; - } - - if (valueA > valueB) { - return 1; - } - - return 0; - }); - - for (const record of changes) { - const action = record._deletedAt ? 'removed' : 'changed'; - const newRecord = callbacks.run(`cachedCollection-sync-${this.name}`, record, action); - const actionTime = newRecord._deletedAt || newRecord._updatedAt; - const { _id, ...recordData } = newRecord; - if (newRecord._deletedAt) { - this.collection.direct.remove({ _id }); - } else { - this.collection.direct.upsert({ _id }, recordData); - } - if (actionTime > this.updatedAt) { - this.updatedAt = actionTime; - } - callbacks.run(`cachedCollection-after-sync-${this.name}`, record, action); - this.onSyncData(action, newRecord); - } - this.updatedAt = this.updatedAt === lastTime ? startTime : this.updatedAt; - - return true; - } - - async init() { - this.ready.set(false); - - if (await this.loadFromCache()) { - this.trySync(); - } else { - await this.loadFromServerAndPopulate(); - } - - this.ready.set(true); - - CachedCollectionManager.onReconnect(() => { - this.trySync(); - }); - - if (!this.userRelated) { - return this.setupListener(); - } - - CachedCollectionManager.onLogin(async () => { - this.setupListener(); - }); - } -} diff --git a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts new file mode 100644 index 000000000000..84100252b47c --- /dev/null +++ b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts @@ -0,0 +1,361 @@ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { Accounts } from 'meteor/accounts-base'; +import { ReactiveVar } from 'meteor/reactive-var'; +import { Emitter } from '@rocket.chat/emitter'; +import localforage from 'localforage'; + +import Notifications from '../../../notifications/client/lib/Notifications'; +import { getConfig } from '../../../../client/lib/utils/getConfig'; +import { call } from '../../../../client/lib/utils/call'; +import { CachedCollectionManager } from './CachedCollectionManager'; +import { withDebouncing } from '../../../../lib/utils/highOrderFunctions'; +import { isTruthy } from '../../../../lib/isTruthy'; + +type Collection = Mongo.Collection & { + _collection: Mongo.Collection & { + queries: Record; + _docs: { + _idStringify: (id: string) => string; + _map: Map; + }; + _recomputeResults: (query: unknown) => void; + }; + direct: Mongo.Collection; +}; + +type EventType = Extract; +type Name = 'rooms' | 'subscriptions' | 'permissions' | 'public-settings' | 'private-settings'; + +const hasId = (record: T): record is T & { _id: string } => typeof record === 'object' && record !== null && '_id' in record; +const hasUpdatedAt = (record: T): record is T & { _updatedAt: Date } => + typeof record === 'object' && + record !== null && + '_updatedAt' in record && + (record as unknown as { _updatedAt: unknown })._updatedAt instanceof Date; +const hasDeletedAt = (record: T): record is T & { _deletedAt: Date } => + typeof record === 'object' && + record !== null && + '_deletedAt' in record && + (record as unknown as { _deletedAt: unknown })._deletedAt instanceof Date; +const hasUnserializedUpdatedAt = (record: T): record is T & { _updatedAt: ConstructorParameters[0] } => + typeof record === 'object' && + record !== null && + '_updatedAt' in record && + !((record as unknown as { _updatedAt: unknown })._updatedAt instanceof Date); + +export class CachedCollection extends Emitter<{ changed: T; removed: T }> { + private static MAX_CACHE_TIME = 60 * 60 * 24 * 30; + + public collection: Collection; + + public ready = new ReactiveVar(false); + + public name: Name; + + public eventType: EventType; + + public version = 17; + + public userRelated: boolean; + + public updatedAt = new Date(0); + + public log: (...args: any[]) => void; + + public timer: ReturnType; + + constructor({ name, eventType = 'onUser', userRelated = true }: { name: Name; eventType?: EventType; userRelated?: boolean }) { + super(); + + this.collection = new Mongo.Collection(null) as Collection; + + this.name = name; + this.eventType = eventType; + this.userRelated = userRelated; + + this.log = [getConfig(`debugCachedCollection-${this.name}`), getConfig('debugCachedCollection'), getConfig('debug')].includes('true') + ? console.log.bind(console, `%cCachedCollection ${this.name}`, `color: navy; font-weight: bold;`) + : () => undefined; + + CachedCollectionManager.register(this); + + if (!userRelated) { + this.init(); + return; + } + + CachedCollectionManager.onLogin(() => { + this.init(); + }); + } + + protected get eventName() { + return `${this.name}-changed`; + } + + getToken() { + if (this.userRelated === false) { + return undefined; + } + + return Accounts._storedLoginToken(); + } + + private async loadFromCache() { + const data = await localforage.getItem<{ version: number; token: unknown; records: unknown[]; updatedAt: Date }>(this.name); + + if (!data) { + return false; + } + + if (data.version < this.version || data.token !== this.getToken()) { + return false; + } + + if (data.records.length <= 0) { + return false; + } + + if (Date.now() - data.updatedAt.getTime() >= 1000 * CachedCollection.MAX_CACHE_TIME) { + return false; + } + + this.log(`${data.records.length} records loaded from cache`); + + const deserializedRecords = data.records.map((record) => this.deserializeFromCache(record)).filter(isTruthy); + + const updatedAt = Math.max(...deserializedRecords.filter(hasUpdatedAt).map((record) => record?._updatedAt.getTime() ?? 0)); + + if (updatedAt > this.updatedAt.getTime()) { + this.updatedAt = new Date(updatedAt); + } + + this.collection._collection._docs._map = new Map( + deserializedRecords.filter(hasId).map((record) => [this.collection._collection._docs._idStringify(record._id), record]), + ); + + this.updatedAt = data.updatedAt || this.updatedAt; + + Object.values(this.collection._collection.queries).forEach((query) => this.collection._collection._recomputeResults(query)); + + return true; + } + + protected deserializeFromCache(record: unknown): T | undefined { + if (typeof record !== 'object' || record === null) { + return undefined; + } + + return { + ...(record as unknown as T), + ...(hasUnserializedUpdatedAt(record) && { + _updatedAt: new Date(record._updatedAt), + }), + }; + } + + private async callLoad() { + // TODO: workaround for bad function overload + const data = await call(`${this.name}/get`); + return data as unknown as T[]; + } + + private async callSync(updatedSince: Date) { + // TODO: workaround for bad function overload + const data = await call(`${this.name}/get`, updatedSince); + return data as unknown as { update: T[]; remove: T[] }; + } + + private async loadFromServer() { + const startTime = new Date(); + const lastTime = this.updatedAt; + const data = await this.callLoad(); + this.log(`${data.length} records loaded from server`); + + data.forEach((record) => { + const newRecord = this.handleLoadFromServer(record); + if (!hasId(newRecord)) { + return; + } + + const { _id, ...data } = newRecord; + this.collection.direct.upsert({ _id } as Mongo.Selector, { $set: data } as Mongo.Modifier); + this.emit('changed', newRecord as any); // TODO: investigate why this is needed + + if (hasUpdatedAt(newRecord) && newRecord._updatedAt > this.updatedAt) { + this.updatedAt = newRecord._updatedAt; + } + }); + this.updatedAt = this.updatedAt === lastTime ? startTime : this.updatedAt; + } + + protected handleLoadFromServer(record: T) { + return record; + } + + protected handleReceived(record: T, _action: 'removed' | 'changed') { + return record; + } + + protected handleSync(record: T, _action: 'removed' | 'changed') { + return record; + } + + private async loadFromServerAndPopulate() { + await this.loadFromServer(); + this.save(); + } + + save = withDebouncing({ wait: 1000 })(async () => { + this.log('saving cache'); + const data = this.collection.find().fetch(); + localforage.setItem(this.name, { + updatedAt: this.updatedAt, + version: this.version, + token: this.getToken(), + records: data, + }); + this.log('saving cache (done)'); + }); + + clearCacheOnLogout() { + if (this.userRelated === true) { + this.clearCache(); + } + } + + async clearCache() { + this.log('clearing cache'); + await localforage.removeItem(this.name); + this.collection.remove({}); + } + + async setupListener() { + Notifications[this.eventType](this.eventName, (action: 'removed' | 'changed', record: any) => { + this.log('record received', action, record); + const newRecord = this.handleReceived(record, action); + + if (!hasId(newRecord)) { + return; + } + + if (action === 'removed') { + this.collection.remove(newRecord._id); + } else { + const { _id, ...data } = newRecord; + this.collection.direct.upsert({ _id } as Mongo.Selector, { $set: data } as Mongo.Modifier); + } + this.save(); + }); + } + + trySync(delay = 10) { + clearTimeout(this.timer); + // Wait for an empty queue to load data again and sync + this.timer = setTimeout(async () => { + if (!(await this.sync())) { + return this.trySync(delay); + } + this.save(); + }, delay); + } + + async sync() { + if (!this.updatedAt || this.updatedAt.getTime() === 0 || Meteor.connection._outstandingMethodBlocks.length !== 0) { + return false; + } + + const startTime = new Date(); + const lastTime = this.updatedAt; + + this.log(`syncing from ${this.updatedAt}`); + + const data = await this.callSync(this.updatedAt); + const changes = []; + + if (data.update && data.update.length > 0) { + this.log(`${data.update.length} records updated in sync`); + for (const record of data.update) { + const action = 'changed'; + const newRecord = this.handleSync(record, action); + + if (!hasId(newRecord) || !hasUpdatedAt(newRecord)) { + continue; + } + + const actionTime = newRecord._updatedAt; + changes.push({ + action: () => { + const { _id, ...data } = newRecord; + this.collection.direct.upsert({ _id } as Mongo.Selector, { $set: data } as Mongo.Modifier); + if (actionTime > this.updatedAt) { + this.updatedAt = actionTime; + } + this.emit(action, newRecord as any); // TODO: investigate why this is needed + }, + timestamp: actionTime.getTime(), + }); + } + } + + if (data.remove && data.remove.length > 0) { + this.log(`${data.remove.length} records removed in sync`); + for (const record of data.remove) { + const action = 'removed'; + const newRecord = this.handleSync(record, action); + + if (!hasId(newRecord) || !hasDeletedAt(newRecord)) { + continue; + } + + const actionTime = newRecord._deletedAt; + changes.push({ + action: () => { + const { _id } = newRecord; + this.collection.direct.remove({ _id } as Mongo.Selector); + if (actionTime > this.updatedAt) { + this.updatedAt = actionTime; + } + this.emit(action, newRecord as any); // TODO: investigate why this is needed + }, + timestamp: actionTime.getTime(), + }); + } + } + + changes + .sort((a, b) => a.timestamp - b.timestamp) + .forEach((c) => { + c.action(); + }); + + this.updatedAt = this.updatedAt === lastTime ? startTime : this.updatedAt; + + return true; + } + + async init() { + this.ready.set(false); + + if (await this.loadFromCache()) { + this.trySync(); + } else { + await this.loadFromServerAndPopulate(); + } + + this.ready.set(true); + + CachedCollectionManager.onReconnect(() => { + this.trySync(); + }); + + if (!this.userRelated) { + return this.setupListener(); + } + + CachedCollectionManager.onLogin(async () => { + this.setupListener(); + }); + } +} diff --git a/apps/meteor/app/ui-cached-collection/client/models/CachedCollectionManager.ts b/apps/meteor/app/ui-cached-collection/client/models/CachedCollectionManager.ts new file mode 100644 index 000000000000..dc77fba22e9e --- /dev/null +++ b/apps/meteor/app/ui-cached-collection/client/models/CachedCollectionManager.ts @@ -0,0 +1,95 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import { Accounts } from 'meteor/accounts-base'; +import { Tracker } from 'meteor/tracker'; +import { Emitter } from '@rocket.chat/emitter'; + +import type { CachedCollection } from './CachedCollection'; + +class CachedCollectionManager extends Emitter<{ reconnect: void; login: string | null }> { + private items: CachedCollection[]; + + private _syncEnabled: boolean; + + private logged: boolean; + + private step: number; + + constructor() { + super(); + this.items = []; + this._syncEnabled = false; + this.logged = false; + + const { _unstoreLoginToken } = Accounts; + Accounts._unstoreLoginToken = (...args) => { + _unstoreLoginToken.apply(Accounts, args); + this.clearAllCacheOnLogout(); + }; + + Tracker.autorun(() => { + const [WAITING_FIRST_CONNECTION, WAITING_FIRST_DISCONNECTION, LISTENING_RECONNECTIONS] = [0, 1, 2]; + this.step = this.step || WAITING_FIRST_CONNECTION; + const { connected } = Meteor.status(); + switch (this.step) { + case WAITING_FIRST_CONNECTION: + return !connected || this.step++; + case WAITING_FIRST_DISCONNECTION: + return connected || this.step++; + case LISTENING_RECONNECTIONS: + return connected && this.emit('reconnect'); + } + }); + + Tracker.autorun(() => { + const uid = Meteor.userId(); + this.logged = uid !== null; + if (this.logged) { + this.emit('login', uid); + } + }); + } + + register(cachedCollection: CachedCollection) { + this.items.push(cachedCollection); + } + + clearAllCache() { + for (const item of this.items) { + item.clearCache(); + } + } + + clearAllCacheOnLogout() { + for (const item of this.items) { + item.clearCacheOnLogout(); + } + } + + set syncEnabled(value) { + check(value, Boolean); + this._syncEnabled = value; + } + + get syncEnabled() { + return this._syncEnabled; + } + + onReconnect(cb: () => void) { + this.on('reconnect', cb); + } + + onLogin(cb: () => void) { + this.on('login', cb); + if (this.logged) { + cb(); + } + } +} + +export const instance = new CachedCollectionManager(); + +export { + /** @deprecated */ + instance as CachedCollectionManager, +}; diff --git a/apps/meteor/app/ui-cached-collection/index.js b/apps/meteor/app/ui-cached-collection/index.js deleted file mode 100644 index 40a7340d3887..000000000000 --- a/apps/meteor/app/ui-cached-collection/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './client/index'; diff --git a/apps/meteor/app/ui-message/client/ActionManager.js b/apps/meteor/app/ui-message/client/ActionManager.js index 4667afbf4e37..bee4cfdae2d5 100644 --- a/apps/meteor/app/ui-message/client/ActionManager.js +++ b/apps/meteor/app/ui-message/client/ActionManager.js @@ -6,7 +6,7 @@ import { Emitter } from '@rocket.chat/emitter'; import { UIKitInteractionTypes } from '@rocket.chat/core-typings'; import Notifications from '../../notifications/client/lib/Notifications'; -import { CachedCollectionManager } from '../../ui-cached-collection'; +import { CachedCollectionManager } from '../../ui-cached-collection/client'; import { modal } from '../../ui-utils/client/lib/modal'; import { APIClient, t } from '../../utils/client'; import * as banners from '../../../client/lib/banners'; diff --git a/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts b/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts index 99a4e5e93d4d..db0bdffb6dfb 100644 --- a/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts +++ b/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts @@ -169,7 +169,7 @@ class RoomHistoryManagerClass extends Emitter { ({ ls } = subscription); } - const result = await callWithErrorHandling('loadHistory', rid, ts, limit, ls, false); + const result = await callWithErrorHandling('loadHistory', rid, ts, limit, ls ? String(ls) : undefined, false); this.unqueue(); diff --git a/apps/meteor/app/ui-utils/client/lib/RoomManager.ts b/apps/meteor/app/ui-utils/client/lib/RoomManager.ts index 187f26fb1206..26c54685c050 100644 --- a/apps/meteor/app/ui-utils/client/lib/RoomManager.ts +++ b/apps/meteor/app/ui-utils/client/lib/RoomManager.ts @@ -138,6 +138,9 @@ const handleTrackSettingsChange = (msg: IMessage) => { close(type + FlowRouter.getParam('name')); const subscription = ChatSubscription.findOne({ rid: msg.rid }); + if (!subscription) { + throw new Error('Subscription not found'); + } const route = subscription.t === 'c' ? 'channel' : 'group'; FlowRouter.go(route, { name: subscription.name }, FlowRouter.current().queryParams); } diff --git a/apps/meteor/app/ui-utils/client/lib/messageContext.ts b/apps/meteor/app/ui-utils/client/lib/messageContext.ts index 2e07b22e6563..c5ad7e0d2fc0 100644 --- a/apps/meteor/app/ui-utils/client/lib/messageContext.ts +++ b/apps/meteor/app/ui-utils/client/lib/messageContext.ts @@ -23,7 +23,7 @@ const fields = { export const createMessageContext = ({ uid = Meteor.userId(), - user = Users.findOne({ _id: uid }, { fields }) || {}, + user = (uid ? Users.findOne({ _id: uid }, { fields }) : {}) || {}, rid = (Template.instance() as CommonRoomTemplateInstance).data.rid, room = Tracker.nonreactive(() => Rooms.findOne( diff --git a/apps/meteor/app/ui-utils/client/lib/readMessages.ts b/apps/meteor/app/ui-utils/client/lib/readMessages.ts index b6a90fcf4758..6481e09eb53c 100644 --- a/apps/meteor/app/ui-utils/client/lib/readMessages.ts +++ b/apps/meteor/app/ui-utils/client/lib/readMessages.ts @@ -134,7 +134,7 @@ export class ReadMessage extends Emitter { lastReadRecord = { ts: new Date(0) }; } - room.unreadSince.set((lastReadRecord || unreadNotLoaded.get() > 0) && subscription.ls); + room.unreadSince.set(lastReadRecord || unreadNotLoaded.get() > 0 ? subscription.ls : undefined); if (!lastReadRecord) { return; diff --git a/apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx b/apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx index f047cde8f03f..8770600de83c 100644 --- a/apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx +++ b/apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx @@ -1,5 +1,5 @@ import { OptionTitle } from '@rocket.chat/fuselage'; -import { useTranslation, useRoute } from '@rocket.chat/ui-contexts'; +import { useTranslation, useRoute, useMethod, useSetModal } from '@rocket.chat/ui-contexts'; import { FlowRouter } from 'meteor/kadira:flow-router'; import type { FC } from 'react'; import React from 'react'; @@ -10,6 +10,8 @@ import { getUpgradeTabLabel, isFullyFeature } from '../../../lib/upgradeTab'; import { useUpgradeTabParams } from '../../views/hooks/useUpgradeTabParams'; import Emoji from '../Emoji'; import ListItem from '../Sidebar/ListItem'; +import { useQuery } from '@tanstack/react-query'; +import RegisterWorkspaceModal from '../../views/admin/cloud/modals/WorkspaceRegistrationModal'; type AdministrationModelListProps = { accountBoxItems: AccountBoxItem[]; @@ -25,10 +27,21 @@ const AdministrationModelList: FC = ({ accountBoxI const shouldShowEmoji = isFullyFeature(tabType); const label = getUpgradeTabLabel(tabType); const hasInfoPermission = userHasAllPermission(INFO_PERMISSIONS); + const setModal = useSetModal(); + + const checkCloudRegisterStatus = useMethod('cloud:checkRegisterStatus'); + const result = useQuery(['admin/cloud/register-status'], async () => checkCloudRegisterStatus()); + const { workspaceRegistered } = result.data || {}; + + const handleRegisterWorkspaceClick = (): void => { + const handleModalClose = (): void => setModal(null); + setModal(); + }; const infoRoute = useRoute('admin-info'); const adminRoute = useRoute('admin-index'); const upgradeRoute = useRoute('upgrade'); + const cloudRoute = useRoute('cloud'); const showUpgradeItem = !isLoading && tabType; return ( @@ -49,6 +62,18 @@ const AdministrationModelList: FC = ({ accountBoxI }} /> )} + { + if (workspaceRegistered) { + cloudRoute.push({ context: '/' }); + onDismiss(); + return; + } + handleRegisterWorkspaceClick(); + }} + /> {showWorkspace && ( , - rooms: Rooms as Mongo.Collection, - subscriptions: Subscriptions as Mongo.Collection, + users: Users, + rooms: Rooms, + subscriptions: Subscriptions, roles: Roles as Mongo.Collection, - roomRoles: RoomRoles as Mongo.Collection>, + roomRoles: RoomRoles, } as const; export const useReactiveQuery = ( diff --git a/apps/meteor/client/lib/chats/data.ts b/apps/meteor/client/lib/chats/data.ts index d9bea3da0676..f393252f1354 100644 --- a/apps/meteor/client/lib/chats/data.ts +++ b/apps/meteor/client/lib/chats/data.ts @@ -1,9 +1,8 @@ -import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; -import type { Mongo } from 'meteor/mongo'; +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import moment from 'moment'; import { hasAtLeastOnePermission, hasPermission } from '../../../app/authorization/client'; -import { Messages, Rooms, Subscriptions } from '../../../app/models/client'; +import { Messages, ChatRoom, ChatSubscription } from '../../../app/models/client'; import { settings } from '../../../app/settings/client'; import { readMessage, MessageTypes } from '../../../app/ui-utils/client'; import { getRandomId } from '../../../lib/random'; @@ -12,9 +11,6 @@ import { call } from '../utils/call'; import { prependReplies } from '../utils/prependReplies'; import type { DataAPI } from './ChatAPI'; -const roomsCollection = Rooms as Mongo.Collection; -const subscriptionsCollection = Subscriptions as Mongo.Collection; - export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage['_id'] | undefined }): DataAPI => { const composeMessage = async ( text: string, @@ -233,7 +229,7 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage drafts.set(mid, draft); }; - const findRoom = async (): Promise => roomsCollection.findOne({ _id: rid }, { reactive: false }); + const findRoom = async (): Promise => ChatRoom.findOne({ _id: rid }, { reactive: false }); const getRoom = async (): Promise => { const room = await findRoom(); @@ -245,7 +241,7 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage return room; }; - const isSubscribedToRoom = async (): Promise => !!subscriptionsCollection.findOne({ rid }, { reactive: false }); + const isSubscribedToRoom = async (): Promise => !!ChatSubscription.findOne({ rid }, { reactive: false }); const joinRoom = async (): Promise => call('joinRoom', rid); @@ -255,7 +251,7 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage }; const findDiscussionByID = async (drid: IRoom['_id']): Promise => - roomsCollection.findOne({ _id: drid, prid: { $exists: true } }, { reactive: false }); + ChatRoom.findOne({ _id: drid, prid: { $exists: true } }, { reactive: false }); const getDiscussionByID = async (drid: IRoom['_id']): Promise => { const discussion = await findDiscussionByID(drid); diff --git a/apps/meteor/client/lib/rooms/roomTypes/direct.ts b/apps/meteor/client/lib/rooms/roomTypes/direct.ts index 4d1ce755939a..34a3552dbbb4 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/direct.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/direct.ts @@ -1,6 +1,7 @@ -import type { AtLeast, IRoom } from '@rocket.chat/core-typings'; +import type { AtLeast, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; import { isRoomFederated } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; +import type { Mongo } from 'meteor/mongo'; import { Session } from 'meteor/session'; import { hasAtLeastOnePermission } from '../../../../app/authorization/client'; @@ -114,7 +115,7 @@ roomCoordinator.add(DirectMessageRoomType, { const sub = Subscriptions.findOne({ rid: room._id }, { fields: { name: 1 } }); if (sub?.name) { - const user = Users.findOne({ username: sub.name }, { fields: { username: 1, avatarETag: 1 } }); + const user = Users.findOne({ username: sub.name }, { fields: { username: 1, avatarETag: 1 } }) as IUser | undefined; return getUserAvatarURL(user?.username || sub.name, user?.avatarETag); } @@ -139,7 +140,7 @@ roomCoordinator.add(DirectMessageRoomType, { }, findRoom(identifier) { - const query = { + const query: Mongo.Selector = { t: 'd', $or: [{ name: identifier }, { rid: identifier }], }; diff --git a/apps/meteor/client/lib/settings/PrivateSettingsCachedCollection.ts b/apps/meteor/client/lib/settings/PrivateSettingsCachedCollection.ts index 7cccd3ba8033..894c90fba288 100644 --- a/apps/meteor/client/lib/settings/PrivateSettingsCachedCollection.ts +++ b/apps/meteor/client/lib/settings/PrivateSettingsCachedCollection.ts @@ -1,7 +1,9 @@ +import type { ISetting } from '@rocket.chat/core-typings'; + import { Notifications } from '../../../app/notifications/client'; import { CachedCollection } from '../../../app/ui-cached-collection/client'; -export class PrivateSettingsCachedCollection extends CachedCollection { +class PrivateSettingsCachedCollection extends CachedCollection { constructor() { super({ name: 'private-settings', @@ -16,14 +18,11 @@ export class PrivateSettingsCachedCollection extends CachedCollection { this.sync(); }); } +} - static instance: PrivateSettingsCachedCollection; - - static get(): PrivateSettingsCachedCollection { - if (!PrivateSettingsCachedCollection.instance) { - PrivateSettingsCachedCollection.instance = new PrivateSettingsCachedCollection(); - } +const instance = new PrivateSettingsCachedCollection(); - return PrivateSettingsCachedCollection.instance; - } -} +export { + /** @deprecated */ + instance as PrivateSettingsCachedCollection, +}; diff --git a/apps/meteor/client/lib/settings/PublicSettingsCachedCollection.ts b/apps/meteor/client/lib/settings/PublicSettingsCachedCollection.ts index f6ebca428dd7..7eab4b1dc7a9 100644 --- a/apps/meteor/client/lib/settings/PublicSettingsCachedCollection.ts +++ b/apps/meteor/client/lib/settings/PublicSettingsCachedCollection.ts @@ -1,22 +1,20 @@ +import type { ISetting } from '@rocket.chat/core-typings'; + import { CachedCollection } from '../../../app/ui-cached-collection/client'; -export class PublicSettingsCachedCollection extends CachedCollection { +class PublicSettingsCachedCollection extends CachedCollection { constructor() { super({ name: 'public-settings', eventType: 'onAll', userRelated: false, - listenChangesForLoggedUsersOnly: true, }); } +} - static instance: PublicSettingsCachedCollection; - - static get(): PublicSettingsCachedCollection { - if (!PublicSettingsCachedCollection.instance) { - PublicSettingsCachedCollection.instance = new PublicSettingsCachedCollection(); - } +const instance = new PublicSettingsCachedCollection(); - return PublicSettingsCachedCollection.instance; - } -} +export { + /** @deprecated */ + instance as PublicSettingsCachedCollection, +}; diff --git a/apps/meteor/client/lib/userData.ts b/apps/meteor/client/lib/userData.ts index 76ecb475dbd2..44a75026725b 100644 --- a/apps/meteor/client/lib/userData.ts +++ b/apps/meteor/client/lib/userData.ts @@ -36,7 +36,7 @@ type RawUserData = Serialized< >; const updateUser = (userData: IUser): void => { - const user: IUser = Users.findOne({ _id: userData._id }); + const user = Users.findOne({ _id: userData._id }) as IUser | undefined; if (!user || !user._updatedAt || user._updatedAt.getTime() < userData._updatedAt.getTime()) { Meteor.users.upsert({ _id: userData._id }, userData as Meteor.User); diff --git a/apps/meteor/client/providers/AuthorizationProvider.tsx b/apps/meteor/client/providers/AuthorizationProvider.tsx index b873781de1c0..457ae2064c34 100644 --- a/apps/meteor/client/providers/AuthorizationProvider.tsx +++ b/apps/meteor/client/providers/AuthorizationProvider.tsx @@ -33,7 +33,7 @@ const AuthorizationProvider: FC = ({ children }) => { .reduce((ret, obj) => { ret[obj._id] = obj; return ret; - }, {}), + }, {} as Record), [], ), ); diff --git a/apps/meteor/client/providers/SettingsProvider.tsx b/apps/meteor/client/providers/SettingsProvider.tsx index 5ab0f770b5b8..87734c6afcca 100644 --- a/apps/meteor/client/providers/SettingsProvider.tsx +++ b/apps/meteor/client/providers/SettingsProvider.tsx @@ -1,3 +1,4 @@ +import type { ISetting } from '@rocket.chat/core-typings'; import type { SettingsContextValue } from '@rocket.chat/ui-contexts'; import { SettingsContext, useAtLeastOnePermission, useMethod } from '@rocket.chat/ui-contexts'; import { Tracker } from 'meteor/tracker'; @@ -21,7 +22,7 @@ const SettingsProvider: FunctionComponent = ({ children, const hasPrivateAccess = privileged && hasPrivilegedPermission; const cachedCollection = useMemo( - () => (hasPrivateAccess ? PrivateSettingsCachedCollection.get() : PublicSettingsCachedCollection.get()), + () => (hasPrivateAccess ? PrivateSettingsCachedCollection : PublicSettingsCachedCollection), [hasPrivateAccess], ); @@ -50,7 +51,11 @@ const SettingsProvider: FunctionComponent = ({ children, }, [cachedCollection]); const querySetting = useMemo( - () => createReactiveSubscriptionFactory((_id) => ({ ...cachedCollection.collection.findOne(_id) })), + () => + createReactiveSubscriptionFactory((_id): ISetting | undefined => { + const subscription = cachedCollection.collection.findOne(_id); + return subscription ? { ...subscription } : undefined; + }), [cachedCollection], ); @@ -67,7 +72,7 @@ const SettingsProvider: FunctionComponent = ({ children, (query.section ? { section: query.section } : { - $or: [{ section: { $exists: false } }, { section: null }], + $or: [{ section: { $exists: false } }, { section: undefined }], })), }, { @@ -99,7 +104,7 @@ const SettingsProvider: FunctionComponent = ({ children, const saveSettings = useMethod('saveSettings'); const dispatch = useCallback( async (changes) => { - await settingsChangeCallback(changes); + settingsChangeCallback(changes); await saveSettings(changes); }, [saveSettings], @@ -120,3 +125,6 @@ const SettingsProvider: FunctionComponent = ({ children, }; export default SettingsProvider; + +// '[subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => {}]' +// '[subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting | undefined]' diff --git a/apps/meteor/client/providers/UserProvider.tsx b/apps/meteor/client/providers/UserProvider.tsx index c0361867edda..0e9c45a6a382 100644 --- a/apps/meteor/client/providers/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider.tsx @@ -1,8 +1,8 @@ import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; -import type { LoginService } from '@rocket.chat/ui-contexts'; import { UserContext, useSetting } from '@rocket.chat/ui-contexts'; +import type { LoginService } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -import type { FC } from 'react'; +import type { ContextType, FC } from 'react'; import React, { useEffect, useMemo } from 'react'; import { Subscriptions, Rooms } from '../../app/models/client'; @@ -76,19 +76,23 @@ const UserProvider: FC = ({ children }) => { }, [isLdapEnabled, isCrowdEnabled, loginMethod]); const contextValue = useMemo( - () => ({ + (): ContextType => ({ userId, user, queryPreference: createReactiveSubscriptionFactory( (key: string, defaultValue?: T) => getUserPreference(userId, key, defaultValue) as T, ), - querySubscription: createReactiveSubscriptionFactory((query, fields) => - Subscriptions.findOne(query, { fields }), + querySubscription: createReactiveSubscriptionFactory((query, fields, sort) => + Subscriptions.findOne(query, { fields, sort }), ), queryRoom: createReactiveSubscriptionFactory((query, fields) => Rooms.findOne(query, { fields })), - querySubscriptions: createReactiveSubscriptionFactory | []>((query, options) => - (userId ? Subscriptions : Rooms).find(query, options).fetch(), - ), + querySubscriptions: createReactiveSubscriptionFactory | []>((query, options) => { + if (userId) { + return Subscriptions.find(query, options).fetch(); + } + + return Rooms.find(query, options).fetch(); + }), loginWithToken: (token: string): Promise => new Promise((resolve, reject) => Meteor.loginWithToken(token, (err) => { diff --git a/apps/meteor/client/sidebar/Sidebar.stories.tsx b/apps/meteor/client/sidebar/Sidebar.stories.tsx index f5f03ece9b0f..2f8d31e7ed90 100644 --- a/apps/meteor/client/sidebar/Sidebar.stories.tsx +++ b/apps/meteor/client/sidebar/Sidebar.stories.tsx @@ -25,6 +25,7 @@ const settings: Record = { type: 'boolean', value: true, public: true, + _updatedAt: new Date(), }, }; diff --git a/apps/meteor/client/sidebar/hooks/useQueryOptions.ts b/apps/meteor/client/sidebar/hooks/useQueryOptions.ts index ffacb7fb035e..55fd137759d1 100644 --- a/apps/meteor/client/sidebar/hooks/useQueryOptions.ts +++ b/apps/meteor/client/sidebar/hooks/useQueryOptions.ts @@ -4,15 +4,15 @@ import { useMemo } from 'react'; export const useQueryOptions = (): { sort: | { - lm?: number | undefined; + lm?: -1 | 1 | undefined; } | { - lowerCaseFName: number; - lm?: number | undefined; + lowerCaseFName: -1 | 1; + lm?: -1 | 1 | undefined; } | { - lowerCaseName: number; - lm?: number | undefined; + lowerCaseName: -1 | 1; + lm?: -1 | 1 | undefined; }; } => { const sortBy = useUserPreference('sidebarSortby'); diff --git a/apps/meteor/client/sidebar/search/SearchList.tsx b/apps/meteor/client/sidebar/search/SearchList.tsx index c60fc1281a6b..7bd188092908 100644 --- a/apps/meteor/client/sidebar/search/SearchList.tsx +++ b/apps/meteor/client/sidebar/search/SearchList.tsx @@ -41,7 +41,7 @@ const options = { lm: -1, name: 1, }, -}; +} as const; const useSearchItems = (filterText: string): UseQueryResult<(ISubscription & IRoom)[] | undefined, Error> => { const expression = /(@|#)?(.*)/i; diff --git a/apps/meteor/client/startup/i18n.ts b/apps/meteor/client/startup/i18n.ts index fc98ea2b1a2a..41681a237859 100644 --- a/apps/meteor/client/startup/i18n.ts +++ b/apps/meteor/client/startup/i18n.ts @@ -1,3 +1,4 @@ +import type { IUser } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; @@ -71,7 +72,12 @@ Meteor.startup(() => { window.defaultUserLanguage = defaultUserLanguage; Tracker.autorun(() => { - const user = Users.findOne(Meteor.userId(), { fields: { language: 1 } }); + const uid = Meteor.userId(); + if (!uid) { + return; + } + + const user = Users.findOne(uid, { fields: { language: 1 } }) as IUser | undefined; setLanguage(user?.language || defaultUserLanguage()); }); diff --git a/apps/meteor/client/startup/incomingMessages.ts b/apps/meteor/client/startup/incomingMessages.ts index a84921529e43..ad3b8cd82ef9 100644 --- a/apps/meteor/client/startup/incomingMessages.ts +++ b/apps/meteor/client/startup/incomingMessages.ts @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { ChatMessage } from '../../app/models/client'; import { Notifications } from '../../app/notifications/client'; -import { CachedCollectionManager } from '../../app/ui-cached-collection'; +import { CachedCollectionManager } from '../../app/ui-cached-collection/client'; Meteor.startup(() => { Tracker.autorun(() => { diff --git a/apps/meteor/client/startup/index.ts b/apps/meteor/client/startup/index.ts index 74248fa7187e..4a2566dfd937 100644 --- a/apps/meteor/client/startup/index.ts +++ b/apps/meteor/client/startup/index.ts @@ -15,7 +15,6 @@ import './incomingMessages'; import './ldap'; import './loadMissedMessages'; import './loginViaQuery'; -import './mergeSubscriptionsAndRooms'; import './messageObserve'; import './messageTypes'; import './notifications'; diff --git a/apps/meteor/client/startup/mergeSubscriptionsAndRooms.ts b/apps/meteor/client/startup/mergeSubscriptionsAndRooms.ts deleted file mode 100644 index 0130e659a077..000000000000 --- a/apps/meteor/client/startup/mergeSubscriptionsAndRooms.ts +++ /dev/null @@ -1,179 +0,0 @@ -import type { IOmnichannelRoom, IRoom, IRoomWithRetentionPolicy, ISubscription } from '@rocket.chat/core-typings'; -import type { Mongo } from 'meteor/mongo'; - -import { Rooms, Subscriptions } from '../../app/models/client'; -import { callbacks } from '../../lib/callbacks'; -import type { SubscriptionWithRoom } from '../definitions/SubscriptionWithRoom'; - -const getLowerCaseNames = ( - room: Pick, - nameDefault = '', - fnameDefault = '', -): { - lowerCaseName: string; - lowerCaseFName: string; -} => { - const name = room.name || nameDefault; - const fname = room.fname || fnameDefault || name; - return { - lowerCaseName: String(!room.prid ? name : fname).toLowerCase(), - lowerCaseFName: String(fname).toLowerCase(), - }; -}; - -const mergeSubRoom = (subscription: ISubscription): SubscriptionWithRoom => { - const options = { - fields: { - lm: 1, - lastMessage: 1, - uids: 1, - streamingOptions: 1, - usernames: 1, - usersCount: 1, - topic: 1, - encrypted: 1, - description: 1, - announcement: 1, - broadcast: 1, - archived: 1, - avatarETag: 1, - retention: 1, - teamId: 1, - teamMain: 1, - msgs: 1, - onHold: 1, - metrics: 1, - muted: 1, - servedBy: 1, - ts: 1, - waitingResponse: 1, - v: 1, - transcriptRequest: 1, - tags: 1, - closedAt: 1, - responseBy: 1, - priorityId: 1, - livechatData: 1, - departmentId: 1, - source: 1, - queuedAt: 1, - federated: 1, - }, - }; - - const room = (Rooms as Mongo.Collection).findOne({ _id: subscription.rid }, options); - - const lastRoomUpdate = room?.lm || subscription.ts || subscription._updatedAt; - - return { - ...subscription, - ...getLowerCaseNames(subscription), - encrypted: room?.encrypted, - description: room?.description, - cl: room?.cl, - topic: room?.topic, - announcement: room?.announcement, - broadcast: room?.broadcast, - archived: room?.archived, - avatarETag: room?.avatarETag, - retention: (room as IRoomWithRetentionPolicy | undefined)?.retention, - lastMessage: room?.lastMessage, - streamingOptions: room?.streamingOptions, - teamId: room?.teamId, - teamMain: room?.teamMain, - uids: room?.uids, - usernames: room?.usernames, - usersCount: room?.usersCount ?? 0, - v: (room as IOmnichannelRoom | undefined)?.v, - transcriptRequest: (room as IOmnichannelRoom | undefined)?.transcriptRequest, - servedBy: (room as IOmnichannelRoom | undefined)?.servedBy, - onHold: (room as IOmnichannelRoom | undefined)?.onHold, - tags: (room as IOmnichannelRoom | undefined)?.tags, - closedAt: (room as IOmnichannelRoom | undefined)?.closedAt, - metrics: (room as IOmnichannelRoom | undefined)?.metrics, - muted: room?.muted, - waitingResponse: (room as IOmnichannelRoom | undefined)?.waitingResponse, - responseBy: (room as IOmnichannelRoom | undefined)?.responseBy, - priorityId: (room as IOmnichannelRoom | undefined)?.priorityId, - livechatData: (room as IOmnichannelRoom | undefined)?.livechatData, - departmentId: (room as IOmnichannelRoom | undefined)?.departmentId, - ts: room?.ts ?? subscription.ts, - source: (room as IOmnichannelRoom | undefined)?.source, - queuedAt: (room as IOmnichannelRoom | undefined)?.queuedAt, - federated: room?.federated, - lm: subscription.lr ? new Date(Math.max(subscription.lr.getTime(), lastRoomUpdate.getTime())) : lastRoomUpdate, - }; -}; - -const mergeRoomSub = (room: IRoom): IRoom => { - const sub = (Subscriptions as Mongo.Collection).findOne({ rid: room._id }); - if (!sub) { - return room; - } - - (Subscriptions as Mongo.Collection).update( - { - rid: room._id, - }, - { - $set: { - encrypted: room.encrypted, - description: room.description, - cl: room.cl, - topic: room.topic, - announcement: room.announcement, - broadcast: room.broadcast, - archived: room.archived, - avatarETag: room.avatarETag, - retention: (room as IRoomWithRetentionPolicy | undefined)?.retention, - uids: room.uids, - usernames: room.usernames, - usersCount: room.usersCount, - lastMessage: room.lastMessage, - streamingOptions: room.streamingOptions, - teamId: room.teamId, - teamMain: room.teamMain, - v: (room as IOmnichannelRoom | undefined)?.v, - transcriptRequest: (room as IOmnichannelRoom | undefined)?.transcriptRequest, - servedBy: (room as IOmnichannelRoom | undefined)?.servedBy, - onHold: (room as IOmnichannelRoom | undefined)?.onHold, - tags: (room as IOmnichannelRoom | undefined)?.tags, - closedAt: (room as IOmnichannelRoom | undefined)?.closedAt, - metrics: (room as IOmnichannelRoom | undefined)?.metrics, - muted: room.muted, - waitingResponse: (room as IOmnichannelRoom | undefined)?.waitingResponse, - responseBy: (room as IOmnichannelRoom | undefined)?.responseBy, - priorityId: (room as IOmnichannelRoom | undefined)?.priorityId, - livechatData: (room as IOmnichannelRoom | undefined)?.livechatData, - departmentId: (room as IOmnichannelRoom | undefined)?.departmentId, - ts: room.ts, - source: (room as IOmnichannelRoom | undefined)?.source, - queuedAt: (room as IOmnichannelRoom | undefined)?.queuedAt, - federated: room.federated, - ...getLowerCaseNames(room, sub.name, sub.fname), - }, - }, - ); - - (Subscriptions as Mongo.Collection).update( - { - rid: room._id, - lm: { $lt: room.lm }, - }, - { - $set: { - lm: room.lm, - }, - }, - ); - - return room; -}; - -callbacks.add('cachedCollection-received-rooms', mergeRoomSub); -callbacks.add('cachedCollection-sync-rooms', mergeRoomSub); -callbacks.add('cachedCollection-loadFromServer-rooms', mergeRoomSub); - -callbacks.add('cachedCollection-received-subscriptions', mergeSubRoom); -callbacks.add('cachedCollection-sync-subscriptions', mergeSubRoom); -callbacks.add('cachedCollection-loadFromServer-subscriptions', mergeSubRoom); diff --git a/apps/meteor/client/startup/notifications/konchatNotifications.ts b/apps/meteor/client/startup/notifications/konchatNotifications.ts index dfcdb74d7f30..f479d4e70044 100644 --- a/apps/meteor/client/startup/notifications/konchatNotifications.ts +++ b/apps/meteor/client/startup/notifications/konchatNotifications.ts @@ -94,11 +94,9 @@ Meteor.startup(() => { notifyNewMessageAudio(notification.payload.rid); }); - CachedChatSubscription.onSyncData = ((action: 'changed' | 'removed', sub: ISubscription): void => { - if (action !== 'removed') { - notifyNewRoom(sub); - } - }) as () => void; + CachedChatSubscription.on('changed', (sub): void => { + notifyNewRoom(sub); + }); Notifications.onUser('subscriptions-changed', (_action: 'changed' | 'removed', sub: ISubscription) => { notifyNewRoom(sub); diff --git a/apps/meteor/client/views/admin/cloud/CloudRoute.js b/apps/meteor/client/views/admin/cloud/CloudRoute.js index 4fb12961d043..07a8079b3d2f 100644 --- a/apps/meteor/client/views/admin/cloud/CloudRoute.js +++ b/apps/meteor/client/views/admin/cloud/CloudRoute.js @@ -3,6 +3,7 @@ import React from 'react'; import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; import CloudPage from './CloudPage'; +import RegisterWorkspace from './RegisterWorkspace'; function CloudRoute() { const canManageCloud = usePermission('manage-cloud'); @@ -11,7 +12,7 @@ function CloudRoute() { return ; } - return ; + return ; } export default CloudRoute; diff --git a/apps/meteor/client/views/admin/cloud/RegisterWorkspace.tsx b/apps/meteor/client/views/admin/cloud/RegisterWorkspace.tsx new file mode 100644 index 000000000000..8f225e2b0437 --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/RegisterWorkspace.tsx @@ -0,0 +1,102 @@ +import React, { useEffect } from 'react'; +import type { ReactNode } from 'react'; +import { Box, Tag } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { + useSetModal, + useToastMessageDispatch, + useQueryStringParameter, + useMethod, + useTranslation, +} from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; + +import Page from '../../../components/Page'; +import RegisterWorkspaceMenu from './components/RegisterWorkspaceMenu'; +import RegisterWorkspaceCards from './components/RegisterWorkspaceCards'; +import RegisterWorkspaceModal from './modals/WorkspaceRegistrationModal'; + +const RegisterWorkspace = (): ReactNode => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const token = useQueryStringParameter('token'); + + const checkCloudRegisterStatus = useMethod('cloud:checkRegisterStatus'); + const result = useQuery(['admin/cloud/register-status'], async () => checkCloudRegisterStatus()); + const reload = useMutableCallback(() => result.refetch()); + + const connectWorkspace = useMethod('cloud:connectWorkspace'); + + const setModal = useSetModal(); + + useEffect(() => { + const acceptWorkspaceToken = async (): Promise => { + try { + if (token) { + const isConnected = await connectWorkspace(token); + + if (!isConnected) { + throw Error(t('An error occured connecting' as Parameters[0])); + } + + dispatchToastMessage({ type: 'success', message: t('Connected') }); + } + } catch (error: unknown) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + reload(); + } + }; + + acceptWorkspaceToken(); + }, [reload, connectWorkspace, dispatchToastMessage, t, token]); + + + if (result.isLoading || result.isError) { + return null; + } + + const { + connectToCloud: isConnectedToCloud, + workspaceRegistered: isWorkspaceRegistered, + } = result.data; + + const handleRegisterWorkspaceClick = (): void => { + const handleModalClose = (): void => { + setModal(null); + reload(); + }; + setModal(); + }; + + console.log('result.data', result.data) + + return ( + + + + + + + {isWorkspaceRegistered ? ( + {t('Workspace_registered')} + ) : ( + {t('RegisterWorkspace_NotRegistered_Title')} + )} + + + { + isWorkspaceRegistered ? t('RegisterWorkspace_Registered_Description') : t('RegisterWorkspace_NotRegistered_Description') + } + + + + + ); +}; + +export default RegisterWorkspace; diff --git a/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceCards.tsx b/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceCards.tsx new file mode 100644 index 000000000000..ff69d034cfe6 --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceCards.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Grid } from '@rocket.chat/fuselage'; +import { Card } from '@rocket.chat/ui-client'; +import useFeatureBullets from '../hooks/useFeatureBullets'; + +const RegisterWorkspaceCards = () => { + const bulletFeatures = useFeatureBullets(); + + return ( + + { + bulletFeatures.map(card => ( + + + {card.title} + + {card.description} + + + + )) + } + + ) +} + +export default RegisterWorkspaceCards; diff --git a/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx b/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx new file mode 100644 index 000000000000..41916e3dc2a7 --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Button, ButtonGroup, Icon } from '@rocket.chat/fuselage'; +import { cloudConsoleUrl } from '../constants'; +import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; +import RegisteredWorkspaceModal from '../modals/RegisteredWorkspaceModal'; + +type RegisterWorkspaceMenuProps = { + isWorkspaceRegistered: boolean | string, + onClick: () => void, +} + +const RegisterWorkspaceMenu = ({ isWorkspaceRegistered, onClick }: RegisterWorkspaceMenuProps) => { + const t = useTranslation(); + const setModal = useSetModal(); + + const handleManageButton = () => { + const handleModalClose = (): void => setModal(null); + setModal(); + } + + return ( + + {!isWorkspaceRegistered ? ( + + ) : ( + <> + + + + )} + + ) +} + +export default RegisterWorkspaceMenu \ No newline at end of file diff --git a/apps/meteor/client/views/admin/cloud/hooks/useFeatureBullets.tsx b/apps/meteor/client/views/admin/cloud/hooks/useFeatureBullets.tsx new file mode 100644 index 000000000000..a3ff399869d4 --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/hooks/useFeatureBullets.tsx @@ -0,0 +1,45 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +type featureBulletsType = { + key: number; + title: string; + description: string; + deregister: string; +}; + +const useFeatureBullets = () => { + const t = useTranslation(); + + const featureBullets: featureBulletsType[] = useMemo( + () => [ + { + key: 1, + title: t('RegisterWorkspace_Features_MobileNotifications_Title'), + description: t('RegisterWorkspace_Features_MobileNotifications_Description'), + deregister: t('RegisterWorkspace_Features_MobileNotifications_Deregister') + }, + { + key: 2, + title: t('RegisterWorkspace_Features_Marketplace_Title'), + description: t('RegisterWorkspace_Features_Marketplace_Description'), + deregister: t('RegisterWorkspace_Features_Marketplace_Deregister'), + }, + { + key: 3, + title: t('RegisterWorkspace_Features_Omnichannel_Title'), + description: t('RegisterWorkspace_Features_Omnichannel_Description'), + deregister: t('RegisterWorkspace_Features_Omnichannel_Deregister'), + }, + { + key: 4, + title: t('RegisterWorkspace_Features_ThirdPartyLogin_Title'), + description: t('RegisterWorkspace_Features_ThirdPartyLogin_Description'), + deregister: t('RegisterWorkspace_Features_ThirdPartyLogin_Deregister'), + }, + ], [t]); + + return featureBullets; +} + +export default useFeatureBullets \ No newline at end of file diff --git a/apps/meteor/client/views/admin/cloud/modals/DeregisterWorkspaceModal.tsx b/apps/meteor/client/views/admin/cloud/modals/DeregisterWorkspaceModal.tsx new file mode 100644 index 000000000000..6bc6ae406d50 --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/modals/DeregisterWorkspaceModal.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Box, Button, ButtonGroup, Modal } from '@rocket.chat/fuselage'; +import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; +import useFeatureBullets from '../hooks/useFeatureBullets'; +import RegisteredWorkspaceModal from './RegisteredWorkspaceModal'; + +type DeregisterWorkspaceModalProps = { + onClose: () => void, +} + +const DeregisterWorkspaceModal = ({ onClose, ...props }: DeregisterWorkspaceModalProps) => { + const t = useTranslation(); + const setModal = useSetModal(); + const bulletFeatures = useFeatureBullets(); + + const handleCancelAction = (): void => { + const handleModalClose = (): void => setModal(null); + setModal(); + }; + + const handleDeregister = (): void => { + // here should be the deregister action + }; + + return ( + + + + + {t('Are_you_sure')} + + + + + + + {`${t('RegisterWorkspace_Deregister_Subtitle')}: `} +
    + { + bulletFeatures.map((item, index) => ( +
  • + {item.title} + {item.deregister} +
  • + )) + } +
+
+
+ + + + + + +
+ ) +} + +export default DeregisterWorkspaceModal; diff --git a/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupModal.tsx b/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupModal.tsx new file mode 100644 index 000000000000..efe8f87ed09a --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupModal.tsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from 'react'; +import { validateEmail } from '../../../../../lib/emailValidator'; +import RegisterWorkspaceSetupStepOneModal from './RegisterWorkspaceSetupStepOneModal'; +import RegisterWorkspaceSetupStepTwoModal from './RegisterWorkspaceSetupStepTwoModal'; +import { useSetModal } from '@rocket.chat/ui-contexts'; +import RegisteredWorkspaceModal from './RegisteredWorkspaceModal'; + +type RegisterWorkspaceSetupModalProps = { + onClose: () => void, + onStatusChange?: () => void, + isConnectedToCloud: boolean | string, +} + +const RegisterWorkspaceSetupModal = ({ onClose, onStatusChange, isConnectedToCloud }: RegisterWorkspaceSetupModalProps) => { + const setModal = useSetModal(); + + const [step, setStep] = useState(1); + const [email, setEmail] = useState(''); + const [terms, setTerms] = useState(false); + const [validInfo, setValidInfo] = useState(false); + const [intentData, setIntentData] = useState({ + device_code: '', + interval: 0, + user_code: '', + }); + + // reset validInfo when users go back to step 1 + useEffect(() => { + setValidInfo(false); + }, [step]); + + useEffect(() => { + if (step === 1) { + setValidInfo(validateEmail(email) && terms); + } + }, [email, terms]); + + const onSuccess = () => { + const handleModalClose = (): void => setModal(null); + setModal(); + }; + + return ( + <> + { step === 1 ? ( + + ) : ( + + ) + } + + ) +} + +export default RegisterWorkspaceSetupModal; diff --git a/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupStepOneModal.tsx b/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupStepOneModal.tsx new file mode 100644 index 000000000000..41e38912aa05 --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupStepOneModal.tsx @@ -0,0 +1,103 @@ +import React from 'react' +import { Modal, Box, Field, TextInput, CheckBox, ButtonGroup, Button } from '@rocket.chat/fuselage'; +import { useEndpoint, useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import WorkspaceRegistrationModal from './WorkspaceRegistrationModal'; + +type Props = { + email: string, + setEmail: (email: string) => void, + step: number, + setStep: (step: number) => void, + terms: boolean, + setTerms: (terms: boolean) => void, + onClose: () => void, + validInfo: boolean, + onStatusChange?: () => void, + setIntentData: (intentData: any) => void, + isConnectedToCloud: boolean | string, +} + +const RegisterWorkspaceSetupStepOneModal = ({ + email, + setEmail, + step, + setStep, + terms, + setTerms, + onClose, + validInfo, + onStatusChange, + setIntentData, + isConnectedToCloud, + ...props +}: Props) => { + const setModal = useSetModal(); + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const createRegistrationIntent = useEndpoint('POST', '/v1/cloud.createRegistrationIntent'); + + const handleBack = (): void => { + const handleModalClose = (): void => setModal(null); + setModal(); + }; + + const handleRegisterWorkspace = async () => { + try { + const { intentData } = await createRegistrationIntent({ resend: false, email }); + setIntentData(intentData); + setStep(step + 1); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }; + + return ( + + + + {t('RegisterWorkspace_Setup_Steps', { step, numberOfSteps: 2 })} + {t('RegisterWorkspace_with_email')} + + + + + + {t('RegisterWorkspace_Setup_Subtitle')} + + {t('RegisterWorkspace_Setup_Label')} + + { + setEmail((e.target as HTMLInputElement).value); + } } /> + + + + {t('RegisterWorkspace_Setup_Have_Account_Title')} + {t('RegisterWorkspace_Setup_Have_Account_Subtitle')} + {t('RegisterWorkspace_Setup_No_Account_Title')} + {t('RegisterWorkspace_Setup_No_Account_Subtitle')} + + + setTerms(!terms)} /> + {t('RegisterWorkspace_Setup_Terms_Privacy')} + + + + + + + + + + + ) +} + +export default RegisterWorkspaceSetupStepOneModal \ No newline at end of file diff --git a/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupStepTwoModal.tsx b/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupStepTwoModal.tsx new file mode 100644 index 000000000000..194289600157 --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceSetupStepTwoModal.tsx @@ -0,0 +1,86 @@ +import React, { useCallback, useEffect } from 'react' +import { Modal, Box, Field, TextInput } from '@rocket.chat/fuselage'; +import { useEndpoint, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; + +type Props = { + email: string, + step: number, + setStep: (step: number) => void, + onClose: () => void, + intentData: { + device_code: string, + interval: number, + user_code: string, + }, + onSuccess: () => void, +} + +const RegisterWorkspaceSetupStepTwoModal = ({ email, step, setStep, onClose, intentData, onSuccess, ...props }: Props) => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const cloudConfirmationPoll = useEndpoint('GET', '/v1/cloud.confirmationPoll'); + const createRegistrationIntent = useEndpoint('POST', '/v1/cloud.createRegistrationIntent'); + + const handleBackFromConfirmation = (): void => setStep(step - 1); + + const handleResendRegistrationEmail = async () => { + try { + await createRegistrationIntent({ resend: true, email }); + } catch (error: unknown) { + dispatchToastMessage({ type: 'error', message: error }); + } + }; + + const getConfirmation = useCallback(async () => { + try { + const { pollData } = await cloudConfirmationPoll({ + deviceCode: intentData.device_code, + }); + + if ('successful' in pollData && pollData.successful) { + dispatchToastMessage({ type: 'success', message: t('Workspace_registered') }); + onSuccess(); + } + } catch (error: any) { + console.log(error) + } + }, [cloudConfirmationPoll, intentData.device_code, dispatchToastMessage, t]); + + useEffect(() => { + const pollInterval = setInterval(() => getConfirmation(), intentData.interval); + + return (): void => clearInterval(pollInterval); + }, [getConfirmation, intentData]); + + return ( + + + + {t('RegisterWorkspace_Setup_Steps', { step, numberOfSteps: 2 })} + {t('Awaiting_confirmation')} + + + + + + {t('RegisterWorkspace_Setup_Email_Confirmation', { email })} + {t('RegisterWorkspace_Setup_Email_Verification')} + + {t('Security_code')} + + + + + + + + + Didn’t receive email? Resend or change email + + + + ) +} + +export default RegisterWorkspaceSetupStepTwoModal \ No newline at end of file diff --git a/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceTokenModal.tsx b/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceTokenModal.tsx new file mode 100644 index 000000000000..f7c9a8639b7d --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/modals/RegisterWorkspaceTokenModal.tsx @@ -0,0 +1,88 @@ +import React, { useState, ChangeEvent } from 'react'; +import { Box, Button, ButtonGroup, Field, Modal, TextInput } from '@rocket.chat/fuselage'; +import { useMethod, useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import WorkspaceRegistrationModal from './WorkspaceRegistrationModal'; + +type RegisterWorkspaceTokenModalProps = { + onClose: () => void, + onStatusChange?: () => void, + isConnectedToCloud: boolean +} + +const RegisterWorkspaceTokenModal = ({ onClose, onStatusChange, isConnectedToCloud, ...props }: RegisterWorkspaceTokenModalProps) => { + const setModal = useSetModal(); + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const connectWorkspace = useMethod('cloud:connectWorkspace'); + const syncWorkspace = useMethod('cloud:syncWorkspace'); + + const [token, setToken] = useState(''); + const [processing, setProcessing] = useState(false); + + const handleBackAction = (): void => { + const handleModalClose = (): void => setModal(null); + setModal(); + }; + + const handleTokenChange = (event: ChangeEvent) => { + setToken(event.target.value); + }; + + const handleConnectButtonClick = async () => { + setProcessing(true); + + try { + const isConnected = await connectWorkspace(token); + + if (!isConnected) { + throw Error(t('An error occured connecting')); + } + + dispatchToastMessage({ type: 'success', message: t('Connected') }); + + const isSynced = await syncWorkspace(); + + if (!isSynced) { + throw Error(t('An error occured syncing')); + } + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + await (onStatusChange && onStatusChange()); + setProcessing(false); + } + }; + + return ( + + + + {t('RegisterWorkspace_Token_Title')} + + + + + {`1. ${t('RegisterWorkspace_Token_Step_One')}`} + {`2. ${t('RegisterWorkspace_Token_Step_Two')}`} + + {t('Registration_Token')} + + + + + + + + + + + + + ) +} + +export default RegisterWorkspaceTokenModal; diff --git a/apps/meteor/client/views/admin/cloud/modals/RegisteredWorkspaceModal.tsx b/apps/meteor/client/views/admin/cloud/modals/RegisteredWorkspaceModal.tsx new file mode 100644 index 000000000000..c29964b8ff54 --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/modals/RegisteredWorkspaceModal.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Box, Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage'; +import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; +import useFeatureBullets from '../hooks/useFeatureBullets'; +import DeregisterWorkspaceModal from './DeregisterWorkspaceModal'; + +type RegisteredWorkspaceModalProps = { + onClose: () => void, +} + +const RegisteredWorkspaceModal = ({ onClose, ...props }: RegisteredWorkspaceModalProps) => { + const t = useTranslation(); + const setModal = useSetModal(); + const bulletFeatures = useFeatureBullets(); + + const handleDeregister = (): void => { + const handleModalClose = (): void => setModal(null); + setModal(); + }; + + const handleSyncAction = (): void => { + // here should be the sync action + }; + + return ( + + + + {t('RegisterWorkspace_Registered_Title')} + + + + + + {`${t('RegisterWorkspace_Registered_Subtitle')}: `} +
    + { + bulletFeatures.map((item, index) => ( +
  • + {item.title} + {item.description} +
  • + )) + } +
+
+
+ + + + + + +
+ ) +} + +export default RegisteredWorkspaceModal; diff --git a/apps/meteor/client/views/admin/cloud/modals/WorkspaceRegistrationModal.tsx b/apps/meteor/client/views/admin/cloud/modals/WorkspaceRegistrationModal.tsx new file mode 100644 index 000000000000..b2a043da686d --- /dev/null +++ b/apps/meteor/client/views/admin/cloud/modals/WorkspaceRegistrationModal.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Box, Button, ButtonGroup, Modal } from '@rocket.chat/fuselage'; +import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; +import RegisterWorkspaceTokenModal from './RegisterWorkspaceTokenModal'; +import RegisterWorkspaceSetupModal from './RegisterWorkspaceSetupModal'; +import useFeatureBullets from '../hooks/useFeatureBullets'; + +type WorkspaceRegistrationModalProps = { + onClose: () => void, + onStatusChange?: () => void, + isConnectedToCloud: boolean | string, +} + +const WorkspaceRegistrationModal = ({ onClose, onStatusChange, isConnectedToCloud, ...props }: WorkspaceRegistrationModalProps) => { + const setModal = useSetModal(); + const bulletFeatures = useFeatureBullets(); + const t = useTranslation(); + + const handleTokenModal = (): void => { + const handleModalClose = (): void => setModal(null); + setModal(); + }; + + const handleSetupModal = (): void => { + const handleModalClose = (): void => setModal(null); + setModal(); + }; + + return ( + + + + {t('RegisterWorkspace_NotRegistered_Title')} + + + + + + {`${t('RegisterWorkspace_NotRegistered_Subtitle')}:`} +
    + { + bulletFeatures.map(features => ( +
  • + {features.title} + {features.description} +
  • + )) + } +
+ {t('RegisterWorkspace_Registered_Benefits')} +
+
+ + + + {t('Learn_more')} + + + + + + + +
+ ) +} + +export default WorkspaceRegistrationModal; diff --git a/apps/meteor/client/views/admin/integrations/new/NewZapier.js b/apps/meteor/client/views/admin/integrations/new/NewZapier.js index dad79f0a5e30..142e7a818c88 100644 --- a/apps/meteor/client/views/admin/integrations/new/NewZapier.js +++ b/apps/meteor/client/views/admin/integrations/new/NewZapier.js @@ -1,4 +1,4 @@ -import { Box, Skeleton, Margins } from '@rocket.chat/fuselage'; +import { Box, Skeleton, Margins, Callout } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useEffect, useState } from 'react'; @@ -16,8 +16,8 @@ const blogSpotStyleScriptImport = (src) => }); export default function NewZapier({ ...props }) { - const t = useTranslation(); const [script, setScript] = useState(); + const t = useTranslation(); useEffect(() => { const importZapier = async () => { const scriptEl = await blogSpotStyleScriptImport( @@ -33,7 +33,9 @@ export default function NewZapier({ ...props }) { return ( <> - + + {t('Install_Zapier_from_marketplace')} + {!script && ( diff --git a/apps/meteor/client/views/admin/permissions/hooks/usePermissionsAndRoles.ts b/apps/meteor/client/views/admin/permissions/hooks/usePermissionsAndRoles.ts index 02454510f2b0..9a7d30500a2f 100644 --- a/apps/meteor/client/views/admin/permissions/hooks/usePermissionsAndRoles.ts +++ b/apps/meteor/client/views/admin/permissions/hooks/usePermissionsAndRoles.ts @@ -1,6 +1,7 @@ import type { IRole, IPermission } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { escapeRegExp } from '@rocket.chat/string-helpers'; +import type { Mongo } from 'meteor/mongo'; import { useCallback } from 'react'; import { ChatPermissions } from '../../../../../app/authorization/client/lib/ChatPermissions'; @@ -14,7 +15,7 @@ export const usePermissionsAndRoles = ( limit = 25, skip = 0, ): { permissions: IPermission[]; total: number; roleList: IRole[]; reload: () => void } => { - const getFilter = useCallback(() => { + const getFilter = useCallback((): Mongo.Selector => { const filterRegExp = new RegExp(escapeRegExp(filter), 'i'); return { diff --git a/apps/meteor/client/views/admin/permissions/hooks/useRole.ts b/apps/meteor/client/views/admin/permissions/hooks/useRole.ts index 1b96c4ed43f3..bf4e385dcb33 100644 --- a/apps/meteor/client/views/admin/permissions/hooks/useRole.ts +++ b/apps/meteor/client/views/admin/permissions/hooks/useRole.ts @@ -4,4 +4,4 @@ import { useCallback } from 'react'; import { Roles } from '../../../../../app/models/client'; import { useReactiveValue } from '../../../../hooks/useReactiveValue'; -export const useRole = (_id?: IRole['_id']): IRole => useReactiveValue(useCallback(() => Roles.findOne({ _id }), [_id])); +export const useRole = (_id?: IRole['_id']): IRole | undefined => useReactiveValue(useCallback(() => Roles.findOne({ _id }), [_id])); diff --git a/apps/meteor/client/views/admin/sidebarItems.ts b/apps/meteor/client/views/admin/sidebarItems.ts index d3f195069647..3ae4852acf9d 100644 --- a/apps/meteor/client/views/admin/sidebarItems.ts +++ b/apps/meteor/client/views/admin/sidebarItems.ts @@ -40,7 +40,7 @@ export const { { icon: 'cloud-plus', href: 'cloud', - i18nLabel: 'Connectivity_Services', + i18nLabel: 'Registration', permissionGranted: (): boolean => hasPermission('manage-cloud'), }, { diff --git a/apps/meteor/client/views/room/components/body/useRoomRolesManagement.ts b/apps/meteor/client/views/room/components/body/useRoomRolesManagement.ts index 7a8ba6f65fe6..b8c3d1af61e8 100644 --- a/apps/meteor/client/views/room/components/body/useRoomRolesManagement.ts +++ b/apps/meteor/client/views/room/components/body/useRoomRolesManagement.ts @@ -1,11 +1,10 @@ -import type { IRole, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; +import type { IRole, IRoom, IUser } from '@rocket.chat/core-typings'; import { useMethod, useStream } from '@rocket.chat/ui-contexts'; -import type { Mongo } from 'meteor/mongo'; import { useEffect } from 'react'; import { RoomRoles, ChatMessage } from '../../../../../app/models/client'; -const roomRoles = RoomRoles as Mongo.Collection>; +// const roomRoles = RoomRoles as Mongo.Collection>; export const useRoomRolesManagement = (rid: IRoom['_id']): void => { const getRoomRoles = useMethod('getRoomRoles'); @@ -17,7 +16,7 @@ export const useRoomRolesManagement = (rid: IRoom['_id']): void => { rid, u: { _id: uid }, } = data; - roomRoles.upsert({ rid, 'u._id': uid }, data); + RoomRoles.upsert({ rid, 'u._id': uid }, { $set: data }); }); }); }, [getRoomRoles, rid]); @@ -74,7 +73,7 @@ export const useRoomRolesManagement = (rid: IRoom['_id']): void => { switch (type) { case 'added': - roomRoles.upsert({ 'rid': role.scope, 'u._id': role.u._id }, { $setOnInsert: { u: role.u }, $addToSet: { roles: role._id } }); + RoomRoles.upsert({ 'rid': role.scope, 'u._id': role.u._id }, { $setOnInsert: { u: role.u }, $addToSet: { roles: role._id } }); break; case 'removed': @@ -89,7 +88,7 @@ export const useRoomRolesManagement = (rid: IRoom['_id']): void => { useEffect( () => subscribeToNotifyLoggedIn('Users:NameChanged', ({ _id: uid, name }: Partial) => { - roomRoles.update( + RoomRoles.update( { 'u._id': uid, }, diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index cf4bd3a0dc8f..0f4de5abae93 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -82,8 +82,10 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }; }, [rid]); + const subscribed = !!subscriptionQuery.data; + useEffect(() => { - if (!subscriptionQuery.data) { + if (!subscribed) { return; } @@ -95,7 +97,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { // Do nothing } }; - }, [rid, subscriptionQuery.data]); + }, [rid, subscribed]); const api = useMemo(() => ({}), []); diff --git a/apps/meteor/client/views/root/MainLayout/Preload.tsx b/apps/meteor/client/views/root/MainLayout/Preload.tsx index 42725bd428da..cd5ba0e817db 100644 --- a/apps/meteor/client/views/root/MainLayout/Preload.tsx +++ b/apps/meteor/client/views/root/MainLayout/Preload.tsx @@ -4,7 +4,7 @@ import React, { useEffect } from 'react'; import { CachedChatSubscription } from '../../../../app/models/client'; import { settings } from '../../../../app/settings/client'; -import { CachedCollectionManager } from '../../../../app/ui-cached-collection'; +import { CachedCollectionManager } from '../../../../app/ui-cached-collection/client'; import { mainReady } from '../../../../app/ui-utils/client'; import { useReactiveVar } from '../../../hooks/useReactiveVar'; import { isSyncReady } from '../../../lib/userData'; diff --git a/apps/meteor/definition/externals/meteor/accounts-base.d.ts b/apps/meteor/definition/externals/meteor/accounts-base.d.ts index 2185629b516e..92c4153e111a 100644 --- a/apps/meteor/definition/externals/meteor/accounts-base.d.ts +++ b/apps/meteor/definition/externals/meteor/accounts-base.d.ts @@ -23,6 +23,10 @@ declare module 'meteor/accounts-base' { function _runLoginHandlers(methodInvocation: T, loginRequest: Record): Record | undefined; + function _storedLoginToken(): unknown; + + function _unstoreLoginToken(): void; + function updateOrCreateUserFromExternalService( serviceName: string, serviceData: Record, diff --git a/apps/meteor/definition/externals/meteor/meteor.d.ts b/apps/meteor/definition/externals/meteor/meteor.d.ts index 42833a9b127c..a8db2a2df201 100644 --- a/apps/meteor/definition/externals/meteor/meteor.d.ts +++ b/apps/meteor/definition/externals/meteor/meteor.d.ts @@ -64,6 +64,8 @@ declare module 'meteor/meteor' { allowConnection: () => void; }; + _outstandingMethodBlocks: unknown[]; + onMessage(message: string): void; status(): { diff --git a/apps/meteor/ee/server/lib/EnterpriseCheck.ts b/apps/meteor/ee/server/lib/EnterpriseCheck.ts index 2cfb42d4d93b..8bccfed59071 100644 --- a/apps/meteor/ee/server/lib/EnterpriseCheck.ts +++ b/apps/meteor/ee/server/lib/EnterpriseCheck.ts @@ -18,7 +18,7 @@ export const EnterpriseCheck: ServiceSchema = { const services: { name: string; nodes: string[]; - }[] = await this.broker.call('$node.services'); + }[] = await this.broker.call('$node.services', { skipInternal: true }); const currentService = services.find((service) => { return service.name === this.name; @@ -33,16 +33,21 @@ export const EnterpriseCheck: ServiceSchema = { const firstNode = nodes.sort().shift(); - // if the first node is the current node and there are others nodes running the same service, then it should shutdown - return firstNode === this.broker.nodeID && nodes.length > 0; + // if the first node is the current node and there are others nodes running the same service or + // if this is the only one node online, then we should shutdown + return firstNode === this.broker.nodeID && (nodes.length > 0 || services.length === 1); }, }, async started(): Promise { setInterval(async () => { - const hasLicense = await this.broker.call('license.hasLicense', ['scalability']); - if (hasLicense) { - checkFails = 0; - return; + try { + const hasLicense = await this.broker.call('license.hasLicense', ['scalability']); + if (hasLicense) { + checkFails = 0; + return; + } + } catch (e: unknown) { + // check failed, so continue } if (++checkFails < maxFails) { diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index 8a46207d1d75..d4e53a2d847a 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -171,18 +171,6 @@ type Hook = | 'beforeSaveMessage' | 'beforeSendMessageNotifications' | 'beforeValidateLogin' - | 'cachedCollection-loadFromServer-rooms' - | 'cachedCollection-loadFromServer-subscriptions' - | 'cachedCollection-received-rooms' - | 'cachedCollection-received-subscriptions' - | 'cachedCollection-sync-rooms' - | 'cachedCollection-sync-subscriptions' - | 'cachedCollection-after-loadFromServer-rooms' - | 'cachedCollection-after-loadFromServer-subscriptions' - | 'cachedCollection-after-received-rooms' - | 'cachedCollection-after-received-subscriptions' - | 'cachedCollection-after-sync-rooms' - | 'cachedCollection-after-sync-subscriptions' | 'enter-room' | 'livechat.beforeForwardRoomToDepartment' | 'livechat.beforeInquiry' diff --git a/apps/meteor/lib/utils/promisify.ts b/apps/meteor/lib/utils/promisify.ts new file mode 100644 index 000000000000..5bd07dbb7960 --- /dev/null +++ b/apps/meteor/lib/utils/promisify.ts @@ -0,0 +1,18 @@ +export const promisify: { + (fn: (...args: [...args: TArgs, callback: (error: any, result: TResult) => void]) => void): ( + ...args: TArgs + ) => Promise; + (fn: (...args: [...args: TArgs, callback: (error: any, result?: never) => void]) => void): ( + ...args: TArgs + ) => Promise; +} = + (fn: (...args: [...args: TArgs, callback: (error: any, result: TResult) => void]) => void) => + (...args: TArgs) => + new Promise((resolve, reject) => { + fn(...args, (error: any, result: TResult) => { + if (error) { + return reject(error); + } + return resolve(result); + }); + }); diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/af.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/af.i18n.json index c86fdab609a7..be091ffcded5 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/af.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/af.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Bykomende e-posse", "Additional_Feedback": "Bykomende terugvoer", "additional_integrations_Bots": "As jy op soek is na hoe om jou eie bot te integreer, kyk dan nie verder as ons Hubot-adapter nie. https://github.com/RocketChat/hubot-rocketchat ", - "additional_integrations_Zapier": "Is jy op soek na ander sagteware en toepassings met Rocket.Chat te integreer, maar jy het nie die tyd om dit handmatig te doen nie? Dan stel ons voor om Zapier te gebruik wat ons ten volle ondersteun. Lees meer hieroor op ons dokumentasie. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/ ", "Admin_Info": "Admin Info", "Administration": "administrasie", "Adult_images_are_not_allowed": "Volwasse beelde word nie toegelaat nie", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json index dbe56f201500..90004834626f 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json @@ -292,7 +292,6 @@ "Additional_emails": "رسائل بريد إلكتروني إضافية", "Additional_Feedback": "ملاحظات إضافية", "additional_integrations_Bots": "إذا كنت تبحث عن كيفية دمج الروبوت الخاص بك، فلن تجد أفضل من محول Hubot الخاص بنا. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "هل تتطلع إلى دمج البرامج والتطبيقات الأخرى مع Rocket.Chat ولكن ليس لديك الوقت للقيام بذلك يدويًا؟ ننصحك باستخدام Zapier الذي ندعمه بالكامل. يمكنك قراءة المزيد عنه على وثائقنا. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "لم يقوم المسؤول لديك بتمكين التشفير بين الوحدات الطرفية.", "Admin_Info": "معلومات المسؤول", "Administration": "الإدارة", @@ -702,7 +701,6 @@ "Busy": "مشغول", "By": "بواسطة", "by": "بواسطة", - "By_author": "بواسطة __author__", "cache_cleared": "تم محو التخزين المؤقت", "Call": "مكالمة", "Calling": "يتم الآن الاتصال", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/az.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/az.i18n.json index e3e3f4f2608b..b619f1218eeb 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/az.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/az.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Əlavə e-poçtlar", "Additional_Feedback": "Əlavə Əlaqə", "additional_integrations_Bots": "Öz botunuzu necə birləşdirə bilərsinizsə, onda Hubot adapterimizdən daha çox baxın. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Digər proqram və proqramları Rocket.Chat ilə inteqrasiya etmək istəyirsənsə, ancaq bunu elle etmək üçün vaxtınız yoxdur? Sonra biz tam dəstəkləyən Zapier-dən istifadə etməyi təklif edirik. Bu barədə bizim sənədlərimiz haqqında ətraflı məlumat əldə edin. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Admin məlumatı", "Administration": "İdarə", "Adult_images_are_not_allowed": "Yetkin görünüşlərə icazə verilmir", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/be-BY.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/be-BY.i18n.json index d7e6902e6e17..e70bed5f8d68 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/be-BY.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/be-BY.i18n.json @@ -221,7 +221,6 @@ "Additional_emails": "Дадатковыя паведамленні электроннай пошты", "Additional_Feedback": "Дадатковая сувязь", "additional_integrations_Bots": "Калі вы шукаеце, як інтэграваць свой уласны бот, то глядзець не далей, чым наш Hubot адаптар. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Вы хочаце інтэграваць іншае праграмнае забеспячэнне і прыкладанні з Rocket.Chat, але ў вас няма часу, каб ўручную зрабіць гэта? Тады мы прапануем выкарыстоўваць Zapier, які мы цалкам падтрымліваем. Падрабязней пра гэта ў нашай дакументацыі.https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/ ", "Admin_Info": "адмін інфармацыя", "Administration": "адміністрацыя", "Adult_images_are_not_allowed": "Выявы для дарослых не дапускаюцца", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/bg.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/bg.i18n.json index 6b4fc5870baf..26432a6ca3d5 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/bg.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/bg.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Допълнителни имейли", "Additional_Feedback": "Допълнителни обратни връзки", "additional_integrations_Bots": "Ако търсите как да интегрирате собствения си бот, не търсете нищо повече от нашия адаптер Хъбот. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Търсите ли да интегрирате друг софтуер и приложения с Rocket.Chat, но нямате време да го направите ръчно? След това предлагаме да използвате Запир, което напълно подкрепяме. Прочетете повече за това в нашата документация. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Администраторска информация", "Administration": "администрация", "Adult_images_are_not_allowed": "Възрастните изображения не са разрешени", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/bs.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/bs.i18n.json index 34234ca88004..1d4b7dea07e1 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/bs.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/bs.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Dodatni Emailovi", "Additional_Feedback": "Dodatne povratne informacije", "additional_integrations_Bots": "Ako tražite kako integrirati svoj bot, nemojte gledati dalje od našeg Hubot adaptera. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Tražite li integrirati drugi softver i aplikacije s Rocket.Chat, ali nemate vremena za ručno raditi? Onda predlažemo da koristite Zapier koji u potpunosti podržavamo. Pročitajte više o našoj dokumentaciji. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Informacije o administratoru", "Administration": "Administracija", "Adult_images_are_not_allowed": "Slike za odrasle nisu dopuštene", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ca.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ca.i18n.json index 46f4e4cff859..0fe82cd6397b 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ca.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ca.i18n.json @@ -292,7 +292,6 @@ "Additional_emails": "Correus electrònics addicionals", "Additional_Feedback": "Retroalimentació addicional", "additional_integrations_Bots": "Si esteu buscant com integrar el vostre propi bot, no busqueu més que el nostre adaptador Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Està buscant integrar un altre programari i aplicacions amb Rocket.Chat però no té temps per fer-ho manualment? Llavors, suggerim utilitzar Zapier, que és totalment compatible. Llegiu més sobre això a la nostra documentació https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "El seu administrador no va habilitar encriptació E2E", "Admin_Info": "Informació d'administrador", "Administration": "Administració", @@ -700,7 +699,6 @@ "Busy": "Ocupat", "By": "per", "by": "per", - "By_author": "Per __author__", "cache_cleared": "Memòria cau esborrada", "Call": "Trucada", "Calling": "Trucant", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/cs.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/cs.i18n.json index 5c8a1f0b5a58..6b9967b307ae 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/cs.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/cs.i18n.json @@ -267,7 +267,6 @@ "Additional_emails": "Další e-maily", "Additional_Feedback": "Dodatečný Feedback", "additional_integrations_Bots": "Hledáte způsob jak implementovat vlastního bota? Nehledejte dál a podívejte se na náš Hubot adaptér https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": " Chcete integrovat software či aplikaci s Rocket.Chat ale nevíte jak na to? Doporučujeme použít Zapier, který plně podporujeme. Pro více informací si přečtěte naši dokumentaci https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Váš správce nepovolil šifrování E2E.", "Admin_Info": "Admin informace", "Administration": "Administrace", @@ -591,7 +590,6 @@ "busy": "zaneprázdněný", "Busy": "Zaneprázdněný", "by": "od", - "By_author": "Od __author__", "cache_cleared": "Cache vyčistěna", "Call": "Hovor", "call-management": "Správa hovorů", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/cy.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/cy.i18n.json index b29de46d2ede..e23fe922a98e 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/cy.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/cy.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "E-bystiau ychwanegol", "Additional_Feedback": "Adborth Ychwanegol", "additional_integrations_Bots": "Os ydych chi'n chwilio am sut i integreiddio'ch bot eich hun, yna edrychwch ymhellach na'n adapter Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Ydych chi eisiau integreiddio meddalwedd a chymwysiadau eraill gyda Rocket.Chat ond nad oes gennych chi'r amser i wneud hynny? Yna awgrymwn ddefnyddio Zapier yr ydym yn ei gefnogi'n llawn. Darllenwch fwy amdano ar ein dogfennau. https://rocket.chat/docs/administrator-guides/integrations / zapier / using-zaps /", "Admin_Info": "Gwybodaeth Gweinyddol", "Administration": "Gweinyddiaeth", "Adult_images_are_not_allowed": "Ni chaniateir delweddau oedolion", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/da.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/da.i18n.json index a8eb0e5f3c84..080991ed5355 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/da.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/da.i18n.json @@ -266,7 +266,6 @@ "Additional_emails": "Yderligere e-mails", "Additional_Feedback": "Yderligere feedback", "additional_integrations_Bots": "Hvis du gerne vil integrere din egen bot, skal du bare bruge vores Hubot-adapter: https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Hvis du gerne vil integrere anden software og andre applikationer med Rocket.Chat, men ikke har tid , forslår vi, at du bruger Zapier, som vi fuldt ud understøtter. Du kan læse mere om det i vores dokumentation: https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Din administrator har ikke aktiveret E2E-kryptering", "Admin_Info": "Administratorinfo", "Administration": "Administration", @@ -591,7 +590,6 @@ "busy": "travl", "Busy": "Travl", "by": "ved", - "By_author": "Af __author__", "cache_cleared": "Cache ryddet", "Call": "Opkald", "call-management": "Opkaldsadministration", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/de-AT.i18n.json index 4d591d4afdd6..1ca74bb09712 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/de-AT.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/de-AT.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Zusätzliche E-Mails", "Additional_Feedback": "Zusätzliches Feedback", "additional_integrations_Bots": "Wenn Sie nach der Integration Ihres eigenen Bot suchen, dann suchen Sie nicht weiter als unseren Hubot-Adapter. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Möchten Sie andere Software und Anwendungen mit Rocket.Chat integrieren, aber Sie haben nicht die Zeit, es manuell zu tun? Dann empfehlen wir die Verwendung von Zapier, die wir voll unterstützen. Lesen Sie mehr darüber in unserer Dokumentation. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Admin-Info", "Administration": "Administration", "Adult_images_are_not_allowed": "Erwachsene Bilder sind nicht erlaubt", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/de-IN.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/de-IN.i18n.json index 3feb7c6e28b8..f292545e13fd 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/de-IN.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/de-IN.i18n.json @@ -223,7 +223,6 @@ "Additional_emails": "Zusätzliche E-Mails", "Additional_Feedback": "Zusätzliches Feedback", "additional_integrations_Bots": "Wenn Du auf der Suche nach der Integration Deines eigenen Bot bist, dann ist unser Hubot-Adapter genau das Richtige für Dich. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Du möchtest andere Software und Anwendungen in Rocket.Chat integrieren, hast aber nicht die Zeit, dies manuell zu tun? Dann empfehlen wir die Verwendung von Zapier, das wir voll unterstützen. Lies mehr darüber in unserer Dokumentation. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Dein Administrator hat Ende-zu-Ende-Verschlüsselung nicht aktiviert", "Admin_Info": "Admin-Info", "Administration": "Administration", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/de.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/de.i18n.json index 4f59a4f16ab9..eaf2c40c9921 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/de.i18n.json @@ -316,7 +316,6 @@ "Additional_emails": "Zusätzliche E-Mails", "Additional_Feedback": "Zusätzliches Feedback", "additional_integrations_Bots": "Wenn Sie nach der Integration Ihres eigenen Bot suchen, dann suchen Sie nicht weiter als unseren Hubot-Adapter. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Möchten Sie andere Software und Anwendungen mit Rocket.Chat integrieren, aber Sie haben nicht die Zeit, es manuell zu tun? Dann empfehlen wir die Verwendung von Zapier, die wir vollständig unterstützen. Lesen Sie mehr darüber in unserer Dokumentation. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Ihr Administrator hat Ende-zu-Ende-Verschlüsselung nicht aktiviert.", "Admin_Info": "Admin-Info", "admin-no-active-video-conf-provider": "**Conference call not enabled**: Konfigurieren Sie Telefonkonferenzen, um sie auf diesem Arbeitsbereich verfügbar zu machen", @@ -590,6 +589,8 @@ "archive-room": "Room archivieren", "archive-room_description": "Berechtigung, einen Channel zu archivieren", "are_typing": "schreiben", + "are_playing": "spielen", + "is_playing": "spielt", "are_uploading": "laden hoch", "are_recording": "nehmen auf", "is_uploading": "lädt hoch", @@ -772,7 +773,6 @@ "Buy": "Kaufen", "By": "Von", "by": "von", - "By_author": "Von __author__", "cache_cleared": "Zwischenspeicher gelöscht", "Call": "Anruf", "Call_back": "Rückruf", @@ -794,6 +794,7 @@ "call-management": "Anrufverwaltung", "call-management_description": "Erlaubnis zum Starten einer Besprechung", "Call_ongoing": "Anruf laufend", + "Call_started": "Aufruf gestartet", "Call_unavailable_for_federation": "Anruf ist für Verbundräume nicht verfügbar", "Call_was_not_answered": "Anruf wurde nicht beantwortet", "Caller": "Anrufer", @@ -2057,7 +2058,7 @@ "Favorites": "Favoriten", "featured": "unterstützt", "Featured": "Unterstützt", - "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Diese Funktion hängt davon ab, ob der oben ausgewählte Anrufservice-Provider in den Verwaltungseinstellungen aktiviert ist.
Stellen Sie für **Jitsi** bitte sicher, dass Jitsi unter Admin -> Video Conference -> Jitsi -> Enabled aktiviert ist.
Für **WebRTC** muss WebRTC unter Admin -> WebRTC -> Enabled aktiviert sein.", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Diese Funktion hängt davon ab, dass der oben ausgewählte Anrufservice-Provider in den Verwaltungseinstellungen aktiviert ist.(Admin -> Einstellungen -> Videokonferenz).", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Diese Funktion hängt davon ab, ob \"Besucher-Navigationsprotokoll als Nachricht senden\" aktiviert ist.", "Feature_Limiting": "Funktionsbegrenzung", "Features": "Funktionen", @@ -3295,6 +3296,7 @@ "Message_ShowDeletedStatus": "Löschstatus anzeigen", "Message_ShowEditedStatus": "Bearbeitungsstatus anzeigen", "Message_ShowFormattingTips": "Formatierungshilfe anzeigen", + "Message_Formatting_Toolbox": "Formatierungs-Werkzeuge", "Message_starring": "Markieren von favorisierten Nachrichten", "Message_Time": "Zeitpunkt der Nachricht", "Message_TimeAndDateFormat": "Zeit- und Datumsformat", @@ -3860,6 +3862,7 @@ "Read_by": "Gelesen von", "Read_only": "Schreibgeschützt", "This_room_is_read_only": "Der Raum ist schreibgeschützt", + "Only_people_with_permission_can_send_messages_here": "Nur berechtigte Personen können hier Nachrichten senden", "Read_only_changed_successfully": "Erfolgreich schreibgeschützt", "Read_only_channel": "Channel schreibgeschützt", "Read_only_group": "Schreibgeschützte Gruppe", @@ -4077,6 +4080,7 @@ "room_disallowed_reactions": "unzulässige Reaktionen", "Room_Edit": "Raum bearbeiten", "Room_has_been_archived": "Der Room wurde archiviert", + "Room_has_been_created": "Room wurde erstellt", "Room_has_been_deleted": "Der Room wurde gelöscht", "Room_has_been_removed": "Raum wurde entfernt", "Room_has_been_unarchived": "Der Room wurde aus dem Archiv geholt", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/el.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/el.i18n.json index 2bec4b6ca033..933f006725d4 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/el.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/el.i18n.json @@ -221,7 +221,6 @@ "Additional_emails": "Πρόσθετες Διευθύνσεις E-mail", "Additional_Feedback": "Πρόσθετα Σχόλια", "additional_integrations_Bots": "Αν ψάχνετε για το πώς να ενσωματώσετε το δικό σας bot, τότε μην κοιτάξετε περισσότερο από τον προσαρμογέα Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Ψάχνετε να ενσωματώσετε άλλα λογισμικά και εφαρμογές με το Rocket.Chat αλλά δεν έχετε το χρόνο να το κάνετε χειροκίνητα; Στη συνέχεια, προτείνουμε τη χρήση του Zapier, την οποία υποστηρίζουμε πλήρως. Διαβάστε περισσότερα σχετικά με την τεκμηρίωσή μας. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Πληροφορίες διαχειριστή", "Administration": "Διαχείριση", "Adult_images_are_not_allowed": "Ακατάλληλες φωτογραφίες δεν επιτρέπονται", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 8869a623bcb5..441c9cd7df72 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -317,7 +317,6 @@ "Additional_emails": "Additional Emails", "Additional_Feedback": "Additional Feedback", "additional_integrations_Bots": "If you are looking for how to integrate your own bot, then look no further than our Hubot adapter. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Are you looking to integrate other software and applications with Rocket.Chat but you don't have the time to manually do it? Then we suggest using Zapier which we fully support. Read more about it on our documentation. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Your administrator did not enable E2E encryption.", "Admin_Info": "Admin Info", "admin-no-active-video-conf-provider": "**Conference call not enabled**: Configure conference calls in order to make it available on this workspace.", @@ -3062,6 +3061,7 @@ "Make_Admin": "Make Admin", "Make_sure_you_have_a_copy_of_your_codes_1": "Make sure you have a copy of your codes:", "Make_sure_you_have_a_copy_of_your_codes_2": "If you lose access to your authenticator app, you can use one of these codes to log in.", + "Manage": "Manage", "manage-agent-extension-association": "Manage Agent Extension Association", "manage-agent-extension-association_description": "Permission to manage agent extension association", "manage-apps": "Manage Apps", @@ -4760,6 +4760,8 @@ "This_feature_is_currently_in_alpha": "This feature is currently in alpha!", "This_is_a_desktop_notification": "This is a desktop notification", "This_is_a_deprecated_feature_alert": "This is a deprecated feature. It may not work as expected and will not get new updates.", + "Zapier_integration_has_been_deprecated": "The Zapier integration has been deprecated, may not work as expected and will not receive updates", + "Install_Zapier_from_marketplace": "Install the Zapier app from Marketplace to avoid disruptions", "This_is_a_push_test_messsage": "This is a push test message", "This_message_was_rejected_by__peer__peer": "This message was rejected by __peer__ peer.", "This_monitor_was_already_selected": "This monitor was already selected", @@ -4926,6 +4928,7 @@ "Unread": "Unread", "Unread_Count": "Unread Count", "Unread_Count_DM": "Unread Count for Direct Messages", + "Unread_Count_Omni": "Unread Count for Omnichannel Chats", "Unread_Messages": "Unread Messages", "Unread_on_top": "Unread on top", "Unread_Rooms": "Unread Rooms", @@ -5550,5 +5553,47 @@ "Theme_dark": "Dark", "Theme_match_system": "Match system", "Join_your_team": "Join your team", - "Create_an_account": "Create an account" + "Create_an_account": "Create an account", + "Use_token": "Use token", + "Deregister": "Deregister", + "Deregister_workspace": "Deregister workspace", + "Awaiting_confirmation": "Awaiting confirmation", + "Security_code": "Security code", + "Registration_Token": "Registration Token", + "RegisterWorkspace_Button": "Register workspace", + "Workspace_registered": "Workspace registered", + "RegisterWorkspace_Registered_Description": "These services are available", + "RegisterWorkspace_Registered_Subtitle": "Because this workspace is registered the following is available", + "RegisterWorkspace_Registered_Benefits": "Registration allows automatic license updates, notifications of critical vulnerabilities and access to Rocket.Chat Cloud services. No sensitive workspace data is shared with Rocket.Chat.", + "RegisterWorkspace_NotRegistered_Title": "Workspace not registered", + "RegisterWorkspace_NotRegistered_Subtitle": "Register this workspace and get", + "RegisterWorkspace_NotRegistered_Description": "Benefits of registering workspace", + "RegisterWorkspace_Deregister_Subtitle": "Deregistering your workspace will result in the loss of the following", + "RegisterWorkspace_Features_MobileNotifications_Title": "Mobile push notifications", + "RegisterWorkspace_Features_MobileNotifications_Description": "Allows workspace members to receive notifications on their mobile devices.", + "RegisterWorkspace_Features_MobileNotifications_Deregister": "Workspace members will no longer receive notifications on their mobile devices.", + "RegisterWorkspace_Features_Marketplace_Title": "Marketplace", + "RegisterWorkspace_Features_Marketplace_Description": "Install Rocket.Chat Marketplace apps on this workspace.", + "RegisterWorkspace_Features_Marketplace_Deregister": "It will no longer be possible to install apps.", + "RegisterWorkspace_Features_Omnichannel_Title": "Omnichannel", + "RegisterWorkspace_Features_Omnichannel_Description": "Talk to your audience, where they are, through the most popular social channels in the world.", + "RegisterWorkspace_Features_Omnichannel_Deregister": "Omnichannel capabilities will no longer be available.", + "RegisterWorkspace_Features_ThirdPartyLogin_Title": "Third-party login", + "RegisterWorkspace_Features_ThirdPartyLogin_Description": "Let workspace members log in using a set of third-party applications.", + "RegisterWorkspace_Features_ThirdPartyLogin_Deregister": "Third-party login options will no longer be available.", + "RegisterWorkspace_Token_Title": "Register workspace with token", + "RegisterWorkspace_Token_Step_One": "Go to: cloud.rocket.chat > Workspaces and click 'Register self-managed'.", + "RegisterWorkspace_Token_Step_Two": "Copy the token and paste it below.", + "RegisterWorkspace_Registration_Token": "Click 'Register'.", + "RegisterWorkspace_with_email": "Register workspace with email", + "RegisterWorkspace_Setup_Subtitle": "To register this workspace it needs to be associated it with a Rocket.Chat Cloud account.", + "RegisterWorkspace_Setup_Steps": "Step __step__ of __numberOfSteps__", + "RegisterWorkspace_Setup_Label": "Cloud account email", + "RegisterWorkspace_Setup_Have_Account_Title": "Have an account?", + "RegisterWorkspace_Setup_Have_Account_Subtitle": "Enter your Cloud account email to associate this workspace with your account.", + "RegisterWorkspace_Setup_No_Account_Title": "Don't have an account?", + "RegisterWorkspace_Setup_No_Account_Subtitle": "Enter your email to create a new Cloud account and associate this workspace.", + "RegisterWorkspace_Setup_Email_Confirmation": "Email sent to __email__ with a confirmation link.", + "RegisterWorkspace_Setup_Email_Verification": "Please verify that the security code below matches the one in the email.", + "RegisterWorkspace_Setup_Terms_Privacy": "I agree with Terms and Conditions and Privacy Policy" } diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/eo.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/eo.i18n.json index f85114d15214..a158a5662fba 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/eo.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/eo.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Aldonaj retpoŝtoj", "Additional_Feedback": "Pliaj rimarkoj", "additional_integrations_Bots": "Se vi serĉas kiel integri vian propran boton, tiam rigardu ne plu ol nia Hubot-adaptilo. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Ĉu vi volas integri aliajn programojn kaj programojn kun Rocket.Chat sed vi ne havas la tempon por permane fari ĝin? Tiam ni sugestas uzi Zapier, kiun ni plene subtenas. Legu pli pri ĝi pri nia dokumentado. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Admin Informoj", "Administration": "Administrado", "Adult_images_are_not_allowed": "Plenkreskulaj bildoj ne estas permesataj", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json index 633f7068ecf2..64aa8dbeaaf3 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json @@ -291,7 +291,6 @@ "Additional_emails": "Correos electrónicos adicionales", "Additional_Feedback": "Comentarios adicionales", "additional_integrations_Bots": "Si estás buscando cómo integrar tu propio bot, nuestro adaptador Hubot es lo que necesitas. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "¿Quieres integrar otro software y otras aplicaciones con Rocket.Chat pero no tienes tiempo para hacerlo manualmente? Si es así, te sugerimos que uses Zapier, que es totalmente compatible. Consulta nuestra documentación para obtener más información al respecto. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Tu administrador no ha habilitado el cifrado E2E", "Admin_Info": "Información de administración", "Administration": "Administración", @@ -697,7 +696,6 @@ "Busy": "Ocupado", "By": "Por", "by": "por", - "By_author": "Por __author__", "cache_cleared": "Caché borrada", "Call": "Llamada", "Calling": "Llamando", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/fa.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/fa.i18n.json index 8f7a3aeae5ef..b35aa219fae0 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/fa.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/fa.i18n.json @@ -243,7 +243,6 @@ "Additional_emails": "ایمیل های اضافی", "Additional_Feedback": "بازخورد اضافی", "additional_integrations_Bots": "اگر به دنبال چگونگی ادغام ربات خود هستید ، به دنبال آداپتور Hubot ما نباشید. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "آیا می خواهید نرم افزار و برنامه های دیگر را با Rocket.Chat ادغام کنید اما زمان لازم برای انجام این کار را ندارید؟ ما پیشنهاد می کنیم از Zapier استفاده کنید که ما به طور کامل آن را پشتیبانی می کنیم. در مورد اسناد ما بیشتر بخوانید. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "ادمین شما رمزگذاری E2E را فعال نکرده.", "Admin_Info": "اطلاعات مدیریت", "Administration": "مدیریت", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/fi.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/fi.i18n.json index 3f060aca21d2..70bf0291d7f2 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/fi.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/fi.i18n.json @@ -4,9 +4,10 @@ "__count__empty_rooms_will_be_removed_automatically__rooms__": "__count__ tyhjää huonetta poistetaan automaattisesti:
__rooms__.", "__count__message_pruned": "__count__ viesti karsittu", "__count__message_pruned_plural": "__count__ viestiä karsittu", + "__usersCount__member_joined": "+ __usersCount__ jäsen liittyi", "__usersCount__member_joined_plural": "+ __usersCount__ jäsentä liittynyt", "__usersCount__people_will_be_invited": "__usersCount__ henkilöä kutsutaan", - "__username__is_no_longer__role__defined_by__user_by_": "__username__ ei ole enää __role__ (__user_by__ toimesta)", + "__username__is_no_longer__role__defined_by__user_by_": "__username__ ei ole enää __role__ (muutoksen teki __user_by__)", "__username__was_set__role__by__user_by_": "__user_by__ muutti käyttäjän __username__ rooliksi __role__ ", "This_room_encryption_has_been_enabled_by__username_": "Tämän huoneen salauksen otti käyttöön __username__ ", "This_room_encryption_has_been_disabled_by__username_": "Tämän huoneen salauksen poisti käytöstä __username__", @@ -21,15 +22,19 @@ "2_Erros_Information_and_Debug": "2 - Virheet, tiedot ja virheenkorjaus", "12_Hour": "12 tunnin kello", "24_Hour": "24 tunnin kello", + "A_cloud-based_platform_for_those_needing_a_plug-and-play_app": "Pilviympäristö niille, jotka tarvitsevat plug-and-play-sovelluksen.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Uusi omistaja liitetään automaattisesti __count__ huoneeseen.", "A_new_owner_will_be_assigned_automatically_to_the__roomName__room": "Uusi omistaja liitetään automaattisesti huoneeseen __roomName__.", "A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__": "Uusi omistaja liitetään automaattisesti __count__ huoneeseen:
__rooms__.", + "A_secure_and_highly_private_self-managed_solution_for_conference_calls": "Suojattu ja vahvasti yksityinen itsepalveluratkaisu neuvottelupuheluille.", + "A_workspace_admin_needs_to_install_and_configure_a_conference_call_app": "Työtilan järjestelmänvalvojan on asennettava ja määritettävä neuvottelupuhelusovellus.", + "An_app_needs_to_be_installed_and_configured": "Sovellus on asennettavaa ja määritettävä.", "Accept_Call": "Hyväksy puhelu", "Accept": "Hyväksy", "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Hyväksy saapuvat monikanavapyynnöt, vaikka agentteja ei ole paikalla", "Accept_new_livechats_when_agent_is_idle": "Hyväksy uudet monikanavapyynnöt, kun agentti on vapaa", "Accept_with_no_online_agents": "Hyväksy ilman agentteja paikalla", - "Access_not_authorized": "Pääsy estetty", + "Access_not_authorized": "Pääsyä ei valtuutettu", "Access_Token_URL": "Käyttöoikeustietueen URL-osoite", "Access_Your_Account": "Avaa käyttäjätilisi", "access-mailer": "Avaa postittajanäyttö", @@ -79,7 +84,7 @@ "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Salli käyttäjien valita Lähetä myös kanavalle -toiminta", "Accounts_Default_User_Preferences_desktopNotifications": "Työpöytäilmoitusten oletushälytys", "Accounts_Default_User_Preferences_pushNotifications": "Push-ilmoitusten oletushälytys", - "Accounts_Default_User_Preferences_not_available": "Käyttäjäasetuksia ei voitu hakea, koska käyttäjä ei ole vielä asettanut niitä", + "Accounts_Default_User_Preferences_not_available": "Käyttäjäasetuksia ei voitu hakea, koska käyttäjä ei ole vielä määrittänyt niitä", "Accounts_DefaultUsernamePrefixSuggestion": "Oletuskäyttäjätunnuksen etuliite-ehdotus", "Accounts_denyUnverifiedEmail": "Estä vahvistamaton sähköpostiosoite", "Accounts_Directory_DefaultView": "Oletushakemistoluettelo", @@ -111,7 +116,7 @@ "Accounts_OAuth_Custom_Button_Label_Color": "Painiketekstin väri", "Accounts_OAuth_Custom_Button_Label_Text": "Painiketeksti", "Accounts_OAuth_Custom_Channel_Admin": "Käyttäjätietoryhmän määritys", - "Accounts_OAuth_Custom_Channel_Map": "OAuth-ryhmän Channel-määritys", + "Accounts_OAuth_Custom_Channel_Map": "OAuth-ryhmän kanavamääritys", "Accounts_OAuth_Custom_Email_Field": "Sähköpostikenttä", "Accounts_OAuth_Custom_Enable": "Ota käyttöön", "Accounts_OAuth_Custom_Groups_Claim": "Kanavamäärityksen Roolit/Ryhmät-kenttä", @@ -231,7 +236,7 @@ "Accounts_RegistrationForm_SecretURL_Description": "Anna satunnainen merkkijono, joka lisätään rekisteröitymis-URL-osoitteeseesi. Esimerkki: https://open.rocket.chat/register/[secret_hash]", "Accounts_RequireNameForSignUp": "Edellytä nimeä rekisteröidyttäessä", "Accounts_RequirePasswordConfirmation": "Edellytä salasanan vahvistusta", - "Accounts_RoomAvatarExternalProviderUrl": "Room-avatarin ulkoisen toimittajan URL-osoite", + "Accounts_RoomAvatarExternalProviderUrl": "Huoneen avatarin ulkoisen toimittajan URL-osoite", "Accounts_RoomAvatarExternalProviderUrl_Description": "Esimerkki: `https://acme.com/api/v1/{roomId}`", "Accounts_SearchFields": "Haussa huomioitavat kentät", "Accounts_Send_Email_When_Activating": "Lähetä käyttäjälle sähköpostia, kun käyttäjä on aktivoitu", @@ -290,8 +295,8 @@ "add-livechat-department-agents_description": "Oikeus lisätä monikanava-agentteja osastoille", "add-oauth-service": "Lisää OAuth-palvelu", "add-oauth-service_description": "Oikeus lisätä uusi OAuth-palvelu", - "add-team-channel": "Lisää tiimin Channel", - "add-team-channel_description": "Lupa lisätä kanava tiimiin", + "add-team-channel": "Lisää tiimin kanava", + "add-team-channel_description": "Oikeus lisätä kanava tiimiin", "add-team-member": "Lisää tiimin jäsen", "add-team-member_description": "Oikeus lisätä jäseniä tiimiin", "add-user": "Lisää käyttäjä", @@ -312,7 +317,6 @@ "Additional_emails": "Lisäsähköposti", "Additional_Feedback": "Lisäpalaute", "additional_integrations_Bots": "Jos haluat ingeroida oman botin, kokeile Hubot-sovitintamme. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Haluatko integroida muita ohjelmia ja sovelluksia Rocket.Chatiin, muttet ehdi tehdä sitä manuaalisesti? Sitten suosittelemme Zapierin käyttöä, jota tuemme täysin. Lue lisää oppaistamme. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Järjestelmänvalvoja ei ole ottanut E2E-salausta käyttöön.", "Admin_Info": "Järjestelmänvalvojan tiedot", "admin-no-active-video-conf-provider": "**Neuvottelupuhelu ei ole käytössä**: Määritä neuvottelupuhelut, jotta ne ovat käytettävissä tässä työtilassa.", @@ -434,7 +438,7 @@ "API_Upper_Count_Limit": "Tietueiden enimmäismäärä", "API_Upper_Count_Limit_Description": "Mikä on enimmäismäärä tietueita, jotka REST APIn pitäisi palauttaa (jos ei rajoittamaton)?", "API_Use_REST_For_DDP_Calls": "Käytä RESTiä Meteor-puheluihin websocketin sijasta", - "API_User_Limit": "Käyttäjärajoitus kaikkien käyttäjien lisäämiselle Channel", + "API_User_Limit": "Käyttäjärajoitus kaikkien käyttäjien lisäämiselle kanavalle", "API_Wordpress_URL": "WordPress-URL-osoite", "api-bypass-rate-limit": "REST APIn ohitusnopeusraja", "api-bypass-rate-limit_description": "Oikeus kutsua APIa ilman nopeuden rajoitusta", @@ -446,6 +450,7 @@ "App_Info": "Sovelluksen tiedot", "App_Information": "Sovellustiedot", "App_Installation": "Sovelluksen asennus", + "App_not_enabled": "Sovellus ei käytössä", "App_not_found": "Sovellusta ei löydy", "App_status_auto_enabled": "Käytössä", "App_status_constructed": "Rakennettu", @@ -518,7 +523,7 @@ "Apps_Logs_TTL_14days": "14 päivää", "Apps_Logs_TTL_30days": "30 päivää", "Apps_Logs_TTL_Alert": "Lokikokoelman koosta riippuen tämän asetuksen muuttaminen voi aiheuttaa ajoittain hidastumista", - "Apps_Marketplace_Deactivate_App_Prompt": "Haluatko vartodella poistaa tämän sovelluksen käytöstä?", + "Apps_Marketplace_Deactivate_App_Prompt": "Haluatko varmasti poistaa tämän sovelluksen käytöstä?", "Apps_Marketplace_Login_Required_Description": "Sovellusten ostaminen Rocket.Chatin kaupasta edellyttää työtilan rekisteröintiä ja kirjautumista sisään.", "Apps_Marketplace_Login_Required_Title": "Kauppaan on kirjauduttava", "Apps_Marketplace_Modify_App_Subscription": "Muokkaa tilausta", @@ -582,9 +587,15 @@ "Apps_WhatIsIt_paragraph4": "Jos kuitenkin haluat ottaa ominaisuuden käyttöön ja kokeilla sitä, napsauta ota sovellusjärjestelmä käyttöön valitsemalla tämä painike.", "Archive": "Arkistoi", "Archived": "Arkistoitu", - "archive-room": "Arkistoi Room", + "archive-room": "Arkistoi huone", "archive-room_description": "Oikeus arkistoida kanava", "are_typing": "kirjoittavat", + "are_playing": "pelaavat", + "is_playing": "pelaa", + "are_uploading": "lataavat", + "are_recording": "tallentavat", + "is_uploading": "lataa", + "is_recording": "tallentaa", "Are_you_sure": "Oletko varma?", "Are_you_sure_you_want_to_clear_all_unread_messages": "Haluatko varmasti tyhjentää kaikki lukemattomat viestit?", "Are_you_sure_you_want_to_close_this_chat": "Haluatko varmasti sulkea tämän keskustelun?", @@ -682,7 +693,7 @@ "Back_to_login": "Takaisin kirjautumiseen", "Back_to_Manage_Apps": "Takaisin sovellusten hallintaan", "Back_to_permissions": "Takaisin oikeuksiin", - "Back_to_room": "Takaisin Room", + "Back_to_room": "Takaisin huoneeseen", "Back_to_threads": "Takaisin viestiketjuihin", "Backup_codes": "Varakoodit", "ban-user": "Estä käyttäjä", @@ -712,7 +723,7 @@ "Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes": "Aika IP-osoitteen eston purkuun (minuutteina)", "Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes": "Aika käyttäjän eston purkuun (minuutteina)", "Block_Multiple_Failed_Logins_Notify_Failed": "Ilmoitus epäonnistuneista kirjautumisyrityksistä", - "Block_Multiple_Failed_Logins_Notify_Failed_Channel": "Channel ilmoitusten lähettämiseen", + "Block_Multiple_Failed_Logins_Notify_Failed_Channel": "Kanava ilmoitusten lähettämiseen", "Block_Multiple_Failed_Logins_Notify_Failed_Channel_Desc": "Ilmoitukset vastaanotetaan tähän. Varmista, että kanava on olemassa. Kanavan nimi ei saa sisältää #-symbolia", "Block_User": "Estä käyttäjä", "Blockchain": "Lohkoketju", @@ -735,7 +746,7 @@ "Bots_Description": "Määritä kentät, joihin voidaan viitata ja joita voidaan käyttää kehitettäessä botteja.", "Branch": "Haara", "Broadcast": "Lähetys", - "Broadcast_channel": "Lähetyskanava Channel", + "Broadcast_channel": "Lähetyskanava", "Broadcast_channel_Description": "Vain valtuutetut käyttäjät voivat kirjoittaa uusia viestejä, mutta muut käyttäjät voivat vastata", "Broadcast_Connected_Instances": "Lähetä yhdistetyt esiintymät", "Broadcasting_api_key": "Lähetyksen API-avain", @@ -764,11 +775,11 @@ "Buy": "Osta", "By": "Mukaan", "by": "mukaan", - "By_author": "Mukaan __author__", "cache_cleared": "Välimuisti tyhjennetty", "Call": "Soita", + "Call_back": "Soita takaisin", "Calling": "Soitetaan", - "Call_Center": "Ääni Channel", + "Call_Center": "Äänikanava", "Call_Center_Description": "Määritä Rocket.Chatin äänikanavat", "Call_ended": "Puhelu päättyi", "Calls": "Puhelut", @@ -776,6 +787,7 @@ "Calls_in_queue_plural": "__calls__ puhelua jonossa", "Calls_in_queue_empty": "Jono on tyhjä", "Call_declined": "Puhelu hylätty!", + "Call_history_provides_a_record_of_when_calls_took_place_and_who_joined": "Puheluhistoriassa näkyvät puhelujen ajat ja osallistujat.", "Call_Information": "Puhelutiedot", "Call_provider": "Puhelun palveluntarjoaja", "Call_Already_Ended": "Puhelu on jo päättynyt", @@ -783,7 +795,10 @@ "Call_number_enterprise_only": "Soittajanumero (vain Enterprise Edition)", "call-management": "Puhelujen hallinta", "call-management_description": "Oikeus aloittaa kokous", + "Call_ongoing": "Puhelu käynnissä", + "Call_started": "Puhelu aloitettu", "Call_unavailable_for_federation": "Puhelu ei ole käytettävissä liittoutuneissa huoneissa", + "Call_was_not_answered": "Puheluun ei vastattu", "Caller": "Soittaja", "Caller_Id": "Soittajan tunnus", "Cam_on": "Kamera käytössä", @@ -839,25 +854,25 @@ "CDN_PREFIX": "CDN-etuliite", "CDN_PREFIX_ALL": "Käytä CDN-etuliitettä kaikille resursseille", "Certificates_and_Keys": "Varmenteet ja avaimet", - "change-livechat-room-visitor": "Muuta Livechat Room Vierailijoita", + "change-livechat-room-visitor": "Muuta Livechat-huoneen vierailijoita", "change-livechat-room-visitor_description": "Oikeus lisätä tietoja Livechat-huoneen vieraalle", - "Change_Room_Type": "Huoneen tyypin Room muuttaminen", + "Change_Room_Type": "Huoneen tyypin muuttaminen", "Changing_email": "Vaihdetaan sähköpostia", "channel": "kanava", - "Channel": "Channel", + "Channel": "Kanava", "Channel_already_exist": "Kanava '#%s' on jo olemassa.", "Channel_already_exist_static": "Kanava on jo olemassa.", - "Channel_already_Unarchived": "Channel nimellä `#%s` on jo Arkistoimaton-tilassa", - "Channel_Archived": "Channel nimellä `#%s` on Arkistoitu onnistuneesti", - "Channel_created": "Channel `%s` luotiin.", + "Channel_already_Unarchived": "Kanava nimellä `#%s` on jo Arkistoimaton-tilassa", + "Channel_Archived": "Kanava nimellä `#%s` on arkistoitu", + "Channel_created": "Kanava `%s` luotiin.", "Channel_doesnt_exist": "Kanavaa `#%s` ei ole olemassa.", - "Channel_Export": "Channel Vienti", - "Channel_name": "Channel nimi", + "Channel_Export": "Kanavan vienti", + "Channel_name": "Kanavan nimi", "Channel_Name_Placeholder": "Anna kanavan nimi...", - "Channel_to_listen_on": "Kuunneltava Channel", - "Channel_Unarchived": "Channel `#%s` on asetettu arkistoimattomaksi", - "Channels": "Channel Kanavat lisätty", - "Channels_added": "Channel on lisätty", + "Channel_to_listen_on": "Kuunneltava kanava", + "Channel_Unarchived": "Kanava nimellä `#%s` on asetettu arkistoimattomaksi", + "Channels": "Kanavat", + "Channels_added": "Kanava on lisätty", "Channels_are_where_your_team_communicate": "Tiimisi voi kommunikoida kanavilla", "Channels_list": "Julkisten kanavien luettelo", "Channel_what_is_this_channel_about": "Mikä on tämän kanavan aihe?", @@ -892,7 +907,7 @@ "Chatpal_Base_URL_Description": "Etsi kuvaus paikallisen esiintymän suorittamisesta githubissa. URL-osoitteen on oltava absoluuttinen ja viitattava chatpal-ytimeen, esim. http://localhost:8983/solr/chatpal.", "Chatpal_Batch_Size": "Indeksin erän koko", "Chatpal_Batch_Size_Description": "Indeksitiedostojen erän koko (käynnistyksessä)", - "Chatpal_channel_not_joined_yet": "Channel ei ole vielä liittynyt", + "Chatpal_channel_not_joined_yet": "Kanava ei ole vielä liittynyt", "Chatpal_create_key": "Luo avain", "Chatpal_created_key_successfully": "API-avain on luotu", "Chatpal_Current_Room_Only": "Sama huone", @@ -910,7 +925,7 @@ "Chatpal_go_to_user": "Lähetä suora viesti", "Chatpal_HTTP_Headers": "Http-ylätunnisteet", "Chatpal_HTTP_Headers_Description": "Luettelo HTTP-ylätunnisteista, yksi ylätunniste rivillä. Muoto: nimi:arvo", - "Chatpal_Include_All_Public_Channels": "Sisällytä kaikki julkiset Channel", + "Chatpal_Include_All_Public_Channels": "Sisällytä kaikki julkiset kanavat", "Chatpal_Include_All_Public_Channels_Description": "Hae kaikilta julkisilta kanavilta, vaikket olisi liittynyt niille vielä.", "Chatpal_Main_Language": "Pääkieli", "Chatpal_Main_Language_Description": "Kieli jota käytetään useimmissa keskusteluissa", @@ -920,7 +935,7 @@ "Chatpal_No_Results": "Ei tuloksia", "Chatpal_no_search_results": "Ei tulosta", "Chatpal_one_search_result": "Löytyi 1 tulos", - "Chatpal_Rooms": "Rooms", + "Chatpal_Rooms": "Huoneet", "Chatpal_run_search": "Haku", "Chatpal_search_page_of": "Sivu %s/%s", "Chatpal_search_results": "Löytyi %s tulosta", @@ -946,7 +961,7 @@ "Choose_users": "Valitse käyttäjät", "Clean_History_unavailable_for_federation": "Tyhjennyshistoria ei ole käytettävissä liittoutuneissa huoneissa", "Clean_Usernames": "Tyhjennä käyttäjätunnukset", - "clean-channel-history": "Puhdista Channel historia", + "clean-channel-history": "Tyhjennä kanavan historia", "clean-channel-history_description": "Oikeus tyhjentää historia kanavilta", "clear": "Tyhjennä", "Clear_all_unreads_question": "Tyhjennetäänkö kaikki lukemattomat?", @@ -975,7 +990,7 @@ "Close_room_description": "Olet sulkemassa keskustelun. Haluatko varmasti jatkaa?", "Close_to_seat_limit_banner_warning": "*Sinulla on [__seats__] käyttäjää jäljellä* \nTämä työtila lähestyy käyttäjämäärän rajaa. Kun raja on saavutettu, uusia jäseniä ei voida lisätä. *[Request More Seats](__url__)*", "Close_to_seat_limit_warning": "Uusia jäseniä ei voida luoda, kun käyttäjämäärä on saavutettu.", - "close-livechat-room": "Sulje Omnichannel Room", + "close-livechat-room": "Sulje Omnichannel-huone", "close-livechat-room_description": "Lupa nykyisen monikanavahuoneen sulkemiseen", "Close_menu": "Sulje valikko", "close-others-livechat-room": "Sulje Muut Omnichannel Room huoneet", @@ -983,6 +998,7 @@ "Closed": "Suljettu", "Closed_At": "Suljettu", "Closed_automatically": "Järjestelmä sulkenut automaattisesti", + "Closed_automatically_because_chat_was_onhold_for_seconds": "Suljettiin automaattisesti, koska keskustelu oli pidossa __onHoldTime__ sekuntia", "Closed_automatically_chat_queued_too_long": "Järjestelmä sulkenut automaattisesti (jonon enimmäisaika ylitetty)", "Closed_by_visitor": "Vierailija sulki", "Closing_chat": "Suljetaan keskustelua", @@ -1050,10 +1066,12 @@ "Commit_details": "Vahvista tiedot", "Completed": "Valmis", "Computer": "Tietokone", + "Conference_call_apps": "Neuvottelupuhelusovellukset", "Conference_call_has_ended": "_Puhelu on päättynyt._", "Conference_name": "Neuvottelun nimi", "Configure_Incoming_Mail_IMAP": "Määritä saapuva sähköposti (IMAP)", "Configure_Outgoing_Mail_SMTP": "Määritä lähtevä sähköposti (SMTP)", + "Configure_video_conference_to_make_it_available_on_this_workspace": "Määritä videoneuvottelupuhelu käyttöön tässä työtilassa", "Confirm": "Vahvista", "Confirm_new_encryption_password": "Vahvista uusi salauksen salasana", "Confirm_new_password": "Vahvista uusi salasana", @@ -1089,6 +1107,7 @@ "convert-team_description": "Oikeus muuntaa tiimi kanavaksi", "Conversation": "Keskustelu", "Conversation_closed": "Keskustelu suljettu: __comment__.", + "Conversation_closed_without_comment": "Keskustelu suljettu", "Conversation_closing_tags": "Keskustelun lopetustunnisteet", "Conversation_closing_tags_description": "Lopetustunnisteet liitetään keskusteluihin automaattisesti suljettaessa.", "Conversation_finished": "Keskustelu päättynyt", @@ -1099,7 +1118,7 @@ "Conversations_per_day": "Keskusteluja päivässä", "Convert": "Muunna", "Convert_Ascii_Emojis": "Muunna ASCII-merkit emojeiksi", - "Convert_to_channel": "Muunna kohteeseen Channel", + "Convert_to_channel": "Muunna kanavaksi", "Converting_channel_to_a_team": "Muunnat tämän Channel kanavan tiimiksi. Kaikki jäsenet säilyvät ennallaan.", "Converted__roomName__to_team": "muunsi huoneen #__roomName__ tiimiksi", "Converted__roomName__to_channel": "muunnettu #__roomName__ Channel kanavaksi", @@ -1357,7 +1376,7 @@ "Cozy": "Kodikas", "Create": "Luo", "Create_Canned_Response": "Luo valmis vastaus", - "Create_channel": "Luo Channel", + "Create_channel": "Luo kanava", "Create_channels": "Luo kanavia", "Create_a_public_channel_that_new_workspace_members_can_join": "Luo julkinen kanava, jolle työtilan uudet jäsenet voivat liittyä.", "Create_A_New_Channel": "Luo uusi kanava Channel", @@ -1382,7 +1401,7 @@ "Created_as": "Luotu nimellä", "Created_at": "Luotu", "Created_at_s_by_s": "Luonut %s %s ", - "Created_at_s_by_s_triggered_by_s": "Luotu osoitteessa %s tekijän %s toimesta ja käynnistäjänä %s", + "Created_at_s_by_s_triggered_by_s": "Luotu: %s %s, laukaisin %s", "Created_by": "Luonut", "CRM_Integration": "CRM-integraatio", "CROWD_Allow_Custom_Username": "Salli mukautettu käyttäjätunnus Rocket.Chatissa", @@ -1530,6 +1549,7 @@ "Desktop_Notifications_Duration_Description": "Työpöytäilmoitusten näkymisaika sekunteina. Tämä voi vaikuttaa OS X:n ilmoituskeskukseen. Arvolla 0 voit käyttää selaimen oletusasetuksia, mikä ei vaikuta OS X:n ilmoituskeskukseen.", "Desktop_Notifications_Enabled": "Työpöytäilmoitukset ovat käytössä", "Desktop_Notifications_Not_Enabled": "Työpöytäilmoitukset eivät ole käytössä", + "Unselected_by_default": "Oletusarvoisesti valitsematta", "Details": "Tiedot", "Device_Changes_Not_Available": "Laitemuutokset eivät ole käytettävissä tässä selaimessa. Jos haluat varmistaa käytettävyyden, käytä Rocket.Chatin virallista tietokonesovellusta.", "Device_Changes_Not_Available_Insecure_Context": "Laitemuutokset ovat käytettävissä vain suojatuissa yhteyksissä (esim. https://)", @@ -1597,7 +1617,7 @@ "Discussion": "Keskustelu", "Discussion_Description": "Keskustelut on lisätapa järjestää keskusteluja. Sillä voidaan kutsua ulkopuolisia käyttäjiä osallistumaan tiettyihin keskusteluihin.", "Discussion_description": "Helpottaa kokonaiskuvan säilyttämistä. Luomalla keskustelun luot valitsemallesi kanavalle alikanavan, ja kanavat liitetään yhteen.", - "Discussion_first_message_disabled_due_to_e2e": "Voit aloittaa End-to-End-salattujen viestien lähettämisen tässä keskustelussa sen luomisen jälkeen.", + "Discussion_first_message_disabled_due_to_e2e": "Voit aloittaa täysin salattujen viestien lähettämisen tässä keskustelussa sen luomisen jälkeen.", "Discussion_first_message_title": "Viestisi", "Discussion_name": "Keskustelun nimi", "Discussion_start": "Aloita keskustelu", @@ -1645,7 +1665,7 @@ "Drop_to_upload_file": "Lataa tiedosto pudottamalla", "Dry_run": "Harjoitus", "Dry_run_description": "Lähettää vain yhden sähköpostiviestin lähettäjän osoitteeseen. Osoitteen on kuuluttava kelvolliselle käyttäjälle.", - "Duplicate_archived_channel_name": "Arkistoitu Channel kanava nimellä `#%s` on jo olemassa", + "Duplicate_archived_channel_name": "Arkistoitu kanava nimellä `#%s` on jo olemassa", "Duplicate_archived_private_group_name": "Arkistoitu yksityinen ryhmä '%s' on olemassa", "Duplicate_channel_name": "Channel kanava nimellä '%s' on jo olemassa", "Duplicate_file_name_found": "Samanniminen tiedosto löytyi.", @@ -1686,7 +1706,7 @@ "Edit_Trigger": "Muokkaa laukaisijaa", "Edit_Unit": "Muokkaa yksikköä", "Edit_User": "Muokkaa käyttäjää", - "edit-livechat-room-customfields": "Muokkaa Livechat Room Mukautetut kentät", + "edit-livechat-room-customfields": "Muokkaa Livechat-huoneen mukautettuja kenttiä", "edit-livechat-room-customfields_description": "Oikeus muokata Livechat-huoneen mukautettuja kenttiä", "edit-message": "Muokkaa viestiä", "edit-message_description": "Oikeus muokata viestiä huoneessa", @@ -1706,13 +1726,13 @@ "edit-privileged-setting_description": "Oikeus muokata asetuksia", "edit-team": "Muokkaa tiimiä", "edit-team_description": "Oikeus muokata tiimejä", - "edit-team-channel": "Muokkaa tiimiä Channel", + "edit-team-channel": "Muokkaa tiimin kanavaa", "edit-team-channel_description": "Oikeus muokata tiimin kanavaa", "edit-team-member": "Muokkaa tiimin jäsentä", "edit-team-member_description": "Oikeus muokata tiimin jäseniä", - "edit-room": "Muokkaa tilaa Room", + "edit-room": "Muokkaa huonetta", "edit-room_description": "Oikeus muokata huoneen nimeä, aihetta, tyyppiä (yksityinen tai julkinen tila) ja tilaa (aktiivinen tai arkistoitu)", - "edit-room-avatar": "Muokkaa Room Avataria", + "edit-room-avatar": "Muokkaa huoneen avataria", "edit-room-avatar_description": "Oikeus muokata huoneen avataria.", "edit-room-retention-policy": "Muokkaa huoneen Room säilytyskäytäntöä", "edit-room-retention-policy_description": "Oikeus muokata huoneen säilytyskäytäntöä, jotta viestit poistetaan automaattisesti", @@ -1766,6 +1786,8 @@ "Enable_Auto_Away": "Ota käyttöön automaattinen Poissa-tila", "Enable_CSP": "Ota käyttöön sisällönsuojauskäytäntö", "Enable_CSP_Description": "Älä poista tätä asetusta käytöstä, ellei sinulla ole mukautettua koontiversiota ja jos sinulla on ongelmia sisäisten komentosarjojen vuoksi", + "Extra_CSP_Domains": "CSP-lisätoimialueet", + "Extra_CSP_Domains_Description": "Sisällönsuojauskäytäntöön lisättävät lisätoimialueet", "Enable_Desktop_Notifications": "Ota käyttöön työpöytäilmoitukset", "Enable_inquiry_fetch_by_stream": "Ota käyttöön kyselytietojen nouto palvelimesta virran kautta", "Enable_omnichannel_auto_close_abandoned_rooms": "Salli vieraan hylkäämien huoneiden automaattinen sulkeminen", @@ -1811,6 +1833,7 @@ "Enter_to": "Siirry", "Enter_your_E2E_password": "Anna E2E-salasanasi", "Enterprise": "Yritys", + "Enterprise_capabilities": "Yritysominaisuudet", "Enterprise_Description": "Päivitä yrityskäyttöoikeus manuaalisesti.", "Enterprise_License": "Yrityskäyttöoikeus", "Enterprise_License_Description": "Jos työtilasi on rekisteröity ja sinulla on Rocket.Chat-pilven käyttöoikeus, sinun ei tarvitse päivittää käyttöoikeutta manuaalisesti tässä.", @@ -1935,7 +1958,7 @@ "error-password-policy-not-met-oneLowercase": "Salasana ei vastaa palvelimen käytäntöä, jonka mukaan tarvitaan vähintään yksi pieni kirjain", "error-password-policy-not-met-oneNumber": "Salasana ei vastaa palvelimen käytäntöä, jonka mukaan tarvitaan vähintään yksi numeromerkki", "error-password-policy-not-met-oneSpecial": "Salasana ei vastaa palvelimen käytäntöä, jonka mukaan tarvitaan vähintään yksi erikoismerkki", - "error-password-policy-not-met-oneUppercase": "Salasana ei vastaa asetettua vaatimusta, vähintään yksi iso kirjain", + "error-password-policy-not-met-oneUppercase": "Salasana ei vastaa palvelimen käytäntöä, jonka mukaan tarvitaan vähintään yksi iso kirjain", "error-password-policy-not-met-repeatingCharacters": "Salasana ei vastaa palvelimen kiellettyjen toistuvien merkkien käytäntöä (liian monta samaa merkkiä peräkkäin)", "error-password-same-as-current": "Annettu salasana sama kuin nykyinen salasana", "error-personal-access-tokens-are-current-disabled": "Henkilökohtaiset käyttöoikeustietueet ovat tällä hetkellä poissa käytöstä", @@ -1947,8 +1970,8 @@ "error-role-name-required": "Roolin nimi on pakollinen", "error-room-does-not-exist": "Huonetta ei ole olemassa", "error-role-already-present": "Tämänniminen rooli on jo olemassa", - "error-room-is-not-closed": "Room Huone ei ole suljettu", - "error-room-onHold": "Virhe! Room Huone on pidossa", + "error-room-is-not-closed": "Huone ei ole suljettu", + "error-room-onHold": "Virhe! Huone on pidossa", "error-selected-agent-room-agent-are-same": "Valittu agentti on sama kuin huoneen agentti", "error-starring-message": "Viestiä ei voitu merkata tähdellä", "error-tags-must-be-assigned-before-closing-chat": "Tunnisteet on määritettävä ennen keskustelun sulkemista", @@ -2034,11 +2057,11 @@ "Fallback_forward_department": "Varaosasto välitystä varten", "Fallback_forward_department_description": "Voit määrittää varaosaston, joka vastaanottaa tälle välitetyt keskustelut, jos agentteja ei ole paikalla", "Favorite": "Suosikki", - "Favorite_Rooms": "Ota käyttöön Room suosikkihuoneet", + "Favorite_Rooms": "Ota käyttöön suosikkihuoneet", "Favorites": "Suosikit", "featured": "esittelyssä", "Featured": "Esittelyssä", - "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Tämä ominaisuus riippuu yllä valitusta puhelun tarjoajasta, joka on otettu käyttöön hallinta-asetuksissa.
Jos kyseessä on **Jitsi**, varmista, että Jitsi on käytössä, kohdassa Hallinta > Videoneuvottelu > Jitsi > Käytössä.
Jos kyseessä on **WebRTC**, varmista, että WebRTC on käytössä kohdassa Hallinta > WebRTC > Käytössä.", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Tämä ominaisuus riippuu siitä, että edellä valittu puhelun tarjoaja on otettu käyttöön hallinta-asetuksista (Hallinta > Videoneuvottelu).", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Ominaisuus edellyttää, että Lähetä vierailijan navigointihistoria viestinä -asetus on käytössä.", "Feature_Limiting": "Ominaisuuden rajaaminen", "Features": "Ominaisuudet", @@ -2119,6 +2142,8 @@ "Federation_Matrix_bridge_url": "Siltaus-URL-osoite", "Federation_Matrix_bridge_localpart": "AppService-käyttäjän Localpart", "Federation_Matrix_registration_file": "Rekisteröintitiedosto", + "Federation_Matrix_registration_file_Alert": "Tärkeää: Jos otat käyttöön tilapäiset tapahtumat, palvelin vastaanottaa kaikkien käyttäjien kirjoittamisen tilan kaikista palvelimista, joihin olet yhteydessä.
Voit ottaa sen käyttöön päivittämällä rekisteröitymistiedostoosi (.yaml-tiedosto, jolla rekisteröit Rocket.Chatin kotipalvelimeesi) seuraavan:
de.sorunome.msc2409.push_ephemeral: true", + "Federation_Matrix_enable_typing_status": "Ota käyttöön käyttäjien kirjoittamisen tila", "Field": "Kenttä", "Field_removed": "Kenttä poistettu", "Field_required": "Kenttä pakollinen", @@ -2158,6 +2183,8 @@ "FileUpload_GoogleStorage_AccessId_Description": "Käyttöoikeustunnus on yleensä sähköpostimuodossa, esimerkiksi: \"example-test@example.iam.gserviceaccount.com\"", "FileUpload_GoogleStorage_Bucket": "Google-tallennussäilön nimi", "FileUpload_GoogleStorage_Bucket_Description": "Sen säilön nimi, johon tiedostot olisi ladattava.", + "FileUpload_GoogleStorage_ProjectId": "Projektin tunnus", + "FileUpload_GoogleStorage_ProjectId_Description": "Projektin tunnus Google Developer Consolessa", "FileUpload_GoogleStorage_Proxy_Avatars": "Välityspalvelimen avatarit", "FileUpload_GoogleStorage_Proxy_Avatars_Description": "Välityspalvelimen avatartiedostojen siirto palvelimen kautta käyttämättä resurssin URL-osoitetta suoraan", "FileUpload_GoogleStorage_Proxy_Uploads": "Välityspalvelinlataukset", @@ -2286,6 +2313,7 @@ "Global_purge_override_warning": "Yleinen säilytyskäytäntö on käytössä. Jos jätät asetuksen Ohita yleinen säilytyskäytäntö pois käytöstä, voit käyttää ainoastaan yleistä käytäntöä tiukempaa käytäntöä.", "Global_Search": "Yleinen haku", "Go_to_your_workspace": "Siirry työtilaasi", + "Google_Meet_Enterprise_only": "Google Meet (vain yritysversio)", "Google_Play": "Google Play", "Hold_Call": "Aseta puhelu pitoon", "Hold_Call_EE_only": "Aseta puhelu pitoon (vain yritysversio)", @@ -2400,7 +2428,7 @@ "Importer_HipChatEnterprise_Information": "Ladattavan tiedoston on oltava purettu tar.gz-tiedosto. Katso lisätietoja oppaista:", "Importer_import_cancelled": "Tuonti peruutettu.", "Importer_import_failed": "Virhe tuotaessa.", - "Importer_importing_channels": "Tuodaan kanavat.", + "Importer_importing_channels": "Tuodaan kanavia.", "Importer_importing_files": "Tuodaan tiedostoja.", "Importer_importing_messages": "Tuodaan viestejä.", "Importer_importing_started": "Aloitetaan tuontia.", @@ -2509,7 +2537,7 @@ "Integrations_for_all_channels": "Määrittämällä all_public_channels voit kuunnella kaikilla julkisilla kanavilla, määrittämällä all_private_groups voit kuunnella kaikissa yksityisissä ryhmissä ja määrittämällä all_direct_messages voit kuunnella kaikkia suoria viestejä.", "Integrations_Outgoing_Type_FileUploaded": "Tiedosto ladattu", "Integrations_Outgoing_Type_RoomArchived": "Arkistoitu huone Room", - "Integrations_Outgoing_Type_RoomCreated": "Huone Room luotu (julkinen ja yksityinen)", + "Integrations_Outgoing_Type_RoomCreated": "Huone luotu (julkinen ja yksityinen)", "Integrations_Outgoing_Type_RoomJoined": "Käyttäjä liittyi huoneeseen Room", "Integrations_Outgoing_Type_RoomLeft": "Käyttäjä poistui huoneesta Room", "Integrations_Outgoing_Type_SendMessage": "Viesti lähetetty", @@ -2589,6 +2617,7 @@ "italic": "Kursivoitu", "italics": "kursivoitu", "Items_per_page:": "Kohteita sivulla:", + "Jitsi_included_with_Community": "Jitsi, sisältyy yhteisöön", "Job_Title": "Työnimike", "Join": "Liity", "Join_with_password": "Liity salasanalla", @@ -2797,7 +2826,7 @@ "LDAP_Sync_User_Avatar": "Synkronoi käyttäjän avatar", "LDAP_Sync_User_Data_Roles": "Synkronoi LDAP-ryhmät", "LDAP_Sync_User_Data_Channels": "Automaattisesti synkronoi LDAP-ryhmät kanaville Channel", - "LDAP_Sync_User_Data_Channels_Admin": "Channel Admin", + "LDAP_Sync_User_Data_Channels_Admin": "Kanavan järjestelmänvalvoja", "LDAP_Sync_User_Data_Channels_Admin_Description": "Kun luodaan automaattisesti kanavia, joita ei ole olemassa synkronoinnin aikana, tästä käyttäjästä tulee automaattisesti kanavan järjestelmänvalvoja.", "LDAP_Sync_User_Data_Channels_BaseDN": "LDAP-ryhmän perus-DN", "LDAP_Sync_User_Data_Channels_Description": "Ottamalla tämän ominaisuuden käyttöön käyttäjät lisätään automaattisesti kanavalle heidän LDAP-ryhmänsä perusteella. Jos haluat myös poistaa käyttäjiä kanavalta, katso alta lisätietoja käyttäjien automaattisesta poistamisesta.", @@ -2860,7 +2889,7 @@ "line": "rivi", "link": "linkki", "Link_Preview": "Linkin esikatselu", - "List_of_Channels": "Kanavaluettelo Channel", + "List_of_Channels": "Kanavaluettelo", "List_of_departments_for_forward": "Luettelo osastoista, joilla on välitysoikeus (valinnainen)", "List_of_departments_for_forward_description": "Salli asettaa rajoitettu luettelo osastoista, jotka voivat vastaanottaa keskusteluja tältä osastolta", "List_of_departments_to_apply_this_business_hour": "Luettelo osastoista, joihin tätä aukioloaikaa sovelletaan", @@ -2917,7 +2946,7 @@ "Omnichannel_On_Hold_due_to_inactivity": "Keskustelu asetettiin pitoon automaattisesti, koska __guest__ ei vastannut __timeout__ sekuntiin", "Omnichannel_On_Hold_manually": "__user__ asetti keskustelun pitoon manuaalisesti", "Omnichannel_onHold_Chat": "Aseta keskustelu pitoon", - "Omnichannel_quick_actions": "Monikanavan pikatoiminnot", + "Omnichannel_quick_actions": "Omnichannelin pikatoiminnot", "Livechat_online": "Monikanava online-tilassa", "Omnichannel_placed_chat_on_hold": "Keskustelu pidossa: __comment__", "Livechat_Queue": "Monikanavajono", @@ -2934,8 +2963,11 @@ "Livechat_transcript_request_has_been_canceled": "Keskustelun tallennepyyntö on peruutettu.", "Livechat_transcript_sent": "Monikanavan tallenne lähetetty", "Livechat_transfer_return_to_the_queue": "__from__ palautti keskustelun jonoon", + "Livechat_transfer_return_to_the_queue_with_a_comment": "__from__ palautti keskustelun jonoon ja kommentoi: __comment__", + "Livechat_transfer_return_to_the_queue_auto_transfer_unanswered_chat": "__from__ palautti keskustelun jonoon, koska siihen ei vastattu __duration__ sekuntiin", "Livechat_transfer_to_agent": "__from__ siirsi keskustelun kohteeseen __to__", "Livechat_transfer_to_agent_with_a_comment": "__from__ siirsi keskustelun kohteeseen __to__ ja kommentoi: __comment__", + "Livechat_transfer_to_agent_auto_transfer_unanswered_chat": "__from__ siirsi keskustelun kohteeseen __to__, koska siihen ei vastattu __duration__ sekuntiin", "Livechat_transfer_to_department": "__from__ siirsi keskustelun osastolle __to__", "Livechat_transfer_to_department_with_a_comment": "__from__ siirsi keskustelun osastolle __to__ ja kommentoi: __comment__", "Livechat_transfer_failed_fallback": "Alkuperäisellä osastolla ( __from__ ) ei ole online-agentteja. Keskustelu on siirretty kohteeseen __to__", @@ -3062,13 +3094,13 @@ "manage-livechat-tags_description": "Oikeus hallita monikanavan tunnisteita", "manage-livechat-units": "Hallitse monikanavan yksiköitä", "manage-livechat-units_description": "Oikeus hallita monikanavan yksiköitä", - "manage-oauth-apps": "Hallitse Oauth-sovelluksia", - "manage-oauth-apps_description": "Käyttöoikeus Oauth-sovellusten hallintaan", + "manage-oauth-apps": "Hallinnoi Oauth-sovelluksia", + "manage-oauth-apps_description": "Oikeus hallita Oauth-sovelluksia", "manage-outgoing-integrations": "Hallitse lähteviä integraatioita", "manage-outgoing-integrations_description": "Oikeus hallita palvelimen lähteviä integraatioita", "manage-own-incoming-integrations": "Hallitse omia saapuvia integraatioita", "manage-own-incoming-integrations_description": "Oikeus sallia käyttäjien luoda ja muokata omia saapuvia integraatioitaan tai webhookseja", - "manage-own-integrations": "Hallitse omia integraatioita", + "manage-own-integrations": "Hallinnoi omia integraatioita", "manage-own-integrations_description": "Oikeus sallia käyttäjien luoda ja muokata omia integraatioita tai webhookseja", "manage-own-outgoing-integrations": "Hallitse omia lähteviä integraatioita", "manage-own-outgoing-integrations_description": "Lupa sallia käyttäjien luoda ja muokata omia lähteviä integraatioita tai webhookseja", @@ -3085,8 +3117,8 @@ "manage-voip-contact-center-settings_description": "Oikeus hallita voip-yhteyskeskuksen asetuksia", "Manage_Omnichannel": "Hallitse monikanavaa", "Manage_workspace": "Hallitse työtilaa", - "Manager_added": "Manageri lisätty", - "Manager_removed": "Manageri poistettu", + "Manager_added": "Päällikkö lisätty", + "Manager_removed": "Päällikkö poistettu", "Managers": "Päälliköt", "manage-chatpal": "Hallitse Chatpalia", "Management_Server": "Asterisk Manager Interface (AMI)", @@ -3094,25 +3126,25 @@ "Managing_integrations": "Integraatioiden hallinta", "Manual_Selection": "Manuaalinen valinta", "Manufacturing": "Tuotanto", - "MapView_Enabled": "Ota karttakuva käyttöön", + "MapView_Enabled": "Ota käyttöön karttanäkymä", "MapView_Enabled_Description": "Karttanäkymän käyttöönotto näyttää chat-syötekentän vasemmalla puolella painikkeen, jolla voi jakaa sijaintia.", - "MapView_GMapsAPIKey": "Google Static Maps API avain", - "MapView_GMapsAPIKey_Description": "Tämä saadaan Google Developers Consolesta, ilmaiseksi.", + "MapView_GMapsAPIKey": "Google Static Mapsin API-avain", + "MapView_GMapsAPIKey_Description": "Tämä saadaan Google Developers Consolesta maksutta.", "Mark_all_as_read": "Merkitse kaikki viestit (kaikilla kanavilla) luetuiksi", "Mark_as_read": "Merkitse luetuksi", "Mark_as_unread": "Merkitse lukemattomaksi", "Mark_read": "Merkitse luetuksi", "Mark_unread": "Merkitse lukemattomaksi", - "Markdown_Headers": "Markdown-otsikot", - "Markdown_Marked_Breaks": "Ota merkityt katkokset käyttöön", - "Markdown_Marked_GFM": "Ota merkitty GFM käyttöön", + "Markdown_Headers": "Salli Markdown-otsikot viesteissä", + "Markdown_Marked_Breaks": "Ota käyttöön merkityt katkokset", + "Markdown_Marked_GFM": "Ota käyttöön merkitty GFM", "Markdown_Marked_Pedantic": "Ota käyttöön Marked Pedantic", "Markdown_Marked_SmartLists": "Ota merkityt älykkäät luettelot käyttöön", "Markdown_Marked_Smartypants": "Ota käyttöön merkitty Smartypants", "Markdown_Marked_Tables": "Ota merkityt taulukot käyttöön", "Markdown_Parser": "Markdown-jäsennin", "Markdown_SupportSchemesForLink": "Linkin Markdown-tukimallit", - "Markdown_SupportSchemesForLink_Description": "Pilkuilla erotettu lista sallittuista schemeistä", + "Markdown_SupportSchemesForLink_Description": "Pilkulla erotettu luettelo sallittuista malleista", "Marketplace": "Kauppapaikka", "Marketplace_app_last_updated": "Viimeksi päivitetty __lastUpdated__", "Marketplace_view_marketplace": "Näytä kauppapaikka", @@ -3144,7 +3176,7 @@ "Medium": "Keskikokoinen", "Members": "Jäsenet", "Members_List": "Jäsenluettelo", - "mention-all": "Mainitse kaikki", + "mention-all": "Kaikki-maininta", "mention-all_description": "Käyttöoikeus käyttää @all-mainintaa", "mention-here": "Mainitse täällä", "mention-here_description": "Lupa käyttää @here-mainintaa", @@ -3183,13 +3215,13 @@ "Message_Attachments_Strip_Exif": "Poista EXIF-metatiedot tuetuista tiedostoista", "Message_Attachments_Strip_ExifDescription": "Poistaa EXIF-metatiedot kuvatiedostoista (jpeg, tiff jne.). Tämä asetus ei ole takautuva, joten EXIF-tiedot säilyvät tiedostoissa, jotka on ladattu asetuksen ollessa pois käytöstä", "Message_Audio": "Ääniviesti", - "Message_Audio_bitRate": "Ääniviestin bittinopeus", + "Message_Audio_bitRate": "Ääniviestin siirtonopeus", "Message_AudioRecorderEnabled": "Äänentallennin käytössä", "Message_AudioRecorderEnabled_Description": "Edellyttää, että 'audio/mp3'-tiedostot ovat hyväksytty mediatyyppi 'Tiedoston lataus'-asetuksissa.", "Message_auditing": "Viestien tarkastaminen", "Message_auditing_log": "Viestien tarkastusloki", "Message_BadWordsFilterList": "Lisää sopimattomia sanoja kiellettyjen luetteloon", - "Message_BadWordsFilterListDescription": "Lisää pilkuilla eroteltu lista sopimattomista sanoista suodatettaviksi", + "Message_BadWordsFilterListDescription": "Lisää pilkulla eroteltu luettelo sopimattomista sanoista suodatettavaksi", "Message_BadWordsWhitelist": "Poista sanoja mustalta listalta", "Message_BadWordsWhitelistDescription": "Lisää pilkulla erotettu luettelo suodattimesta poistettavista sanoista", "Message_Characther_Limit": "Viestin merkkirajoitus", @@ -3197,16 +3229,16 @@ "Message_Code_highlight_Description": "Pilkulla erotettu luettelo kielistä (kaikki tuetut kielet osoitteessa https://github.com/highlightjs/highlight.js/tree/9.18.5#supported-languages), joita käytetään koodilohkojen korostamiseen", "message_counter": "__counter__ viesti", "message_counter_plural": "__counter__ viestit", - "Message_DateFormat": "Päivämäärämuoto", + "Message_DateFormat": "Päivämäärän muoto", "Message_DateFormat_Description": "Katso myös: Moment.js", "Message_deleting_blocked": "Tätä viestiä ei voi enää poistaa", "Message_editing": "Viestin muokkaus", "Message_ErasureType": "Viestin poistotyyppi", "Message_ErasureType_Delete": "Poista kaikki viestit", "Message_ErasureType_Description": "Määritä, mitä tehdään niiden käyttäjien viesteille, jotka poistavat tilinsä.\n\n**Säilytä viestit ja käyttäjätunnus:** Käyttäjän viesti- ja tiedostohistoria poistetaan suorista viesteistä ja säilytetään muissa huoneissa.\n\n**Poista kaikki viestit:** Kaikki käyttäjän viestit ja tiedostot poistetaan tietokannasta, eikä käyttäjää voi enää löytää.\n\n**Poista linkki käyttäjän ja viestien välillä:** Tämä vaihtoehto määrittää kaikki käyttäjän viestit ja tiedostot chatsovelluksen-botille ja Suorat viestit poistetaan.", - "Message_ErasureType_Keep": "Pidä viestit ja käyttäjänimi", + "Message_ErasureType_Keep": "Säilytä viestit ja käyttäjätunnus", "Message_ErasureType_Unlink": "Poista käyttäjän ja viestien välinen yhteys", - "Message_GlobalSearch": "Globaali haku", + "Message_GlobalSearch": "Yleinen haku", "Message_GroupingPeriod": "Ryhmittelyaika (sekunteina)", "Message_GroupingPeriodDescription": "Viestit ryhmitellään edellisen viestin kanssa, jos molemmat viestit ovat samalta käyttäjältä ja viestien välinen aika on pienempi kuin määritetty aika.", "Message_has_been_edited": "Viestiä on muokattu", @@ -3220,19 +3252,19 @@ "Message_HideType_au": "Piilota \"Käyttäjä lisätty\" -viestit", "Message_HideType_added_user_to_team": "Piilota \"Käyttäjä lisätty tiimiin\" -viestit", "Message_HideType_mute_unmute": "Piilota \"Käyttäjä mykistetty / mykistys poistettu\"-viestit", - "Message_HideType_r": "Piilota \"Huoneen Room Nimi muuttui\"-viestit", + "Message_HideType_r": "Piilota \"Huoneen nimi muuttui\" -viestit", "Message_HideType_rm": "Piilota \"Viesti poistettu\"-viestit", - "Message_HideType_room_allowed_reacting": "Piilota \"Huone Room reaktiot sallittu\"-viestit", - "Message_HideType_room_archived": "Piilota \"Huone Room Arkistoitu\"-viestit", - "Message_HideType_room_changed_avatar": "Piilota \"Huone Room avatar vaihdettu\"-viestit", - "Message_HideType_room_changed_privacy": "Piilota \"Huoneen Room tyyppi vaihdettu\"-viestit", - "Message_HideType_room_changed_topic": "Piilota \"Huoneen Room aihe vaihdettu\"-viestit", - "Message_HideType_room_disallowed_reacting": "Piilota \"Huoneen Room reagointi kielletty\"-viestit", - "Message_HideType_room_enabled_encryption": "Piilota \"Huoneen Room salaus käytössä\"-viestit", - "Message_HideType_room_disabled_encryption": "Piilota \"Huoneen Room salaus pois käytöstä\"-viestit", - "Message_HideType_room_set_read_only": "Piilota \"Huone Room asetettu 'Vain Luku'-tilaan\"-viestit", - "Message_HideType_room_removed_read_only": "Piilota \"Huoneen Room kirjoitusoikeus lisätty\"-viestit", - "Message_HideType_room_unarchived": "Piilota \"Huone Room palautettu arkistosta\"-viestit", + "Message_HideType_room_allowed_reacting": "Piilota \"Huone salli reagoinnin\" -viestit", + "Message_HideType_room_archived": "Piilota \"Huone arkistoitu\" -viestit", + "Message_HideType_room_changed_avatar": "Piilota \"Huoneen avatar vaihdettu\" -viestit", + "Message_HideType_room_changed_privacy": "Piilota \"Huoneen tyyppi vaihdettu\" -viestit", + "Message_HideType_room_changed_topic": "Piilota \"Huoneen aihe vaihdettu\" -viestit", + "Message_HideType_room_disallowed_reacting": "Piilota \"Huoneessa reagointi kielletty\" -viestit", + "Message_HideType_room_enabled_encryption": "Piilota \"Huoneen salaus käytössä\" -viestit", + "Message_HideType_room_disabled_encryption": "Piilota \"Huoneen salaus poissa käytöstä\" -viestit", + "Message_HideType_room_set_read_only": "Piilota \"Huone asetettu vain luku -tilaan\" -viestit", + "Message_HideType_room_removed_read_only": "Piilota \"Huoneen kirjoitusoikeus lisätty\" -viestit", + "Message_HideType_room_unarchived": "Piilota \"Huone palautettu arkistosta\" -viestit", "Message_HideType_ru": "Piilota \"Käyttäjä poistettu\"-viestit", "Message_HideType_removed_user_from_team": "Piilota \"Käyttäjä poistettu tiimistä\"-viestit", "Message_HideType_subscription_role_added": "Piilota \"Rooli on asetettu\"-viestit", @@ -3242,19 +3274,21 @@ "Message_HideType_ul": "Piilota \"Käyttäjän poistui\"-viestit", "Message_HideType_ult": "Piilota \"Käyttäjä poistui tiimistä\"-viestit", "Message_HideType_user_added_room_to_team": "Piilota \"Käyttäjä lisäsi huoneen Room Tiimiin\"-viestit", - "Message_HideType_user_converted_to_channel": "Piilota \"Käyttäjä muutti Tiimin Kanavaksi Channel\"-viestit", + "Message_HideType_user_converted_to_channel": "Piilota \"Käyttäjä muutti tiimin kanavaksi\" -viestit", "Message_HideType_user_converted_to_team": "Piilota \"Käyttäjä muutti Kanavan Tiimiksi\"-viestit", "Message_HideType_user_deleted_room_from_team": "Piilota \"Käyttäjä poisti huoneen tiimistä -viestit", "Message_HideType_user_removed_room_from_team": "Piilota \"Käyttäjä poisti huoneen tiimistä\"-viestit", + "Message_HideType_changed_description": "Piilota Huoneen kuvaukseksi vaihdettiin -ilmoitukset", + "Message_HideType_changed_announcement": "Piilota Huoneen ilmoitukseksi vaihdettiin -ilmoitukset", "Message_HideType_ut": "Piilota \"Käyttäjä liittyi keskusteluun\" -viestit", "Message_HideType_wm": "Piilota \"Tervetuloa\"-viestit", "Message_Id": "Viestin ID-tunnus", "Message_Ignored": "Tämä viesti jätettiin huomiotta", "message-impersonate": "Esiinny toisena käyttäjänä", "message-impersonate_description": "Lupa esiintyä muina käyttäjinä viestin aliasta käyttäen", - "Message_info": "Viestiin liittyvät tiedot", + "Message_info": "Viestin tiedot", "Message_KeepHistory": "Säilytä viestikohtainen muokkaushistoria", - "Message_MaxAll": "Suurin kanavan Channel koko Kaikille-viesteille", + "Message_MaxAll": "Kanavan enimmäiskoko KAIKILLE viesteille", "Message_MaxAllowedSize": "Viestikohtainen enimmäismerkkimäärä", "Message_pinning": "Viestin kiinnitys", "message_pruned": "viesti karsittu", @@ -3268,17 +3302,20 @@ "Message_ShowDeletedStatus": "Näytä Poistettu-tila", "Message_ShowEditedStatus": "Näytä Muokattu-tila", "Message_ShowFormattingTips": "Näytä muotoiluvihjeet", + "Message_Formatting_Toolbox": "Muotoilutyökalut", + "Message_composer_toolbox_primary_actions": "Kirjoituksen ensisijaiset toiminnot", + "Message_composer_toolbox_secondary_actions": "Kirjoituksen toissijaiset toiminnot", "Message_starring": "Viestin merkitseminen tähdellä", "Message_Time": "Viestin aika", "Message_TimeAndDateFormat": "Aika- ja päivämäärämuoto", - "Message_TimeAndDateFormat_Description": "Katso myös: Moment.js", + "Message_TimeAndDateFormat_Description": "Katso myös: Moment.js", "Message_TimeFormat": "Aikamuoto", "Message_TimeFormat_Description": "Katso myös: Moment.js", "Message_too_long": "Viesti liian pitkä", "Message_UserId": "Käyttäjätunnus", "Message_VideoRecorderEnabled": "Videotallennus käytössä", "Message_VideoRecorderEnabledDescription": "Vaatii 'video/webm'-tiedostot hyväksytyksi mediatyypiksi 'Tiedoston lataus'-asetuksissa.", - "Message_view_mode_info": "Tämä muuttaa sitä, kuinka paljon tilaa viestit vievät ruudulla.", + "Message_view_mode_info": "Tämä muuttaa viestien viemää tilaa näytössä.", "MessageBox_view_mode": "MessageBox-näkymätila", "messages": "viestit", "Messages": "Viestit", @@ -3289,7 +3326,7 @@ "Meta_Description": "Määritä mukautetut Meta-ominaisuudet.", "Meta_custom": "Mukautetut metatunnisteet", "Meta_fb_app_id": "Facebook-sovellustunnus", - "Meta_google-site-verification": "Google-sivuston vahvistustunnus", + "Meta_google-site-verification": "Google-sivuston vahvistus", "Meta_language": "Kieli", "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robotit", @@ -3311,6 +3348,7 @@ "Minimum_balance": "Vähimmäistasapaino", "minute": "minuutti", "minutes": "minuuttia", + "Missing_configuration": "Puuttuva määritys", "Mobex_sms_gateway_address": "Mobex SMS Gateway:n osoite", "Mobex_sms_gateway_address_desc": "Mobex-palvelun IP-osoite tai isäntä ja määritetty portti. Esim. `http://192.168.1.1:1401` tai `https://www.esimerkki.com:1401`", "Mobex_sms_gateway_from_number": "Lähettäjä", @@ -3326,7 +3364,7 @@ "Mobile_Description": "Määrittele käyttäytymismallit, joiden avulla voit muodostaa yhteyden työtilaasi mobiililaitteista.", "mobile-upload-file": "Salli tiedostojen lataaminen mobiililaitteilla", "mobile-upload-file_description": "Lupa sallia tiedostojen lataaminen mobiililaitteilla", - "Mobile_Push_Notifications_Default_Alert": "Mobiiliviestien oletushälytys", + "Mobile_Push_Notifications_Default_Alert": "Push-ilmoitusten oletushälytys", "Monday": "Maanantai", "Mongo_storageEngine": "Mongo Storage Engine", "Mongo_version": "Mongo-versio", @@ -3363,7 +3401,7 @@ "Mute_user": "Mykistä käyttäjä", "Mute_microphone": "Mykistä mikrofoni", "mute-user": "Mykistä käyttäjä", - "mute-user_description": "Lupaa mykistää muita käyttäjiä samalla kanavalla", + "mute-user_description": "Oikeus mykistää muita käyttäjiä samalla kanavalla", "Muted": "Mykistetty", "My Data": "Omat tiedot", "My_Account": "Oma tili", @@ -3375,7 +3413,7 @@ "Name_of_agent": "Agentin nimi", "Name_optional": "Nimi (valinnainen)", "Name_Placeholder": "Kirjoita nimesi...", - "Navigation_History": "Selaushistoria", + "Navigation_History": "Siirtymishistoria", "Next": "Seuraava", "Never": "Ei koskaan", "New": "Uusi", @@ -3404,17 +3442,17 @@ "New_messages": "Uusia viestejä", "New_OTR_Chat": "Uusi epävirallinen keskustelu", "New_password": "Uusi salasana", - "New_Password_Placeholder": "Anna uusi salasana ...", + "New_Password_Placeholder": "Kirjoita uusi salasana ...", "New_Priority": "Uusi prioriteetti", "New_role": "Uusi rooli", "New_Room_Notification": "Uusi huone Room-ilmoitus", "New_Tag": "Uusi tunniste", - "New_Trigger": "Uusi käynnistin", + "New_Trigger": "Uusi laukaisin", "New_Unit": "Uusi yksikkö", "New_users": "Uudet käyttäjät", "New_version_available_(s)": "Uusi versio saatavilla (%s)", "New_videocall_request": "Uusi videopuhelupyyntö", - "New_visitor_navigation": "Uusi navigointi: __history__", + "New_visitor_navigation": "Uusi siirtyminen: __history__", "Newer_than": "Uudempi kuin", "Newer_than_may_not_exceed_Older_than": "\"Uudempi kuin\" ei saa ylittää \"Vanhempi kuin\"", "Nickname": "Lempinimi", @@ -3429,7 +3467,7 @@ "No_Canned_Responses_Yet": "Ei esivalmistettuja vastauksia vielä", "No_Canned_Responses_Yet-description": "Käytä esivalmistettuja vastauksia, jotta voit antaa nopeita ja johdonmukaisia vastauksia usein kysyttyihin kysymyksiin.", "No_channels_in_team": "Ei kanavia Channel tässä tiimissä", - "No_channels_yet": "Et ole vielä millään kanavalla.", + "No_channels_yet": "Et ole vielä millään kanavalla", "No_data_found": "Tietoja ei löytynyt", "No_direct_messages_yet": "Ei suoria viestejä.", "No_Discussions_found": "Keskusteluja ei löytynyt", @@ -3441,9 +3479,9 @@ "No_groups_yet": "Sinulla ei ole vielä yksityisiä ryhmiä.", "No_history": "Ei historiaa", "No_installed_app_matches": "Mikään asennettu sovellus ei vastaa", - "No_integration_found": "Tarjottua tunnusta ei löydy integraatiosta.", + "No_integration_found": "Annetulla tunnuksella ei löydy integraatiota.", "No_Limit": "Ei rajoitusta", - "No_livechats": "Sinulla ei ole livechatteja.", + "No_livechats": "Sinulla ei ole suoria keskusteluja.", "No_marketplace_matches_for": "Ei markkinapaikan osumia termille", "No_members_found": "Jäseniä ei löytynyt", "No_mentions_found": "Mainintoja ei löydy", @@ -3461,11 +3499,11 @@ "No_Threads": "Viestiketjuja ei löydy", "no-videoconf-provider-app": "**Konferenssipuhelu ei ole käytettävissä**: Työtilan ylläpitäjä voi asentaa konferenssipuhelusovelluksia chatsovelluksen-markkinapaikalta.", "Nobody_available": "Ketään ei ole vapaana", - "Node_version": "Node-versio", + "Node_version": "Solmun versio", "None": "Ei mitään", "Nonprofit": "Voittoa tavoittelematon", "Normal": "Normaali", - "Not_authorized": "Ei sallittu", + "Not_authorized": "Ei valtuutettu", "Not_Available": "Ei saatavilla", "Not_enough_data": "Ei riittävästi tietoa", "Not_following": "Ei seuraa", @@ -3485,8 +3523,8 @@ "Notification_RequireInteraction_Description": "Toimii vain Chrome-selaimen versioilla 50 tai uudempi. Hyödyntää parametria requireInteraction näyttääkseen työpöytäilmoituksen toistaiseksi, kunnes käyttäjä onvuorovaikutuksessa sen kanssa.", "Notifications": "Ilmoitukset", "Notifications_Max_Room_Members": "Maksimi huoneen Room jäsenet ennen kaikkien viesti-ilmoitusten poistamista käytöstä", - "Notifications_Max_Room_Members_Description": "Maksimi jäsenmäärä huoneessa, kun kaikkien viestien ilmoitukset poistetaan käytöstä. Käyttäjät voivat silti muuttaa huoneen asetusta kohti vastaanottaa kaikki ilmoitukset yksilöllisesti. (0 poista käytöstä)", - "Notifications_Muted_Description": "Jos päätät mykistää kaiken, et näe huoneen korostusta luettelossa, kun uusia viestejä on, lukuun ottamatta mainintoja. Mykistysilmoitukset ohittavat ilmoitusten asetukset.", + "Notifications_Max_Room_Members_Description": "Huoneen jäsenten enimmäismäärä, kun kaikkien viestien ilmoitukset poistetaan käytöstä. Muuttamalla huonekohtaista asetusta käyttäjät voivat edelleen saada kaikki ilmoitukset erikseen. (poista käytöstä: 0)", + "Notifications_Muted_Description": "Jos mykistät kaiken, et näe huonetta korostettuna luettelossa, uusien viestien yhteydessä, paitsi mainintojen kohdalla. Ilmoitusten mykistäminen ohittaa ilmoitusasetukset.", "Notifications_Preferences": "Ilmoitusasetukset", "Notifications_Sound_Volume": "Ilmoitusten äänenvoimakkuus", "Notify_active_in_this_room": "Ilmoita tämän huoneen aktiivisille käyttäjille", @@ -3511,10 +3549,10 @@ "OAuth": "OAuth", "OAuth_Description": "Määritä muitakin todennusmenetelmiä kuin vain käyttäjätunnus ja salasana.", "OAuth Apps": "OAuth sovellukset", - "OAuth_Application": "OAuth sovellus", - "OAuth_Applications": "OAuth sovellukset", + "OAuth_Application": "OAuth-sovellus", + "OAuth_Applications": "OAuth-sovellukset", "Objects": "Kohteet", - "Off": "Pois", + "Off": "Ei käytössä", "Off_the_record_conversation": "Epävirallinen keskustelu", "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "Epävirallista keskustelua ei ole saatavilla käyttämälläsi selaimella tai laitteella.", "Office_Hours": "Toimiston aukioloajat", @@ -3522,13 +3560,13 @@ "Office_hours_updated": "Toimiston aukioloajat päivitetty", "offline": "offline", "Offline": "Offline", - "Offline_DM_Email": "__user__ lähetti sinulle yksityisviestin", - "Offline_Email_Subject_Description": "Voit käyttää seuraavia placeholdereita:
  • [Site_Name], [Site_URL], [User] ja [ [Room] sovelluksen nimelle, URL-osoitteelle, käyttäjänimelle ja Room.
", + "Offline_DM_Email": "Suoran viestin sähköpostin aihe", + "Offline_Email_Subject_Description": "Voit käyttää seuraavia paikkamerkkejä:
  • [Site_Name], [Site_URL], [User] ja [Room] sovelluksen nimen, URL-osoitteen, käyttäjätunnuksen ja huoneen nimen paikalla.
", "Offline_form": "Offline-lomake", - "Offline_form_unavailable_message": "Offline-lomakkeen \"poissa\" viesti", + "Offline_form_unavailable_message": "Offline-lomakkeen Poissa-ilmoitus", "Offline_Link_Message": "MENE VIESTIIN", "Offline_Mention_All_Email": "Mainitse kaikki -Sähköpostin aihe", - "Offline_Mention_Email": "__user__ mainitsi sinut huoneessa #__room__", + "Offline_Mention_Email": "Mainintasähköpostiviestin aihe", "Offline_message": "Offline-viesti", "Offline_Message": "Offline-tilan viesti", "Offline_Message_Use_DeepLink": "Käytä syvälinkki-URL-muotoa", @@ -3538,7 +3576,7 @@ "Ok": "Ok", "Old Colors": "Vanhat värit", "Old Colors (minor)": "Vanhat värit (vähäinen)", - "Older_than": "Vanhempia kuin", + "Older_than": "Vanhempi kuin", "Omnichannel": "Omnichannel", "Omnichannel_Description": "Ota käyttöön Omnichannel, jotta voit viestiä asiakkaille yhdestä paikasta riippumatta siitä, miten he ottavat sinuun yhteyttä.", "Omnichannel_Directory": "Omnichannel-hakemisto", @@ -3553,7 +3591,7 @@ "Omnichannel_External_Frame_Encryption_JWK": "Salausavain (JWK)", "Omnichannel_External_Frame_Encryption_JWK_Description": "Jos se annetaan, se salaa käyttäjän tokenin annetulla avaimella, ja ulkoisen järjestelmän on purettava salaus saadakseen tokenin käyttöönsä", "Omnichannel_External_Frame_URL": "Ulkoisen kehyksen URL-osoite", - "On": "Päällä", + "On": "Käytössä", "on-hold-livechat-room": "Omnichannelin pidon huone Room", "on-hold-livechat-room_description": "Lupa asettaa Omnichannel-huone pitoon", "on-hold-others-livechat-room": "Aseta muident Omnichannel-huone Room pitoon", @@ -3565,14 +3603,14 @@ "Online": "Online", "Only_authorized_users_can_write_new_messages": "Vain valtuutetut käyttäjät voivat kirjoittaa uusia viestejä", "Only_authorized_users_can_react_to_messages": "Vain sallitut käyttäjät voivat reagoida viesteihin", - "Only_from_users": "Vain leikata sisältöä näistä käyttäjistä (jätä tyhjäksi karsia kaikkien sisältöä)", + "Only_from_users": "Karsi vain näiden käyttäjien sisältöä (karsi kaikkien sisältöä jättämällä tyhjäksi)", "Only_Members_Selected_Department_Can_View_Channel": "Vain valitun osaston jäsenet voivat katsella tämän kanavan keskusteluja", "Only_On_Desktop": "Työpöytätila (lähettää vain enterillä työpöydällä)", "Only_works_with_chrome_version_greater_50": "Toimii vain Chrome-selaimen versioilla 50 tai uudempi", "Only_you_can_see_this_message": "Vain sinä näet tämän viestin", "Only_invited_users_can_acess_this_channel": "Vain kutsutut käyttäjät voivat käyttää tätä kanavaa Channel", "Only_available_on_Enterprise_learn_more__URL": "Saatavilla vain Enterprisella. [Lue lisää.](__URL__)", - "Oops_page_not_found": "Hups, sivua ei löytynyt", + "Oops_page_not_found": "Oho, sivua ei löydy", "Oops!": "Oho", "Open": "Avaa", "Open_call": "Vastaa puheluun", @@ -3586,8 +3624,9 @@ "Open_Livechats": "Keskustelut käynnissä", "Open_menu": "Open_menu", "Open_settings": "Avaa asetukset", + "Open-source_conference_call_solution": "Avoimen lähdekoodin neuvottelupuheluratkaisu.", "Open_thread": "Avaa säie", - "Open_your_authentication_app_and_enter_the_code": "Avaa autentikointisovellus ja anna koodi. Voit myös käyttää yhtä varakoodeista.", + "Open_your_authentication_app_and_enter_the_code": "Avaa todennussovellus ja anna koodi. Voit myös käyttää varakoodia.", "Opened": "Avattu", "Opened_in_a_new_window": "Avattu uudessa ikkunassa.", "Opens_a_channel_group_or_direct_message": "Avaa kanavan, ryhmän tai suoran viestin", @@ -3610,10 +3649,10 @@ "OS_Loadavg": "Käyttöjärjestelmän keskimääräinen kuorma", "OS_Platform": "Käyttöjärjestelmän alusta", "OS_Release": "Käyttöjärjestelmäjulkaisu", - "OS_Totalmem": "OS Muisti yhteensä", + "OS_Totalmem": "Käyttöjärjestelmän kokonaismuisti", "OS_Type": "Käyttöjärjestelmätyyppi", - "OS_Uptime": "OS Päälläoloaika", - "Other": "Muut", + "OS_Uptime": "Käyttöjärjestelmän toiminta-aika", + "Other": "Muu", "others": "muuta", "Others": "Muut", "OTR": "Epävirallinen keskustelu", @@ -3642,7 +3681,7 @@ "Page_title": "Sivun otsikko", "Page_URL": "Sivun URL", "Pages": "Sivut", - "Parent_channel_doesnt_exist": "Kanava Channel ei ole olemassa.", + "Parent_channel_doesnt_exist": "Kanavaa ei ole olemassa.", "Participants": "Osallistujat", "Password": "Salasana", "Password_Change_Disabled": "Rocket.Chatin järjestelmänvalvoja on poistanut käytöstä salasanan vaihtamisen", @@ -3659,6 +3698,7 @@ "Past_Chats": "Aiemmat keskustelut", "Paste_here": "Liitä tähän...", "Paste": "Liitä", + "Pause": "Keskeytä", "Paste_error": "Virhe leikepöydältä luettaessa", "Paid_Apps": "Maksulliset sovellukset", "Payload": "Sisältö", @@ -3668,6 +3708,7 @@ "Permalink": "Pysyvä linkki", "Permissions": "Oikeudet", "Personal_Access_Tokens": "Henkilökohtaiset pääsykoodit", + "Pexip_Enterprise_only": "Pexip (vain yritysversio)", "Phone": "Puhelin", "Phone_call": "Puhelu", "Phone_Number": "Puhelinnumero", @@ -3685,43 +3726,43 @@ "Pinned_messages_unavailable_for_federation": "Kiinnitetyt viestit eivät ole käytettävissä Federoiduissa huoneissa.", "pinning-not-allowed": "Kiinnitys ei ole sallittua", "PiwikAdditionalTrackers": "Lisää Piwik-sivustoja", - "PiwikAdditionalTrackers_Description": "Anna Piwik-sivuston URL-osoitteet ja SiteID-sivut seuraavaan muotoon, jos et pysty seuraamaan samoja tietoja eri verkkosivustoihin: [{\"trackerURL\": \"https: //my.piwik.domain2/\", \"siteId\": 42} {\"trackerURL\": \"https: //my.piwik.domain3/\", \"siteId\": 15}]", - "PiwikAnalytics_cookieDomain": "Kaikki aliverkkotunnukset", - "PiwikAnalytics_cookieDomain_Description": "Seuraa kävijöitä kaikilla aliverkkotunnuksilla", + "PiwikAdditionalTrackers_Description": "Anna muut Piwik-sivustojen URL-osoitteet ja sivustotunnukset seuraavassa muodossa, jos haluat seurata samoja tietoja esi sivustoissa: [ { \"trackerURL\" : \"https://my.piwik.domain2/\", \"siteId\" : 42 }, { \"trackerURL\" : \"https://my.piwik.domain3/\", \"siteId\" : 15 } ]", + "PiwikAnalytics_cookieDomain": "Kaikki alitoimialueet", + "PiwikAnalytics_cookieDomain_Description": "Seuraa vierailijoita kaikilla alitoimialueilla", "PiwikAnalytics_domains": "Piilota lähtevät linkit", "PiwikAnalytics_domains_Description": "Piilota \"Outlinks\"-raportissa tunnettujen alias-URL-osoitteiden klikkaukset. Lisää yksi verkkotunnus per rivi äläkä käytä erottimia.", "PiwikAnalytics_prependDomain": "Lisää verkkotunnus", "PiwikAnalytics_prependDomain_Description": "Sivuston verkkotunnuksen lisääminen sivun otsikkoon seurannan aikana", "PiwikAnalytics_siteId_Description": "Sivustotunnus, josta tämä sivusto tunnistetaan. Esimerkki: 17", - "PiwikAnalytics_url_Description": "URL, jossa Piwik sijaitsee. Muista sisällyttää vinoviiva(/) URLin loppuun. Esimerkki: //piwik.rocket.chat/", - "Placeholder_for_email_or_username_login_field": "Kirjautumisen sähköposti- tai käyttäjätunnuskentän paikkamerkki", + "PiwikAnalytics_url_Description": "URL-osoite, jossa Piwik sijaitsee. Muista sisällyttää URL-osoitteen lopussa oleva vinoviiva. Esimerkki: //piwik.rocket.chat/", + "Placeholder_for_email_or_username_login_field": "Sähköpostin tai käyttäjätunnuksen kirjautumiskentän paikkamerkki", "Placeholder_for_password_login_confirm_field": "Vahvista salasanan kirjautumiskentän paikanhaltija", "Placeholder_for_password_login_field": "Kirjautumisen salasanakentän paikkamerkki", "Platform_Windows": "Windows", "Platform_Linux": "Linux", "Platform_Mac": "Mac", - "Please_add_a_comment": "Ole hyvä, lisää kommentti", + "Please_add_a_comment": "Lisää kommentti", "Please_add_a_comment_to_close_the_room": "Sulje huone lisäämällä kommentti", "Please_answer_survey": "Vastaa lyhyeen kyselyyn tästä keskustelusta", - "Please_enter_usernames": "Syötä käyttäjätunnukset..", - "please_enter_valid_domain": "Anna kelvollinen verkkotunnus", - "Please_enter_value_for_url": "Anna avatarisi URL", + "Please_enter_usernames": "Anna käyttäjätunnukset..", + "please_enter_valid_domain": "Anna kelvollinen toimialue", + "Please_enter_value_for_url": "Anna avatarisi URL-osoite.", "Please_enter_your_new_password_below": "Anna uusi salasana alla:", - "Please_enter_your_password": "Syötä salasanasi", - "Please_fill_a_label": "Anna tunniste", + "Please_enter_your_password": "Anna salasanasi", + "Please_fill_a_label": "Kirjoita merkintä", "Please_fill_a_name": "Anna nimi", "Please_fill_a_token_name": "Täytä kelvollinen tunnuksen nimi", "Please_fill_a_username": "Anna käyttäjätunnus", - "Please_fill_all_the_information": "Täytä kaikki tiedot", + "Please_fill_all_the_information": "Anna kaikki tiedot", "Please_fill_an_email": "Täytä sähköpostiosoite", "Please_fill_name_and_email": "Anna nimi ja sähköpostiosoite", - "Please_go_to_the_Administration_page_then_Livechat_Facebook": "Siirry Hallinta-sivulle ja sitten Livechat> Facebook", + "Please_go_to_the_Administration_page_then_Livechat_Facebook": "Valitse Hallinta-sivulla Omnichannel > Facebook", "Please_select_an_user": "Valitse käyttäjä", "Please_select_enabled_yes_or_no": "Valitse Käytössä-vaihtoehto", "Please_select_visibility": "Valitse näkyvyys", "Please_wait": "Odota", - "Please_wait_activation": "Ole hyvä ja odota, tämä voi kestää jonkin aikaa.", - "Please_wait_while_OTR_is_being_established": "Odota, kun epävirallisen keskustelun yhteyttä muodostetaan", + "Please_wait_activation": "Odota, tämä voi kestää jonkin aikaa.", + "Please_wait_while_OTR_is_being_established": "Odota, kun epävirallista keskustelua aloitetaan", "Please_wait_while_your_account_is_being_deleted": "Odota, kun tilisi poistetaan...", "Please_wait_while_your_profile_is_being_saved": "Odota, kun profiilisi tallennetaan...", "Policies": "Käytännöt", @@ -3729,7 +3770,7 @@ "Port": "Portti", "Post_as": "Julkaise käyttäjänä", "Post_to": "Postaa", - "Post_to_Channel": "Postaa kanavalle", + "Post_to_Channel": "Julkaise kanavalla", "Post_to_s_as_s": "Julkaise kohteessa %s käyttäjänä %s", "post-readonly": "Postaa ReadOnly", "post-readonly_description": "Lupa lähettää viesti kanavalle, jolla on vain lukuoikeus", @@ -3743,7 +3784,7 @@ "Presence": "Läsnäolo", "Preview": "Esikatselu", "preview-c-room": "Esikatsele julkista kanavaa", - "preview-c-room_description": "Lupa tarkastella julkisen kanavan sisältöä ennen liittymistä", + "preview-c-room_description": "Oikeus tarkastella julkisen kanavan sisältöä ennen liittymistä", "Previous_month": "Edellinen kuukausi", "Previous_week": "Edellinen viikko", "Price": "Hinta", @@ -3760,12 +3801,12 @@ "Private_Channels": "Yksityiset kanavat Channel", "Private_Chats": "Yksityiset keskustelut", "Private_Group": "Yksityinen ryhmä", - "Private_Groups": "Yksityisryhmät", - "Private_Groups_list": "Yksityisryhmien luettelo", + "Private_Groups": "Yksityiset ryhmät", + "Private_Groups_list": "Yksityisten ryhmien luettelo", "Private_Team": "Yksityinen Tiimi", "Productivity": "Tuottavuus", "Profile": "Profiili", - "Profile_details": "Profiilin tiedot", + "Profile_details": "Profiilitiedot", "Profile_picture": "Profiilikuva", "Profile_saved_successfully": "Profiili on tallennettu", "Prometheus": "Prometheus", @@ -3778,10 +3819,10 @@ "Prune_finished": "Karsiminen valmis", "Prune_Messages": "Karsi viestit", "Prune_Modal": "Haluatko varmasti karsia näitä viestejä? Karsittuja viestejä ei voida palauttaa.", - "Prune_Warning_after": "Tämä poistaa kaikki %s %s: sta %s: n jälkeen.", + "Prune_Warning_after": "Tämä poistaa kaikki kohteet %s kohteesta %s kohteen %s jälkeen.", "Prune_Warning_all": "Tämä poistaa kaikki %s %s:ssa!", - "Prune_Warning_before": "Tämä poistaa kaikki %s %s: ssa ennen %s.", - "Prune_Warning_between": "Tämä poistaa kaikki %s %s: stä %s: n ja %s: n välillä.", + "Prune_Warning_before": "Tämä poistaa kaikki kohteet %s kohteessa %s ennen kohdetta %s.", + "Prune_Warning_between": "Tämä poistaa kaikki kohteet %s kohteessa %s kohteiden %s ja %s välillä.", "Pruning_files": "Tiedostojen karsiminen...", "Pruning_messages": "Viestien karsiminen...", "Public": "Julkinen", @@ -3796,21 +3837,22 @@ "Push": "Push", "Push_Description": "Ota käyttöön ja määritä push-ilmoitukset mobiililaitteita käyttäville työtilan jäsenille.", "Push_Notifications": "Push-ilmoitukset", - "Push_apn_cert": "APN Cert", + "Push_apn_cert": "Tukiaseman varmenne", "Push_apn_dev_cert": "Tukiaseman kehitysvarmenne", - "Push_apn_dev_key": "APN Key (kehitys)", + "Push_apn_dev_key": "Tukiaseman kehitysavain", "Push_apn_dev_passphrase": "APN tunnuslause (kehitys)", - "Push_apn_key": "APN Key", - "Push_apn_passphrase": "APN Passphrase", - "Push_enable": "Kytke päälle", + "Push_apn_key": "Tukiaseman avain", + "Push_apn_passphrase": "Tukiaseman salasana", + "Push_enable": "Ota käyttöön", "Push_enable_gateway": "Ota käyttöön yhdyskäytävä", "Push_enable_gateway_Description": "Varoitus: Sinun on hyväksyttävä palvelimesi rekisteröinti (Ohjattu asennus > Organisaation tiedot > Rekisteröi palvelin) ja tietosuojaehtomme (Ohjattu asennus > Pilvitiedot > Pilvipalvelun tietosuojaehtosopimus), jotta voit ottaa tämän asetuksen käyttöön ja käyttää yhdyskäytäväämme. Vaikka tämä asetus olisi käytössä, se ei toimi, jos palvelinta ei ole rekisteröity.", "Push_gateway": "Yhdyskäytävä", "Push_gateway_description": "Useita rivejä voidaan käyttää useamman yhdyskäytävän määrittämiseen", - "Push_gcm_api_key": "GCM API Key", - "Push_gcm_project_number": "GCM Project Number", + "Push_gcm_api_key": "GCM API -avain", + "Push_gcm_project_number": "GCM-projektin numero", "Push_production": "Tuotanto", "Push_request_content_from_server": "Nouda koko viestin sisältö palvelimelta vastaanotettaessa", + "Push_request_content_from_server_Description": "Käyttämällä push-ilmoituksessa vain viestin tunnusta koko viestin sisällön sijasta voit piilottaa viestin sisällön Applelta/Googlelta. Mobiilisovellus noutaa sisällön palvelimesta dynaamisesti ja päivittää ilmoituksen ennen sen näyttämistä. API-virheen yhteydessä saat ilmoituksen Sinulla on uusi viesti. Asetus toimii vain yritysversiossa.", "Push_Setting_Requires_Restart_Alert": "Tämän arvon muuttaminen edellyttää chasovelluksen uudelleenkäynnistämistä.", "Push_show_message": "Näytä viesti ilmoituksessa", "Push_show_username_room": "Näytä kanava Channel / ryhmä / käyttäjänimi ilmoituksessa", @@ -3834,10 +3876,12 @@ "React_when_read_only": "Salli reaktiot", "React_when_read_only_changed_successfully": "Salli reagointi, kun vain luku on muutettu onnistuneesti", "Reacted_with": "Reagoi:", - "Reactions": "Reaktiot", + "Reactions": "Reagoinnit", "Read_by": "Lukenut", "Read_only": "Vain luku", - "Read_only_changed_successfully": "Vain luku vaihdettu onnistuneesti", + "This_room_is_read_only": "Huone on vain luku -tilassa", + "Only_people_with_permission_can_send_messages_here": "Viestejä voivat lähettää vain ne, joilla on oikeus", + "Read_only_changed_successfully": "Vain luku vaihdettu", "Read_only_channel": "Vain luku -kanava Channel", "Read_only_group": "Vain luku -ryhmä", "Real_Estate": "Kiinteistöt", @@ -3850,13 +3894,13 @@ "Receive_Login_Detection_Emails": "Vastaanota kirjautumisen tunnistussähköposteja", "Receive_Login_Detection_Emails_Description": "Saat sähköpostiviestin aina, kun tilillesi havaitaan uusi kirjautuminen.", "Recent_Import_History": "Viimeaikainen tuontihistoria", - "Record": "Nauhoita", + "Record": "Tallenna", "recording": "tallennus", "Redirect_URI": "Uudelleenohjaus-URI", "Refresh": "Päivitä", "Refresh_keys": "Päivitä avaimet", "Refresh_oauth_services": "Päivitä OAuth-palvelut", - "Refresh_your_page_after_install_to_enable_screen_sharing": "Virkistä sivu asennuksen jälkeen ottaaksesi käyttöön näytön jakamisen", + "Refresh_your_page_after_install_to_enable_screen_sharing": "Ota käyttöön näytön jako päivittämällä sivu asennuksen jälkeen", "Refreshing": "Päivitetään", "Regenerate_codes": "Luo koodit uudelleen", "Regexp_validation": "Validointi säännöllisellä lausekkeella", @@ -3864,23 +3908,23 @@ "Register_new_account": "Rekisteröi uusi käyttäjätili", "Register_Server": "Rekisteröi palvelin", "Register_Server_Info": "Käytä chatsovelluksen:n valmiiksi määrittämiä yhdyskäytäviä ja välityspalvelimia.", - "Register_Server_Opt_In": "Uutiskirje, tarjoukset ja tuotepäivitykset", + "Register_Server_Opt_In": "Tuote- ja tietoturvapäivitykset", "Register_Server_Registered": "Rekisteröidy päästäksesi sisään", "Register_Server_Registered_I_Agree": "Olen samaa mieltä", "Register_Server_Registered_Livechat": "Livechat omnichannel-välityspalvelin", "Register_Server_Registered_Marketplace": "Sovellusten markkinapaikka", - "Register_Server_Registered_OAuth": "OAuth-välityspalvelin sosiaaliselle verkostolle", + "Register_Server_Registered_OAuth": "Yhteisöverkoston OAuth-välityspalvelin", "Register_Server_Registered_Push_Notifications": "Mobiilien push-ilmoitusten yhdyskäytävä", - "Register_Server_Standalone": "Pidä itsenäinen, sinun täytyy", - "Register_Server_Standalone_Own_Certificates": "Muodosta mobiilisovellukset uudelleen omilla varmenteillasi", - "Register_Server_Standalone_Service_Providers": "Luo tilit palveluntarjoajien kanssa", - "Register_Server_Standalone_Update_Settings": "Päivitä valmiiksi määritetyt asetukset", + "Register_Server_Standalone": "Jos haluat pysyä erillisenä, toimi seuraavasti:", + "Register_Server_Standalone_Own_Certificates": "Käännä mobiilisovellukset uudelleen käyttämällä omia varmenteitasi", + "Register_Server_Standalone_Service_Providers": "Luo palveluntarjoajien tilejä", + "Register_Server_Standalone_Update_Settings": "Päivitä esimääritetyt asetukset", "Register_Server_Terms_Alert": "Hyväksy ehdot rekisteröinnin viimeistelemiseksi", "register-on-cloud": "Rekisteröidy pilvipalvelussa", "register-on-cloud_description": "Lupa rekisteröityä pilvipalvelussa", "Registration": "Rekisteröinti", "Registration_Succeeded": "Rekisteröinti onnistui", - "Registration_via_Admin": "Rekisteröinti Adminin kautta", + "Registration_via_Admin": "Rekisteröinti Hallinta-kohdassa", "Regular_Expressions": "Säännölliset lausekkeet", "Reject_call": "Hylkää puhelu", "Release": "Julkaisu", @@ -3888,7 +3932,7 @@ "Religious": "Uskonnollinen", "Reload": "Lataa uudelleen", "Reload_page": "Lataa sivu uudelleen", - "Reload_Pages": "Lataa sivuja uudelleen", + "Reload_Pages": "Lataa sivut uudelleen", "Remove": "Poista", "Remove_Admin": "Poista järjestelmänvalvoja", "Remove_Association": "Poista assosiaatio", @@ -3913,8 +3957,8 @@ "remove-slackbridge-links_description": "Lupa poistaa Slackbridge-linkkejä", "remove-team-channel": "Poista Tiimin kanava Channel", "remove-team-channel_description": "Lupa poistaa Tiimin kanava", - "remove-user": "Poista Käyttäjä", - "remove-user_description": "Lupa poistaa käyttäjä huoneesta", + "remove-user": "Poista käyttäjä", + "remove-user_description": "Oikeus poistaa käyttäjä huoneesta", "Removed": "Poistettu", "Removed_User": "Poistettu käyttäjä", "Removed__roomName__from_this_team": "poistettu #__roomName__ tästä tiimistä", @@ -3937,7 +3981,7 @@ "Report_has_been_sent": "Ilmoitus lähetetty", "Report_Number": "Ilmoitus numero", "Report_this_message_question_mark": "Haluatko ilmoittaa tästä viestistä?", - "Reporting": "Raportointi", + "Reporting": "Ilmoitetaan", "Request": "Pyyntö", "Request_seats": "Pyydä paikkoja", "Request_more_seats": "Pyydä lisää paikkoja.", @@ -3986,25 +4030,25 @@ "RetentionPolicy_DoNotPruneThreads": "Älä karsi viestiketjuja", "RetentionPolicy_Enabled": "Käytössä", "RetentionPolicy_ExcludePinned": "Älä sisällytä kiinnitettyjä viestejä", - "RetentionPolicy_FilesOnly": "Vain tiedostojen poistaminen", - "RetentionPolicy_FilesOnly_Description": "Vain tiedostot poistetaan, viestit pysyvät paikallaan.", + "RetentionPolicy_FilesOnly": "Poista vain tiedostot", + "RetentionPolicy_FilesOnly_Description": "Vain tiedostot poistetaan, itse viestit säilyvät.", "RetentionPolicy_MaxAge": "Viestin maksimi-ikä", "RetentionPolicy_MaxAge_Channels": "Viestin maksimi-ikä kanavilla", "RetentionPolicy_MaxAge_Description": "Karsi kaikki viestit, jotka ovat vanhempia kuin tämä arvo (päiviä)", "RetentionPolicy_MaxAge_DMs": "Viestin maksimi-ikä yksityisviesteissä", "RetentionPolicy_MaxAge_Groups": "Viestin maksimi-ikä yksityisissä ryhmissä", "RetentionPolicy_Precision": "Ajastimen tarkkuus", - "RetentionPolicy_Precision_Description": "Kuinka usein poistoajastin käynnistyy. Tämän tarkemman arvon asettaminen tekee kanavista, joilla on nopeat pidätysajastimet toimivat paremmin, mutta saattaa maksaa ylimääräisen prosessointitehon suurissa yhteisöissä.", + "RetentionPolicy_Precision_Description": "Karsinta-ajastimen suoritustiheys. Tarkan arvon määritys toimii paremmin kanavilla, joilla on nopeat säilytysajastimet, mutta käsittelyteho saattaa kärsiä suurissa yhteisöissä.", "RetentionPolicy_RoomWarning": "Viestit, jotka ovat vanhempia kuin __time__, karsitaan automaattisesti tässä kohdassa", "RetentionPolicy_RoomWarning_FilesOnly": "Tiedostot, jotka ovat vanhempia kuin __time__, karsitaan täällä automaattisesti (viestit säilyvät ennallaan)", "RetentionPolicy_RoomWarning_Unpinned": "Kiinnittämättömät viestit, jotka ovat vanhempia kuin __time__, karsitaan automaattisesti tässä kohdassa", - "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": "__time__: n vanhat tiedostot, jotka ovat vanhempia kuin __time__, leikataan automaattisesti täällä (viestit pysyvät ennallaan)", + "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": "Kiinnittämättömät viestit, jotka ovat vanhempia kuin __time__, karsitaan automaattisesti tässä (viestit säilyvät ennallaan)", "RetentionPolicyRoom_Enabled": "Karsi vanhat viestit automaattisesti", "RetentionPolicyRoom_ExcludePinned": "Älä sisällytä kiinnitettyjä viestejä", "RetentionPolicyRoom_FilesOnly": "Karsi vain tiedostot, pidä viestit", "RetentionPolicyRoom_MaxAge": "Viestin maksimi-ikä päivinä (oletus: __max__)", "RetentionPolicyRoom_OverrideGlobal": "Ohita yleinen säilytyskäytäntö", - "RetentionPolicyRoom_ReadTheDocs": "Varo! Näiden asetusten säätäminen äärimmäisen varovasti voi tuhota kaikki viestihistorian. Lue ohjeet ennen ominaisuuden kääntämistä täällä.", + "RetentionPolicyRoom_ReadTheDocs": "Ole varovainen! Näiden asetusten muuttaminen huolettomasti voi poistaa koko viestihistorian. Tutustu oppaisiin täällä, ennen kuin otat ominaisuuden käyttöön.", "Retry": "Yritä Uudelleen", "Retry_Count": "Uudelleenyritysten määrä", "Return_to_home": "Palaa kotinäkymään", @@ -4012,8 +4056,10 @@ "Return_to_the_queue": "Palaa takaisin jonoon", "Review_devices": "Tarkista, milloin ja mistä laitteet muodostavat yhteyden", "Ringing": "Soi", + "Ringtones_and_visual_indicators_notify_people_of_incoming_calls": "Soittoäänet ja visuaaliset ilmaisimet ilmoittavat saapuvista puheluista.", "Robot_Instructions_File_Content": "Robots.txt-tiedoston sisältö", "Root": "Root", + "Required_action": "Toimia tarvitaan", "Default_Referrer_Policy": "Oletusviittauskäytäntö", "Default_Referrer_Policy_Description": "Tämä ohjaa 'Viittaaja'-otsikkoa, joka lähetetään, kun pyydetään upotettua mediaa muilta palvelimilta. Lisätietoja on tässä MDN:n linkissä. Muista, että tämä edellyttää koko sivun päivittämistä, jotta se tulee voimaan", "No_Referrer": "Ei viittaajaa", @@ -4032,66 +4078,67 @@ "Role_Editing": "Roolin muokkaaminen", "Role_Mapping": "Roolien kartoitus", "Role_removed": "Rooli poistettu", - "Room": "Huone Room", - "room_allowed_reacting": "Huoneessa Room sallittu reagointi __user_by__ toimesta", + "Room": "Huone", + "room_allowed_reacting": "__user_by__ salli reagoinnin huoneessa", "room_allowed_reactions": "sallitut reaktiot", - "Room_announcement_changed_successfully": "Huoneen Room ilmoitus muutettiin onnistuneesti", + "Room_announcement_changed_successfully": "Huoneen ilmoitus on muutettu", "Room_archivation_state": "Tila", "Room_archivation_state_false": "Aktiivinen", "Room_archivation_state_true": "Arkistoitu", "Room_archived": "Huone arkistoitu", - "room_changed_announcement": "Huoneen Room ilmoitus muutettiin: __room_announcement__ muuttajana __user_by__", - "room_changed_avatar": "Huoneen Room avatar muuttanut __user_by__", + "room_changed_announcement": "__user_by__ vaihtoi huoneen ilmoitukseksiRoom __room_announcement__ by ", + "room_changed_avatar": "__user_by__ vaihtoi huoneen avatarin", "room_avatar_changed": "muutti huoneen avatarin", - "room_changed_description": "Huoneen kuvaus vaihdettu: __room_description__ - __user_by__", - "room_changed_privacy": "Huoneen tyyppi vaihdettu __room_type__ käyttäjän __user_by__ toimesta", - "room_changed_topic": "Huoneen aihe vaihdettu __room_topic__ käyttäjän __user_by__ toimesta", + "room_changed_description": "__user_by__ vaihtoi huoneen kuvaukseksi: __room_description__", + "room_changed_privacy": "__user_by__ vaihtoi huoneen tyypiksi: __room_type__", + "room_changed_topic": "__user_by__ vaihtoi huoneen aiheeksi: __room_topic__", "room_changed_type": "muutti huoneen muotoon __room_type__", "room_changed_topic_to": "muutti huoneen aiheen muotoon __room_topic__", - "Room_default_change_to_private_will_be_default_no_more": "Tämä on oletuskanava ja sen vaihtaminen yksityisryhmäksi poistaa sen oletus-statuksen. Haluatko jatkaa?", - "Room_description_changed_successfully": "Huoneen kuvaus päivitetty onnistuneesti", - "room_disallowed_reacting": "Huoneessa Room kielletty reagoiminen __user_by__ toimesta", + "Room_default_change_to_private_will_be_default_no_more": "Tämä on oletuskanava, mutta se ei ole enää oletuskanava, jos se muutetaan yksityiseksi ryhmäksi. Haluatko jatkaa?", + "Room_description_changed_successfully": "Huoneen kuvaus päivitetty", + "room_disallowed_reacting": "__user_by__ esti reagoinnin huoneessa", "room_disallowed_reactions": "kielletyt reaktiot", - "Room_Edit": "Huone Room Muokkaa", + "Room_Edit": "Huoneen muokkaus", "Room_has_been_archived": "Huone on arkistoitu", - "Room_has_been_deleted": "Huone poistettu", + "Room_has_been_created": "Huone on luotu", + "Room_has_been_deleted": "Huone on poistettu", "Room_has_been_removed": "Huone on poistettu", - "Room_has_been_unarchived": "Huone on poistettu arkistosta", - "Room_Info": "Huoneen info", + "Room_has_been_unarchived": "Huone on palautettu arkistosta", + "Room_Info": "Huoneen tiedot", "room_is_blocked": "Tämä huone on estetty", "room_account_deactivated": "Tämä tili on poistettu käytöstä", "room_is_read_only": "Tämä kanava on vain luku -tilassa", "room_name": "huoneen nimi", - "Room_name_changed": "Huoneen nimi vaihdettu __room_name__, __user_by__ toimesta", + "Room_name_changed": "__user_by__ vaihtoi huoneen nimeksi __room_name__ by ", "Room_name_changed_to": "muutettu huoneen nimi muotoon __room_name__", - "Room_name_changed_successfully": "Huoneen nimi vaihdettu", + "Room_name_changed_successfully": "Huoneen nimi on vaihdettu", "Room_not_exist_or_not_permission": "Huonetta ei ole olemassa tai sinulla ei ehkä ole käyttöoikeutta", - "Room_not_found": "Huonetta ei löytynyt", - "Room_password_changed_successfully": "Salasanan muuttaminen onnistui", - "room_removed_read_only": "Huoneeseen Room lisätty kirjoitusoikeus __user_by__ toimesta", - "room_set_read_only": "Huone Room asetetaan vain luku-tilaan __user_by__:n toimesta", + "Room_not_found": "Huonetta ei löydy", + "Room_password_changed_successfully": "Huoneen salasana on vaihdettu", + "room_removed_read_only": "__user_by__ lisäsi huoneeseen kirjoitusoikeuden", + "room_set_read_only": "__user_by__ asetti huoneen vain luku -tilaan", "room_removed_read_only_permission": "poisti vain lukuoikeuden", "room_set_read_only_permission": "asetti huoneen vain luku-tilaan", - "Room_topic_changed_successfully": "Huoneen otsikko vaihdettu", + "Room_topic_changed_successfully": "Huoneen otsikko on vaihdettu", "Room_type_changed_successfully": "Huoneen tyyppi vaihdettu", "Room_type_of_default_rooms_cant_be_changed": "Tämä on oletushuone, eikä sen tyyppiä voi muuttaa, ota yhteys järjestelmänvalvojaan.", - "Room_unarchived": "Huone Room palautettu arkistosta", - "Room_updated_successfully": "Huone Room päivitetty onnistuneesti!", + "Room_unarchived": "Huone palautettu arkistosta", + "Room_updated_successfully": "Huone on päivitetty!", "Room_uploaded_file_list": "Tiedostoluettelo", "Room_uploaded_file_list_empty": "Tiedostoja ei ole saatavilla.", - "Rooms": "Huoneet Room", - "Rooms_added_successfully": "Huoneet Room lisätty onnistuneesti", + "Rooms": "Huoneet", + "Rooms_added_successfully": "Huoneet on lisätty", "Routing": "Reititys", "Run_only_once_for_each_visitor": "Suorita vain kerran jokaiselle vierailijalle", - "run-import": "Suorita tuonti", + "run-import": "Tuonti", "run-import_description": "Mahdollisuus käyttää tuontitoimintoa", "run-migration": "Suorita migraatio", "run-migration_description": "Lupa suorittaa migraatioita", - "Running_Instances": "Käynnissä olevat instanssit", + "Running_Instances": "Käynnissä olevat esiintymät", "Runtime_Environment": "Suoritusaikainen ympäristö", - "S_new_messages_since_s": "%s uutta viestiä alkaen %s", - "Same_As_Token_Sent_Via": "Sama kuin \"Token Sent Via\"", - "Same_Style_For_Mentions": "Sama tyyli mainintoihin", + "S_new_messages_since_s": "%s uutta viestiä %s jälkeen", + "Same_As_Token_Sent_Via": "Sama kuin Tunnuksen lähetystapa", + "Same_Style_For_Mentions": "Sama tyyli maininnoissa", "SAML": "SAML", "SAML_Description": "Security Assertion Markup-kieli, jota käytetään todennus- ja valtuutustietojen vaihtamiseen. (SAML)", "SAML_Allowed_Clock_Drift": "Sallittu kellonajoitusvirhe Identiteettipalveluntarjoajaan verrattuna", @@ -4106,16 +4153,16 @@ "SAML_Custom_Authn_Context": "Mukautettu Authn-konteksti", "SAML_Custom_Authn_Context_Comparison": "Authn kontekstin vertailu", "SAML_Custom_Authn_Context_description": "Jätä tämä tyhjäksi, jos haluat jättää authn-kontekstin pois pyynnöstä.\n\n Jos haluat lisätä useita authn-konteksteja, lisää ylimääräiset kontekstit suoraan __AuthnContext Template__ -asetukseen.", - "SAML_Custom_Cert": "Mukautettu sertifikaatti", + "SAML_Custom_Cert": "Mukautettu varmenne", "SAML_Custom_Debug": "Ota vianmääritys käyttöön", "SAML_Custom_EMail_Field": "Sähköpostikentän nimi", "SAML_Custom_Entry_point": "Mukautettu tulopaikka", - "SAML_Custom_Generate_Username": "Generoi käyttäjätunnus", + "SAML_Custom_Generate_Username": "Luo käyttäjätunnus", "SAML_Custom_IDP_SLO_Redirect_URL": "IDP SLO uudelleenohjaus-URL", "SAML_Custom_Immutable_Property": "Muuttumaton kentän nimi", "SAML_Custom_Immutable_Property_EMail": "Sähköpostiosoite", "SAML_Custom_Immutable_Property_Username": "Käyttäjätunnus", - "SAML_Custom_Issuer": "Custom Issuer", + "SAML_Custom_Issuer": "Mukautettu myöntäjä", "SAML_Custom_Logout_Behaviour": "Uloskirjautumiskäyttäytyminen", "SAML_Custom_Logout_Behaviour_End_Only_RocketChat": "Kirjaudu ulos vain chatsovelluksesta", "SAML_Custom_Logout_Behaviour_Terminate_SAML_Session": "Lopeta SAML-istunto", @@ -4131,7 +4178,7 @@ "SAML_Custom_signature_validation_type": "Allekirjoituksen validointityyppi", "SAML_Custom_signature_validation_type_description": "Tätä asetusta ei oteta huomioon, jos mukautettua varmentetta ei ole annettu.", "SAML_Custom_user_data_fieldmap": "Käyttäjätietokenttien määritys", - "SAML_Custom_user_data_fieldmap_description": null, + "SAML_Custom_user_data_fieldmap_description": "Määritä, miten käyttäjätilin kentät (kuten sähköpostiosoite) täytetään SAML-tietueesta (kun se on löydetty). \nEsimerkiksi `{\"name\": \"cn\", \"email\": \"mail\"}` valitsee henkilön ihmisen luettavissa olevan nimen cn-määritteestä ja hänen sähköpostiosoitteensa mail-määritteestä.\nKäytettävissä olevat kentät Rocket.Chatissa: `name`, `email` ja `username`, kaikki muu jätetään pois.\n```{\n \"email\": \"mail\",\n \"username\": {\n \"fieldName\": \"mail\",\n \"regex\": \"(.*)@.+$\",\n \"template\": \"user-__regex__\"\n },\n \"name\": {\n \"fieldNames\": [\n \"firstName\",\n \"lastName\"\n ],\n \"template\": \"__firstName__ __lastName__\"\n },\n \"__identifier__\": \"uid\"\n}\n", "SAML_Custom_user_data_custom_fieldmap": "Käyttäjätietojen mukautetun kentän kartta", "SAML_Custom_user_data_custom_fieldmap_description": "Määritä, miten käyttäjän mukautetut kentät täytetään SAML-tietueesta (kun se on löydetty).", "SAML_Custom_Username_Field": "Käyttäjätunnus-kentän nimi", @@ -4162,7 +4209,7 @@ "SAML_Section_4_Roles": "Roolit", "SAML_Section_5_Mapping": "Kartoitus", "SAML_Section_6_Advanced": "Edistynyt", - "SAML_Custom_channels_update": "Päivitä huonetilaukset Room jokaisella sisäänkirjautumisella", + "SAML_Custom_channels_update": "Päivitä huonetilaukset joka sisäänkirjautumisella", "SAML_Custom_channels_update_description": "Varmistaa, että käyttäjä on kaikkien SAML-vakuutuksen kanavien jäsen jokaisella kirjautumiskerralla.", "SAML_Custom_include_private_channels_update": "Sisällytä yksityinen huone Room huoneiden Room tilauksessa", "SAML_Custom_include_private_channels_update_description": "Lisää käyttäjän kaikkiin SAML-vakuutuksessa oleviin yksityisiin huoneisiin.", @@ -4179,12 +4226,12 @@ "save-canned-responses_description": "Lupa tallentaa esivalmistettuja vastauksia", "save-department-canned-responses": "Tallenna osaston esivalmistetut vastaukset", "save-department-canned-responses_description": "Lupa tallentaa osaston esivalmistetut vastaukset", - "save-others-livechat-room-info": "Tallenna muut Livechat-huoneilmoitukset", - "save-others-livechat-room-info_description": "Lupa tallentaa tietoja muista livechat-kanavista", + "save-others-livechat-room-info": "Tallenna muiden Omnichannel-huoneiden tiedot", + "save-others-livechat-room-info_description": "Oikeus tallentaa tietoja muista Omnichannel-huoneista", "Saved": "Tallennettu", "Saving": "Tallennetaan", - "Scan_QR_code": "Skannaa QR-koodi käyttämällä autentikointisovellusta, kuten Google Authenticator, Authy tai Duo. Se näyttää 6-numeroisen koodin, jonka haluat syöttää alla.", - "Scan_QR_code_alternative_s": "Jos et pysty skannaamaan QR-koodia, voit syöttää koodin manuaalisesti sen sijaan:", + "Scan_QR_code": "Skannaa QR-koodi todennussovelluksella, kuten Google Authenticatorilla, Authylla tai Duolla. Se näyttää 6-numeroisen koodin, joka on annettava alla.", + "Scan_QR_code_alternative_s": "Jos et voi skannata QR-koodia, voit antaa koodin manuaalisesti:", "Scope": "Laajuus", "Score": "Pisteytys", "Screen_Lock": "Näytön lukitus", @@ -4194,7 +4241,7 @@ "Search": "Haku", "Searchable": "Hakukelpoinen", "Search_Apps": "Hae sovelluksia", - "Search_by_file_name": "Hae tiedoston nimen mukaan", + "Search_by_file_name": "Hae tiedostonimellä", "Search_by_username": "Hae käyttäjätunnuksella", "Search_by_category": "Haku kategorian mukaan", "Search_Channels": "Hae kanavia Channel", @@ -4212,13 +4259,14 @@ "Search_Page_Size": "Sivun koko", "Search_Private_Groups": "Hae yksityisiä ryhmiä", "Search_Provider": "Hakupalvelun tarjoaja", - "Search_Rooms": "Etsi Huone Room", - "Search_Users": "Etsi käyttäjiä", + "Search_Rooms": "Hae huoneita", + "Search_Users": "Hae käyttäjiä", "Seats_Available": "__seatsLeft__ Vapaita paikkoja saatavilla", "Seats_usage": "Istuinten käyttö", "seconds": "sekuntia", - "Secret_token": "Salainen tunnus", - "Security": "Turvallisuus", + "Secret_token": "Salaustunnus", + "Secure_SaaS_solution": "Suojattu SaaS-ratkaisu.", + "Security": "Suojaus", "See_documentation": "Katso dokumentaatio", "See_Pricing": "Katso hinnoittelu", "See_full_profile": "Katso koko profiili", @@ -4242,25 +4290,27 @@ "Select_user": "Valitse käyttäjä", "Select_users": "Valitse käyttäjät", "Selected_agents": "Valitut agentit", + "Selected_by_default": "Valittu oletusarvoisesti", "Selected_departments": "Valitut osastot", + "Selected_first_reply_unselected_following_replies": "Valittu ensimmäiseen vastaukseen, ei valittu seuraavia vastauksia varten", "Selected_monitors": "Valitut valvojat", "Selecting_users": "Käyttäjien valitseminen", "Send": "Lähetä", "Send_a_message": "Lähetä viesti", "Send_a_test_mail_to_my_user": "Lähetä testiviesti käyttäjälleni", - "Send_a_test_push_to_my_user": "Lähetä push-testi käyttäjälleni", + "Send_a_test_push_to_my_user": "Lähetä push-testiviesti käyttäjälleni", "Send_confirmation_email": "Lähetä vahvistussähköposti", - "Send_data_into_RocketChat_in_realtime": "Lähetä data Rocket.Chatiin reaaliajassa", - "Send_email": "Lähetä sähköposti", + "Send_data_into_RocketChat_in_realtime": "Lähetä tietoja Rocket.Chatiin reaaliaikaisesti.", + "Send_email": "Lähetä sähköpostia", "Send_invitation_email": "Lähetä sähköpostikutsu", - "Send_invitation_email_error": "Et ole antanyt yhtään kelvollista sähköpostiosoitetta.", + "Send_invitation_email_error": "Et ole antanyt kelvollista sähköpostiosoitetta.", "Send_invitation_email_info": "Voit lähettää useita sähköpostikutsuja kerralla.", "Send_invitation_email_success": "Olet lähettänyt sähköpostikutsun seuraaviin osoitteisiin:", "Send_it_as_attachment_instead_question": "Lähetä sittenkin liitetiedostona?", "Send_me_the_code_again": "Lähetä minulle koodi uudelleen", "Send_request_on": "Lähetä pyyntö osoitteeseen", - "Send_request_on_agent_message": "Lähetä pyyntö agenttiviesteistä", - "Send_request_on_chat_close": "Lähetä pyyntö chatin sulkeutuessa", + "Send_request_on_agent_message": "Lähetä pyyntö agenttiviestien saapuessa", + "Send_request_on_chat_close": "Lähetä pyyntö keskustelun sulkeutuessa", "Send_request_on_chat_queued": "Lähetä pyyntö chatin jonossa", "Send_request_on_chat_start": "Lähetä pyyntö chatissa", "Send_request_on_chat_taken": "Lähetä pyyntö chatin ollessa varattu", @@ -4273,7 +4323,7 @@ "Send_via_email": "Lähetä sähköpostitse", "Send_via_Email_as_attachment": "Lähetä sähköpostitse liitetiedostona", "Send_Visitor_navigation_history_as_a_message": "Lähetä vieraan navigointihistoria viestinä", - "Send_visitor_navigation_history_on_request": "Lähetä vierailijoiden navigointihistoria pyynnöstä", + "Send_visitor_navigation_history_on_request": "Lähetä vierailijan siirtymishistoria pyynnöstä", "Send_welcome_email": "Lähetä tervetulosähköposti", "Send_your_JSON_payloads_to_this_URL": "Lähetä JSON-tiedot tähän URL-osoitteeseen.", "send-mail": "Lähetä sähköpostit", @@ -4284,7 +4334,7 @@ "send-omnichannel-chat-transcript_description": "Lupa lähettää omnichannel-keskustelun puhtaaksikirjoitus", "Sender_Info": "Lähettäjän tiedot", "Sending": "Lähetetään...", - "Sent_an_attachment": "Lähetti liitetiedoston", + "Sent_an_attachment": "Lähetettiin liite", "Sent_from": "Lähettäjä", "Separate_multiple_words_with_commas": "Erottele useat sanat pilkuilla", "Served_By": "Palveli", @@ -4292,10 +4342,10 @@ "Server_Configuration": "Palvelimen konfigurointi", "Server_File_Path": "Palvelimen tiedostopolku", "Server_Folder_Path": "Palvelinkansion polku", - "Server_Info": "Palvelimen tiedot", + "Server_Info": "Palvelintiedot", "Server_Type": "Palvelintyyppi", "Service": "Palvelu", - "Service_account_key": "Palvelun tilin avain", + "Service_account_key": "Huoltotilin avain", "Set_as_favorite": "Aseta suosikiksi", "Set_as_leader": "Aseta johtajaksi", "Set_as_moderator": "Aseta valvojaksi", @@ -4306,7 +4356,7 @@ "set-moderator": "Aseta moderaattori", "set-moderator_description": "Lupa asettaa muut käyttäjät kanavan moderaattoriksi", "set-owner": "Aseta omistaja", - "set-owner_description": "Lupa asettaa muut käyttäjät kanavan omistajaksi", + "set-owner_description": "Oikeus asettaa muita käyttäjiä kanavan omistajaksi", "set-react-when-readonly": "Aseta Reagoi kun Vain Luku", "set-react-when-readonly_description": "Lupa asettaa kyky reagoida viesteihin kanavalla joka on Vain Luku-tilassa", "set-readonly": "Aseta Vain Luku-tila", @@ -4315,9 +4365,9 @@ "Settings_updated": "Asetukset päivitetty", "Setup_Wizard": "Ohjattu asennus", "Setup_Wizard_Description": "Perustiedot työtilasta, kuten organisaation nimi ja maa.", - "Setup_Wizard_Info": "Autamme sinua asettamalla ensimmäinen admin-käyttäjäsi, määrittelemällä organisaatiosi ja rekisteröimällä palvelimesi saadaksesi ilmaisia ​​push-ilmoituksia ja paljon muuta.", + "Setup_Wizard_Info": "Autamme sinua asettamaan ensimmäisen järjestelmänvalvojakäyttäjän, määrittämään organisaatiosi ja rekisteröimään palvelimesi, jotta esimerkiksi saat maksuttomat push-ilmoitukset.", "Share": "Jaa", - "Share_Location_Title": "Jaa sijainti?", + "Share_Location_Title": "Jaetaanko sijainti?", "Share_screen": "Näytön jakaminen", "New_CannedResponse": "Uusi esivalmistettu vastaus", "Edit_CannedResponse": "Muokkaa esivalmistettua vastausta", @@ -4338,14 +4388,14 @@ "Show_Message_In_Main_Thread": "Näytä ketjun viestit pääketjussa", "Show_more": "Näytä lisää", "Show_name_field": "Näytä nimi-kenttä", - "show_offline_users": "Näytä käyttäjät, jotka ovat offline", + "show_offline_users": "näytä offline-käyttäjät", "Show_on_offline_page": "Näytä offline-sivulla", "Show_on_registration_page": "Näytä rekisteröintisivulla", - "Show_only_online": "Näytä vain onlinessä olevat", + "Show_only_online": "Näytä vain online-tilassa olevat", "Show_preregistration_form": "Näytä esirekisteröintilomake", - "Show_queue_list_to_all_agents": "Näytä jonolista kaikille agenteille", - "Show_room_counter_on_sidebar": "Näytä laskuri sivupalkissa", - "Show_Setup_Wizard": "Näytä ohjattu asennustoiminto", + "Show_queue_list_to_all_agents": "Näytä jonoluettelo kaikille agenteille", + "Show_room_counter_on_sidebar": "Näytä huonelaskuri sivupalkissa", + "Show_Setup_Wizard": "Näytä ohjattu asennus", "Show_the_keyboard_shortcut_list": "Näytä pikanäppäinten luettelo", "Show_video": "Näytä video", "Showing": "Näytetään", @@ -4354,12 +4404,13 @@ "Showing_results": "

Näytetään %s tulosta

", "Showing_results_of": "Näytetään tulokset %s - %s %s:stä", "Sidebar": "Sivupalkki", - "Sidebar_list_mode": "Sivupalkin kanavaluettelotila", - "Sign_in_to_start_talking": "Kirjaudu sisään aloittaaksesi puhumisen", + "Sidebar_list_mode": "Sivupalkin kanavaluettelon tila", + "Sign_in_to_start_talking": "Kirjaudu ja ala puhua", + "Sign_in_with__provider__": "__provider__-kirjautuminen", "since_creation": "%s sitten", "Site_Name": "Sivuston nimi", "Site_Url": "Sivuston URL-osoite", - "Site_Url_Description": "Esimerkiksi: https://chat.domain.com/", + "Site_Url_Description": "Esimerkki: https://keskustelu.toimialue.com/", "Size": "Koko", "Skip": "Ohita", "Slack_Users": "Slackin käyttäjien CSV", @@ -4367,23 +4418,23 @@ "SlackBridge_APIToken_Description": "Voit määrittää useita slack-palvelimia lisäämällä yhden API-tunnisteen per rivi.", "Slackbridge_channel_links_removed_successfully": "Slackbridge-kanavan linkit on poistettu onnistuneesti.", "SlackBridge_Description": "Ota käyttöön mahdollistaaksesi chatsovelluksen kommunikoinnin suoraan Slackin kanssa.", - "SlackBridge_error": "SlackBridge sai virheen tuotaessa viestejäsi%s:%s", - "SlackBridge_finish": "SlackBridge on lopettanut viestien tuonnin%s: ssä. Lataa kaikki viestit uudelleen.", - "SlackBridge_Out_All": "SlackBridge pois kaikki", + "SlackBridge_error": "SlackBridgessä ilmeni virhe tuotaessa viestejäsi %s: %s", + "SlackBridge_finish": "SlackBridge on tuonut viestit %s. Näytä kaikki viestit lataamalla uudelleen.", + "SlackBridge_Out_All": "SlackBridge - lähetä kaikki", "SlackBridge_Out_All_Description": "Lähetä viestejä kaikista kanavista, jotka ovat olemassa Slackissa ja joihin botti on liittynyt", "SlackBridge_Out_Channels": "SlackBridge Out kanavat Channel", "SlackBridge_Out_Channels_Description": "Valitse, mitkä kanavat lähettävät viestit takaisin Slackiin", - "SlackBridge_Out_Enabled": "SlackBridge Out käytössä", + "SlackBridge_Out_Enabled": "SlackBridgen lähtö käytössä", "SlackBridge_Out_Enabled_Description": "Valitse, haluatko että SlackBridge lähettää viestisi myös takaisin Slackiin", "SlackBridge_Remove_Channel_Links_Description": "Poista sisäinen linkki chatsovvelluksen-kanavien ja Slack-kanavien välillä. Linkit luodaan sen jälkeen uudelleen kanavien nimien perusteella.", - "SlackBridge_start": "@%s on käynnistänyt SlackBridge-tuonnin `#%s`: lla. Ilmoitamme sinulle, milloin se on valmis.", - "Slash_Gimme_Description": "Näyttää ༼ つ ◕_◕ ༽つ ennen viestiäsi", - "Slash_LennyFace_Description": "Näyttää ( ͡° ͜ʖ ͡°) viestisi jälkeen", + "SlackBridge_start": "@%s aloitti SlackBridge-tuonnin `#%s`. Saat ilmoituksen, kun toiminto on valmis.", + "Slash_Gimme_Description": "Näyttää merkit ༼ つ ◕_◕ ༽つ ennen viestiäsi", + "Slash_LennyFace_Description": "Näyttää merkit ( ͡° ͜ʖ ͡°) viestisi jälkeen", "Slash_Shrug_Description": "Näyttää ¯ \\ _ (ツ) _ / ¯ viestisi jälkeen", "Slash_Status_Description": "Aseta tilaviesti", "Slash_Status_Params": "Tilaviesti", - "Slash_Tableflip_Description": "Näyttää (╯°□°)╯︵ ┻━┻", - "Slash_TableUnflip_Description": "Näyttää ┬─┬ ノ( ゜-゜ノ)", + "Slash_Tableflip_Description": "Näyttää merkit (╯°□°)╯︵ ┻━┻", + "Slash_TableUnflip_Description": "Näyttää merkit ┬─┬ ノ( ゜-゜ノ)", "Slash_Topic_Description": "Aseta Otsikko", "Slash_Topic_Params": "Aiheviesti", "Smarsh": "Smarsh", @@ -4393,9 +4444,9 @@ "Smarsh_Enabled": "Smarsh käytössä", "Smarsh_Enabled_Description": "Onko Smarsh EML-liitäntä käytössä vai ei (tarvitsee 'From Email' -kentän täytettynä kohdassa Email > SMTP).", "Smarsh_Interval": "Smarsh aikaväli", - "Smarsh_Interval_Description": "Odotusaika ennen chattien lähettämistä (tarvitsee sähköpostin täyttäjän sähköpostiosoite -> SMTP).", - "Smarsh_MissingEmail_Email": "Puuttuva sähköposti", - "Smarsh_MissingEmail_Email_Description": "Sähköpostiosoite, joka näkyy käyttäjätunnukselle, kun sähköpostiosoite puuttuu, tapahtuu yleensä bot-tileillä.", + "Smarsh_Interval_Description": "Odotusaika ennen keskustelujen lähettämistä (Sähköpostiosoitteesta-kentässä on oltava arvo kohdassa Sähköposti -> SMTP).", + "Smarsh_MissingEmail_Email": "Puuttuva sähköpostiosoite", + "Smarsh_MissingEmail_Email_Description": "Käyttäjätilille näytettävä sähköpostiosoite, kun sähköpostiosoite puuttuu. Tätä ilmenee tavallisesti bot-tileillä.", "Smarsh_Timezone": "Smarsh-aikavyöhyke", "Smileys_and_People": "Hymiöt ja ihmiset", "SMS": "SMS", @@ -4404,25 +4455,25 @@ "SMS_Default_Omnichannel_Department_Description": "Jos tämä asetus on asetettu, kaikki tämän integraation käynnistämät uudet saapuvat keskustelut ohjataan tälle osastolle.\nTämä asetus voidaan korvata siirtämällä pyyntöön parametrina osaston kyselyparametri.\nesim. https:///api/v1/livechat/sms-incoming/twilio?department=.\nHuomautus: jos käytät osaston nimeä, sen pitäisi olla URL-turvallinen.", "SMS_Enabled": "Tekstiviestit käytössä", "SMTP": "SMTP", - "SMTP_Host": "SMTP-palvelin", + "SMTP_Host": "SMTP-isäntä", "SMTP_Password": "SMTP-salasana", "SMTP_Port": "SMTP-portti", "SMTP_Test_Button": "Testaa SMTP-asetukset", "SMTP_Username": "SMTP-käyttäjätunnus", - "Snippet_Added": "Luotu%s: lla", - "Snippet_Messages": "Snippet-viestit", - "Snippet_name": "Leikkausnimi", - "snippet-message": "Snippet-viesti", - "snippet-message_description": "Luvan luoda koodinpätkän viesti", + "Snippet_Added": "Luotu %s", + "Snippet_Messages": "Katkelmaviestit", + "Snippet_name": "Katkelman nimi", + "snippet-message": "Katkelmaviesti", + "snippet-message_description": "Oikeus luoda katkelmaviesti", "Snippeted_a_message": "Luotu snippet __snippetLink__", - "Social_Network": "Sosiaalinen verkosto", + "Social_Network": "Yhteisöverkosto", "Some_ideas_to_get_you_started": "Joitakin ideoita näin alkuun", "Something_went_wrong": "Jokin meni pieleen", "Something_went_wrong_try_again_later": "Jokin meni pieleen, yritä myöhemmin uudelleen.", - "Sorry_page_you_requested_does_not_exist_or_was_deleted": "Valitettavasti pyytämäsi sivu ei ole olemassa tai sitä ei poistettu!", + "Sorry_page_you_requested_does_not_exist_or_was_deleted": "Pyytämääsi sivua ei ole tai se on poistettu!", "Sort": "Lajittele", "Sort_By": "Lajittelu", - "Sort_by_activity": "Lajittele aktiviteetin mukaan", + "Sort_by_activity": "Lajittelu toiminnan mukaan", "Sound": "Ääni", "Sound_File_mp3": "Äänitiedosto (mp3)", "Sound File": "Äänitiedosto", @@ -4432,46 +4483,46 @@ "spy-voip-calls_description": "Lupa vakoilla voip-puheluita", "SSL": "SSL", "Star": "Tähti", - "Star_Message": "Merkkaa tähdellä", + "Star_Message": "Merkitse viesti tähdellä", "Starred_Messages": "Tähdellä merkityt viestit", - "Start": "Alku", + "Start": "Aloita", "Start_a_call": "Aloita puhelu", "Start_a_call_with": "Aloita puhelu", "Start_audio_call": "Aloita äänipuhelu", "Start_call": "Aloita puhelu", - "Start_Chat": "Aloita chat", + "Start_Chat": "Aloita keskustelu", "Start_conference_call": "Aloita konferenssipuhelu", "Start_of_conversation": "Keskustelun alku", "Start_OTR": "Aloita Epävirallinen Keskustelu", "Start_video_call": "Aloita videopuhelu", - "Start_video_conference": "Aloita videokonferenssi?", - "Start_with_s_for_user_or_s_for_channel_Eg_s_or_s": "Lisää aluksi %s (käyttäjä) tai %s (kanava). Esim: %s tai %s", + "Start_video_conference": "Aloitetaanko neuvottelupuhelu?", + "Start_with_s_for_user_or_s_for_channel_Eg_s_or_s": "Lisää aluksi %s (käyttäjä) tai %s (kanava). Esimerkki: %s tai %s", "start-discussion": "Aloita keskustelu", "start-discussion_description": "Lupa aloittaa keskustelu", "start-discussion-other-user": "Aloita keskustelu (Muu käyttäjä)", "start-discussion-other-user_description": "Lupa aloittaa keskustelu, joka antaa käyttäjälle luvan luoda keskustelu myös toisen käyttäjän lähettämästä viestistä", "Started": "Aloitettu", - "Started_a_video_call": "Aloitti videopuhelun", + "Started_a_video_call": "Aloitettiin videopuhelu", "Started_At": "Alkoi", "Statistics": "Tilastot", "Statistics_reporting": "Lähetä tilastot chatsovellukselle", - "Statistics_reporting_Description": "Lähettämällä tilastoja autat meitä tunnistamaan kuinka monta Rocket.Chat- asennusta on käytössä sekä kuinka hyvin järjestelmä toimii, jotta voimme edelleen parantaa sitä. Älä huoli, koska mitään käyttäjätietoja ei lähetetä ja kaikki saamamme tiedot pidetään luottamuksellisina.", + "Statistics_reporting_Description": "Lähettämällä tilastotietoja autat meitä tunnistamaan, miten monta Rocket.Chat-esiintymää on käytössä ja miten hyvin järjestelmä toimii, jotta pystymme parantamaan sitä. Ei hätää, käyttäjätietoja ei lähetetä ja kaikki saamamme tiedot pidetään luottamuksellisina.", "Stats_Active_Guests": "Aktivoidut vieraat", "Stats_Active_Users": "Aktivoidut käyttäjät", "Stats_App_Users": "Chatsovelluksen käyttäjät", "Stats_Avg_Channel_Users": "Keskimääräinen kanavan käyttäjämäärä", "Stats_Avg_Private_Group_Users": "Keskimääräiset yksityisen ryhmän käyttäjät", "Stats_Away_Users": "Poissaolevat käyttäjät", - "Stats_Max_Room_Users": "Maksimi käyttäjämäärä", - "Stats_Non_Active_Users": "Passiivisia käyttäjiä", - "Stats_Offline_Users": "Offline-käyttäjiä", + "Stats_Max_Room_Users": "Huoneiden enimmäiskäyttäjämäärä", + "Stats_Non_Active_Users": "Käytöstä poistetut käyttäjät", + "Stats_Offline_Users": "Offline-käyttäjät", "Stats_Online_Users": "Online-käyttäjät", "Stats_Total_Active_Apps": "Aktiivisia sovelluksia yhteensä", "Stats_Total_Active_Incoming_Integrations": "Aktiiviset saapuvat integraatiot yhteensä", "Stats_Total_Active_Outgoing_Integrations": "Aktiiviset lähtevät integraatiot yhteensä", "Stats_Total_Channels": "Kanavat", "Stats_Total_Connected_Users": "Liittyneet käyttäjät yhteensä", - "Stats_Total_Direct_Messages": "Yksityiskeskustelujen määrä", + "Stats_Total_Direct_Messages": "Suorien viestien huoneet", "Stats_Total_Incoming_Integrations": "Saapuvat integraatiot yhteensä", "Stats_Total_Installed_Apps": "Asennetut sovellukset yhteensä", "Stats_Total_Integrations": "Integraatiot yhteensä", @@ -4484,7 +4535,7 @@ "Stats_Total_Messages_PrivateGroup": "Viestit ryhmissä yhteensä", "Stats_Total_Outgoing_Integrations": "Lähtevät integraatiot yhteensä", "Stats_Total_Private_Groups": "Yksityisryhmät", - "Stats_Total_Rooms": "Huoneiden määrä", + "Stats_Total_Rooms": "Huoneet", "Stats_Total_Uploads": "Latauksia yhteensä", "Stats_Total_Uploads_Size": "Latausten koko yhteensä", "Stats_Total_Users": "Käyttäjiä yhteensä", @@ -4498,7 +4549,7 @@ "Stop_call": "Lopeta puhelu", "Stop_Recording": "Lopeta tallennus", "Store_Last_Message": "Tallenna viimeinen viesti", - "Store_Last_Message_Sent_per_Room": "Tallenna viimeinen viesti joka huoneessa.", + "Store_Last_Message_Sent_per_Room": "Tallenna kussakin huoneessa viimeksi lähetetty viesti.", "Stream_Cast": "Stream Cast", "Stream_Cast_Address": "Stream Cast-osoite", "Stream_Cast_Address_Description": "Chatsovelluksen Central Stream Castin IP-osoite tai isäntä. Esim. `192.168.1.1:3000` tai `localhost:4000`", @@ -4514,7 +4565,7 @@ "Sunday": "Sunnuntai", "Support": "Tuki", "Survey": "Kysely", - "Survey_instructions": "Arvioi jokainen kysymys sen mukaan miten tyytyväinen olet niin, että 1 merkitsee, että olet täysin tyytymätön ja 5 merkitsee, että olet täysin tyytyväinen.", + "Survey_instructions": "Arvioi joka kysymys tyytyväisyytesi mukaan. 1 merkitsee, että olet täysin tyytymätön ja 5 merkitsee, että olet täysin tyytyväinen.", "Symbols": "Symbolit", "Sync": "Synkronointi", "Sync / Import": "Synkronointi / tuonti", @@ -4524,18 +4575,19 @@ "Sync_Users": "Synkronoi käyttäjät", "sync-auth-services-users": "Synkronoi todentamispalvelujen käyttäjät", "sync-auth-services-users_description": "Lupa synkronoida todentamispalvelujen käyttäjiä", - "System_messages": "Järjestelmäviestit", + "System_messages": "Järjestelmäilmoitukset", "Tag": "Tunniste", "Tags": "Tägit", "Tag_removed": "Tägi poistettu", "Tag_already_exists": "Tägi on jo olemassa", - "Take_it": "Ota!", + "Take_it": "Ota se!", "Take_rocket_chat_with_you_with_mobile_applications": "Ota Chatsovellus mukaasi mobiilisovellusten avulla.", "Taken_at": "Otettu klo.", "Talk_Time": "Keskusteluaika", + "Talk_to_your_workspace_administrator_about_enabling_video_conferencing": "Pyydä työtilan järjestelmänvalvojaa ottamaan videoneuvottelut käyttöön", "Target user not allowed to receive messages": "Kohdekäyttäjä ei saa vastaanottaa viestejä", - "TargetRoom": "Tavoitehuone Room", - "TargetRoom_Description": "Tila, jossa lähetetään viestejä, jotka johtuvat tämän tapahtuman tuloksesta. Vain yksi tavoitehuone on sallittu ja sen on oltava olemassa.", + "TargetRoom": "Kohdehuone", + "TargetRoom_Description": "Huone, johon lähetetään viestit, jotka johtuvat tämän tapahtuman laukaisusta. Vain yksi kohdehuone on sallittu, ja sen on oltava olemassa.", "Team": "Tiimi", "Team_Add_existing_channels": "Lisää olemassa oleva kanava Channel", "Team_Add_existing": "Lisää olemassaoleva", @@ -4543,9 +4595,11 @@ "Team_Channels": "Tiimin kanavat Channel", "Team_Delete_Channel_modal_content_danger": "Tätä ei voi peruuttaa.", "Team_Delete_Channel_modal_content": "Haluatko poistaa tämän kanavan Channel?", + "Team_has_been_created": "Tiimi on luotu", "Team_has_been_deleted": "Tiimi on poistettu.", "Team_Info": "Tiimin tiedot", "Team_Mapping": "Ryhmäkartoitus", + "Team_Name": "Tiimin nimi", "Team_Remove_from_team_modal_content": "Haluatko poistaa tämän kanavan Channel __teamName__:sta? Kanava Channel siirretään takaisin työtilaan.", "Team_Remove_from_team": "Poista Tiimistä", "Team_what_is_this_team_about": "Mistä tässä Tiimissä on kyse", @@ -4582,7 +4636,7 @@ "Teams_New_Add_members_Label": "Lisää jäseniä", "Teams_New_Broadcast_Description": "Vain valtuutetut käyttäjät voivat kirjoittaa uusia viestejä, mutta muut käyttäjät voivat vastata", "Teams_New_Broadcast_Label": "Lähetys", - "Teams_New_Description_Label": "Otsikko", + "Teams_New_Description_Label": "Aihe", "Teams_New_Description_Placeholder": "Mistä tässä joukkueessa on kyse", "Teams_New_Encrypted_Description_Disabled": "Saatavilla vain yksityiselle Tiimille", "Teams_New_Encrypted_Description_Enabled": "Päästä päähän salattu tiimi. Haku ei toimi salattujen tiimien kanssa, ja ilmoitukset eivät välttämättä näytä viestien sisältöä.", @@ -4599,7 +4653,7 @@ "Teams_Select_a_team": "Valitse tiimi", "Teams_Search_teams": "Etsi tiimejä", "Teams_New_Read_only_Label": "Vain luku", - "Technology_Services": "Teknologiapalvelut", + "Technology_Services": "Tekniikkapalvelut", "Terms": "Ehdot", "Terms_of_use": "Käyttöehdot", "Test_Connection": "Testaa yhteys", @@ -4626,20 +4680,20 @@ "The_setting_s_is_configured_to_s_and_you_are_accessing_from_s": "Asetukseksi %s on määritetty %s ja käytät kohteesta %s!", "The_user_s_will_be_removed_from_role_s": "Käyttäjä %s poistetaan roolista %s", "The_user_will_be_removed_from_s": "Käyttäjä poistetaan kohteesta %s", - "The_user_wont_be_able_to_type_in_s": "Käyttäjä ei pysty kirjoittamaan %s", + "The_user_wont_be_able_to_type_in_s": "Käyttäjä ei voi kirjoittaa kohteessa %s", "Theme": "Teema", "theme-color-attention-color": "Huomioväri", - "theme-color-component-color": "Komponenttiväri", + "theme-color-component-color": "Komponentin väri", "theme-color-content-background-color": "Sisällön taustaväri", "theme-color-custom-scrollbar-color": "Mukautettu vierityspalkin väri", - "theme-color-error-color": "Virhe Väri", + "theme-color-error-color": "Virheellinen väri", "theme-color-info-font-color": "Tietojen fontin väri", "theme-color-link-font-color": "Linkin fontin väri", "theme-color-pending-color": "Odottava väri", - "theme-color-primary-action-color": "Ensisijainen toimintaväri", + "theme-color-primary-action-color": "Ensisijaisen toiminnan väri", "theme-color-primary-background-color": "Ensisijainen taustaväri", - "theme-color-primary-font-color": "Ensisijainen fontin väri", - "theme-color-rc-color-alert": "hälytys", + "theme-color-primary-font-color": "Ensisijaisen fontin väri", + "theme-color-rc-color-alert": "Hälytys", "theme-color-rc-color-alert-light": "Hälytysvalo", "theme-color-rc-color-alert-message-primary": "Hälytysviesti, Ensisijainen", "theme-color-rc-color-alert-message-primary-background": "Hälytysviesti, Ensisijainen tausta", @@ -4664,12 +4718,12 @@ "theme-color-rc-color-primary-light": "Ensisijainen, vaalea", "theme-color-rc-color-primary-light-medium": "Ensisijainen, vaalea, Keskitaso", "theme-color-rc-color-primary-lightest": "Ensisijainen, vaalein", - "theme-color-rc-color-success": "Menestys", + "theme-color-rc-color-success": "Onnistui", "theme-color-rc-color-success-light": "Onnistumisvalo", "theme-color-secondary-action-color": "Toissijaisen toiminnan väri", "theme-color-secondary-background-color": "Toissijaisen toiminnan taustaväri", - "theme-color-secondary-font-color": "Toissijainen fontin väri", - "theme-color-selection-color": "Valinnan väri", + "theme-color-secondary-font-color": "Toissijaisen fontin väri", + "theme-color-selection-color": "Valintaväri", "theme-color-status-away": "Poissa-tilan väri", "theme-color-status-busy": "Varattu-tilan väri", "theme-color-status-offline": "Offline-tilan väri", @@ -4683,7 +4737,7 @@ "theme-color-transparent-lightest": "Läpinäkyvä, vaalein", "theme-color-unread-notification-color": "Lukemattomien ilmoitusten väri", "theme-custom-css": "Mukautettu CSS", - "theme-font-body-font-family": "Vartalon kirjasinperhe", + "theme-font-body-font-family": "Leipätekstin fonttiperhe", "There_are_no_agents_added_to_this_department_yet": "Tälle osastolle ei ole vielä lisätty agentteja.", "There_are_no_applications": "OAuth-sovelluksia ei ole vielä lisätty.", "There_are_no_applications_installed": "Tällä hetkellä Rocket.Chat-sovelluksia ei ole asennettu.", @@ -4703,20 +4757,21 @@ "this_app_is_included_with_subscription": "Tämä sovellus sisältyy __bundleName__-tilaukseen", "This_cant_be_undone": "Tätä ei voi peruuttaa.", "This_conversation_is_already_closed": "Tämä keskustelu on jo suljettu.", - "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "Tämä sähköpostiosoite on jo käytetty, ja sitä ei ole vahvistettu. Vaihda salasanasi.", + "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "Tämä sähköpostiosoite on jo käytössä, ja sitä ei ole vahvistettu. Vaihda salasanasi.", "This_feature_is_currently_in_alpha": "Tämä ominaisuus on tällä hetkellä alfa-versiossa!", "This_is_a_desktop_notification": "Tämä on työpöytäilmoitus", - "This_is_a_push_test_messsage": "Tämä on testi-pushviesti", + "This_is_a_deprecated_feature_alert": "Tämä on vanhentunut ominaisuus. Se ei välttämättä toimi odotetusti, eikä siihen tule päivityksiä.", + "This_is_a_push_test_messsage": "Tämä on push-testiviesti", "This_message_was_rejected_by__peer__peer": "Vertainen: __peer__ hylkäsi tämän viestin.", "This_monitor_was_already_selected": "Tämä valvoja oli jo valittu", "This_month": "Tämä kuukausi", - "This_room_has_been_archived_by__username_": "Tämä huone on arkistoitu käyttäjän __username__ toimesta", - "This_room_has_been_unarchived_by__username_": "Tämä huone on palautettu arkistosta käyttäjän __username__ toimesta", + "This_room_has_been_archived_by__username_": "__username__ on arkistoinut tämän huoneen", + "This_room_has_been_unarchived_by__username_": "__username__ on palauttanut tämän huoneen arkistosta", "This_room_has_been_archived": "arkistoitu huone", "This_room_has_been_unarchived": "arkistoimaton huone", "This_week": "Tämä viikko", - "thread": "lanka", - "Thread_message": null, + "thread": "ketju", + "Thread_message": "Kommentoi käyttäjän *__username__'s* viestiä: _ __msg__ _", "Threads": "Viestiketjut", "Threads_Description": "Ketjut mahdollistavat järjestellyn keskustelun tietyn viestin ympärillä.", "Threads_unavailable_for_federation": "Viestiketjut eivät ole käytettävissä liittoutuneissa huoneissa", @@ -4731,14 +4786,14 @@ "Title_bar_color_offline": "Otsikkopalkin väri offline-tilassa", "Title_offline": "Offline-otsikko", "To": "Kohde", - "To_additional_emails": "Sähköpostiviesteihin", - "To_install_RocketChat_Livechat_in_your_website_copy_paste_this_code_above_the_last_body_tag_on_your_site": "Asenna Rocket.Chatin livechat sivustoosi kopioimalla ja liittämällä tämä koodi sivustosi viimeisen </body>-tunnisteen yläpuolelle.", + "To_additional_emails": "Lisäsähköposteihin", + "To_install_RocketChat_Livechat_in_your_website_copy_paste_this_code_above_the_last_body_tag_on_your_site": "Asenna Rocket.Chat Livechat sivustoosi kopioimalla ja liittämällä tämä koodi sivustosi viimeisen </body>-tunnisteen yläpuolelle.", "To_prevent_seeing_this_message_again_allow_popups_from_workspace_URL": "Voit estää tämän viestin näkemisen uudelleen varmistamalla, että selaimesi asetukset sallivat ponnahdusikkunoiden avaamisen työtilan URL-osoitteesta:", "to_see_more_details_on_how_to_integrate": "jotta näet lisätietoja integroinnista.", "To_users": "Käyttäjille", "Today": "Tänään", - "Toggle_original_translated": "Vaihda alkuperäinen / käännetty", - "toggle-room-e2e-encryption": "Vaihda huoneen Room E2E-salaus", + "Toggle_original_translated": "Vaihda alkuperäinen/käännetty", + "toggle-room-e2e-encryption": "Vaihda huoneen täysi salaus", "toggle-room-e2e-encryption_description": "Lupa vaihtaa E2E-salaushuone", "Token": "Tunniste", "Token_Access": "Tunniste, kulku", @@ -4747,7 +4802,7 @@ "Token_required": "Tunniste vaaditaan", "Tokens_Minimum_Needed_Balance": "Tarvittava vähimmäistunnistetaso", "Tokens_Minimum_Needed_Balance_Description": "Aseta vähimmäistasapaino jokaiselle tunnukselle. Tyhjä tai \"0\" ei rajoitetta.", - "Tokens_Minimum_Needed_Balance_Placeholder": "Tasapainoarvo", + "Tokens_Minimum_Needed_Balance_Placeholder": "Saldoarvo", "Tokens_Required": "Tunnisteet vaaditaan", "Tokens_Required_Input_Description": "Kirjoita yhden tai useamman tunnisteen omaavan omaisuuden nimet pilkulla erotettuna.", "Tokens_Required_Input_Error": "Virheellisesti kirjoitetut tunnisteet.", @@ -4758,7 +4813,7 @@ "Total_conversations": "Keskustelut yhteensä", "Total_Discussions": "Keskustelut", "Total_messages": "Viestejä yhteensä", - "Total_rooms": "Huoneiden Room määrä", + "Total_rooms": "Huoneiden määrä", "Total_Threads": "Viestiketjut", "Total_visitors": "Vierailijoita yhteensä", "TOTP Invalid [totp-invalid]": "Koodi tai salasana on virheellinen", @@ -4778,10 +4833,10 @@ "Translate": "Käännä", "Translated": "Käännetty", "Translations": "Käännökset", - "Travel_and_Places": "Matkustus ja paikat", + "Travel_and_Places": "Matkailu ja paikat", "Trigger_removed": "Laukaisija poistettu", - "Trigger_Words": "Trigger sanat", - "Triggers": "Laukaisijat", + "Trigger_Words": "Laukaisinsanat", + "Triggers": "Laukaisimet", "Troubleshoot": "Vianmääritys", "Troubleshoot_Description": "Määritä, miten vianmääritystä käsitellään työtilassa.", "Troubleshoot_Disable_Data_Exporter_Processor": "Tietojen viejäprosessorin poistaminen käytöstä", @@ -4816,21 +4871,21 @@ "Turn_off_video": "Kytke video pois päältä", "Two Factor Authentication": "Kaksivaiheinen todennus", "Two-factor_authentication": "Kaksivaiheinen tunnistautuminen TOTP:n avulla", - "Two-factor_authentication_disabled": "Kaksivaiheinen tunnistautuminen pois käytöstä", + "Two-factor_authentication_disabled": "Kaksivaiheinen todennus poissa käytöstä", "Two-factor_authentication_email": "Kaksivaiheinen tunnistautuminen sähköpostitse", "Two-factor_authentication_email_is_currently_disabled": "Sähköpostin kautta tapahtuva kaksivaiheinen tunnistautuminen on tällä hetkellä poistettu käytöstä", - "Two-factor_authentication_enabled": "Kaksivaiheinen tunnistautuminen käytössä", - "Two-factor_authentication_is_currently_disabled": "Kaksivaiheinen tunnistautuminen on tällä hetkellä poistettu käytöstä", - "Two-factor_authentication_native_mobile_app_warning": "VAROITUS: Otettaessa tämä käyttöön, et pysty kirjautumaan natiivimobiilisovelluksista (Rocket.Chat+ jne) käyttäen vain salasanaasi ennenkuin sovelluksiin toteutetaan 2FA", + "Two-factor_authentication_enabled": "Kaksivaiheinen todennus käytössä", + "Two-factor_authentication_is_currently_disabled": "Kaksivaiheinen todennus on poissa käytöstä", + "Two-factor_authentication_native_mobile_app_warning": "VAROITUS: jos otat tämän käyttöön, voit kirjautua mobiilisovelluksiin (Rocket.Chat+) salasanalla vasta, kun sovellukset ottavat 2FA-todennuksen käyttöön.", "Type": "Tyyppi", "typing": "kirjoittaa", "Types": "Tyypit", "Types_and_Distribution": "Tyypit ja jakelu", "Type_your_email": "Kirjoita sähköpostiosoitteesi", - "Type_your_job_title": "Kirjoita työnimesi otsikko", + "Type_your_job_title": "Kirjoita työtehtäväsi", "Type_your_message": "Kirjoita viestisi", "Type_your_name": "Kirjoita nimesi", - "Type_your_new_password": "Anna uusi salasana", + "Type_your_new_password": "Kirjoita uusi salasana", "Type_your_password": "Kirjoita salasanasi", "Type_your_username": "Kirjoita käyttäjätunnuksesi", "UI_Allow_room_names_with_special_chars": "Salli erikoismerkit huoneiden nimessä", @@ -4856,15 +4911,15 @@ "Undefined": "Määrittelemätön", "Unfavorite": "Poista suosikeista", "Unfollow_message": "Poista viesti seuratuista", - "Unignore": "Huomioi", + "Unignore": "Kumoa ohitus", "Uninstall": "Poista asennus", "Unit_removed": "Yksikkö poistettu", "Unknown_Import_State": "Tuntematon tuonnin tila", "Unlimited": "Rajoittamaton", "Unmute": "Poista mykistys", - "Unmute_someone_in_room": "Poista jonkun mykistys huoneessa", - "Unmute_user": "Poista mykistys käyttäjältä", - "Unnamed": "Nimetön", + "Unmute_someone_in_room": "Kumoa jonkun mykistys huoneessa", + "Unmute_user": "Kumoa käyttäjän mykistys", + "Unnamed": "Nimeämätön", "Unpin": "Poista kiinnitys", "Unpin_Message": "Poista viestin kiinnitys", "unpinning-not-allowed": "Kiinnityksen poistaminen ei ole sallittua", @@ -4872,10 +4927,10 @@ "Unread_Count": "Lukemattomien määrä", "Unread_Count_DM": "Lukemattomien suorien viestien määrä", "Unread_Messages": "Lukemattomat viestit", - "Unread_on_top": "Lukemattomat päälle", - "Unread_Rooms": "Lukemattomia huoneita", + "Unread_on_top": "Lukemattomat ylinnä", + "Unread_Rooms": "Lukemattomat huoneet", "Unread_Rooms_Mode": "Lukemattomien huoneiden Room tila", - "Unread_Tray_Icon_Alert": "Lukematon lokeron kuvakehälytys", + "Unread_Tray_Icon_Alert": "Lukemattomien kuvakehälytys ilmaisinalueella", "Unstar_Message": "Poista tähti", "Unmute_microphone": "Mikrofonin mykistyksen poistaminen", "Update": "Päivitä", @@ -4908,7 +4963,7 @@ "Use": "Käytä", "Use_account_preference": "Käytä tilin asetuksia", "Use_Emojis": "Käytä emojeita", - "Use_Global_Settings": "Käytä globaaleja asetuksia", + "Use_Global_Settings": "Käytä yleisiä asetuksia", "Use_initials_avatar": "Käytä käyttäjätunnuksen nimikirjaimia", "Use_minor_colors": "Käytä vähäistä väripalettia (oletukset perivät päävärit)", "Use_Room_configuration": "Korvaa palvelimen asetukset ja käyttää huoneen omaia asetuksia", @@ -4923,16 +4978,16 @@ "User": "Käyttäjä", "User Search": "Käyttäjähaku", "User Search (Group Validation)": "Käyttäjähaku (ryhmän validointi)", - "User__username__is_now_a_leader_of__room_name_": "Käyttäjä __username__ on nyt johtaja __room_name__", - "User__username__is_now_a_moderator_of__room_name_": "Käyttäjä __username__ on nyt __room_name__ moderaattori", - "User__username__is_now_an_owner_of__room_name_": "Käyttäjä __username__ on nyt __room_name__ omistaja", + "User__username__is_now_a_leader_of__room_name_": "__username__ on nyt huoneen __room_name__ johtaja", + "User__username__is_now_a_moderator_of__room_name_": "__username__ on nyt huoneen __room_name__ moderaattori", + "User__username__is_now_an_owner_of__room_name_": "__username__ on nyt huoneen __room_name__ omistaja", "User__username__muted_in_room__roomName__": "Käyttäjä __username__ mykistetty huoneessa __roomName__", - "User__username__removed_from__room_name__leaders": "Käyttäjän __username__ poistettu __room_name__ -johtajista", + "User__username__removed_from__room_name__leaders": "__username__ poistettu huoneen __room_name__ johtajista", "User__username__removed_from__room_name__moderators": "Käyttäjä __username__ on poistettu huoneen __room_name__  valvojista", - "User__username__removed_from__room_name__owners": "Käyttäjä __username__ poistettu huoneen __room_name__ omistajista", + "User__username__removed_from__room_name__owners": "__username__ poistettu huoneen __room_name__ omistajista", "User__username__unmuted_in_room__roomName__": "Käyttäjän __username__ mykistys poistettu huoneessa __roomName__", "User_added": "Käyttäjä lisätty.", - "User_added_by": "Käyttäjä __user_added__ lisätty __user_by__ toimesta.", + "User_added_by": "__user_by__ lisäsi käyttäjän __user_added__.", "User_added_to": "lisätty __user_added__", "User_added_successfully": "Käyttäjän on lisätty", "User_and_group_mentions_only": "Vain käyttäjien ja ryhmien maininnat", @@ -4948,13 +5003,13 @@ "User_has_been_muted_in_s": "Käyttäjä on mykistetty kohteessa %s", "User_has_been_removed_from_s": "Käyttäjä on poistettu kohteesta %s", "User_has_been_removed_from_team": "Käyttäjä on poistettu tiimistä", - "User_has_been_unignored": "Käyttäjää ei enää jätetä huomiotta", - "User_Info": "Käyttäjän tiedot", + "User_has_been_unignored": "Käyttäjää ei enää ohiteta", + "User_Info": "Käyttäjätiedot", "User_Interface": "Käyttöliittymä", "User_is_blocked": "Käyttäjä on estetty", "User_is_no_longer_an_admin": "Käyttäjä ei ole enää järjestelmänvalvoja", - "User_is_now_an_admin": "Käyttäjä on nyt ylläpitäjä", - "User_is_unblocked": "Käyttäjä on estetty", + "User_is_now_an_admin": "Käyttäjä on nyt järjestelmänvalvoja", + "User_is_unblocked": "Käyttäjän esto on kumottu", "User_joined_channel": "Liittyi kanavalle.", "User_joined_conversation": "On liittynyt keskusteluun", "User_joined_team": "liittyi tähän tiimiin", @@ -4970,61 +5025,61 @@ "User_left_this_team": "poistui tästä tiimistä", "User_logged_out": "Käyttäjä on kirjautunut ulos", "User_management": "Käyttäjähallinta", - "User_mentions_only": "Käyttäjä mainitsee vain", - "User_muted": "Käyttäjä nollautuu", - "User_muted_by": "Käyttäjä __user_muted__ mykistetty __user_by__ toimesta.", + "User_mentions_only": "Vain käyttäjämaininnat", + "User_muted": "Käyttäjä mykistetty", + "User_muted_by": "__user_by__ mykisti käyttäjän __user_muted__.", "User_has_been_muted": "mykistetty __user_muted__", "User_not_found": "Käyttäjää ei löydy", "User_not_found_or_incorrect_password": "Käyttäjää ei löydy tai väärä salasana", "User_or_channel_name": "Käyttäjän tai kanavan nimi", "User_Presence": "Käyttäjän läsnäolo", "User_removed": "Käyttäjä poistettu", - "User_removed_by": "Käyttäjä __user_removed__ poistettu __user_by__ toimesta.", + "User_removed_by": "__user_by__ poisti käyttäjän __user_removed__.", "User_has_been_removed": "poistettu __user_removed__", - "User_sent_a_message_on_channel": "__username__ lähetti viestin __channel__", + "User_sent_a_message_on_channel": "__username__ lähetti viestin kanavalla __channel__", "User_sent_a_message_to_you": "__username__ lähetti sinulle viestin", "user_sent_an_attachment": "__user__ lähetti liitteen", "User_Settings": "Käyttäjän asetukset", "User_started_a_new_conversation": "__username__ aloitti uuden keskustelun", - "User_unmuted_by": "Käyttäjän __user_unmuted__ mykistys poistettu __user_by__ toimesta.", + "User_unmuted_by": "__user_by__ kumosi käyttäjän __user_unmuted__ mykistyksen.", "User_has_been_unmuted": "unmuted __user_unmuted__", "User_unmuted_in_room": "Käyttäjän mykistys poistettu huoneessa", "User_updated_successfully": "Käyttäjä on päivitetty", - "User_uploaded_a_file_on_channel": "__username__ lähetti tiedoston __channel__", + "User_uploaded_a_file_on_channel": "__username__ latasi tiedoston kanavalla __channel__", "User_uploaded_a_file_to_you": "__username__ lähetti sinulle tiedoston", - "User_uploaded_file": "Ladattu tiedosto", - "User_uploaded_image": "Lähetetty kuva", - "user-generate-access-token": "Käyttäjä luo käyttöoikeuskoodin", - "user-generate-access-token_description": "Käyttäjien käyttöoikeuksien luominen käyttöoikeusmerkkeihin", - "UserData_EnableDownload": "Ota käyttäjätietojen lataus käyttöön", - "UserData_FileSystemPath": "Järjestelmäpolku (vietyjä tiedostoja)", + "User_uploaded_file": "Latasi tiedoston", + "User_uploaded_image": "Latasi kuvan", + "user-generate-access-token": "Käyttäjän luoma käyttöoikeustietue", + "user-generate-access-token_description": "Käyttäjien oikeus luoda käyttöoikeustietueita", + "UserData_EnableDownload": "Ota käyttöön käyttäjätietojen lataus", + "UserData_FileSystemPath": "Järjestelmäpolku (viedyt tiedostot)", "UserData_FileSystemZipPath": "Järjestelmäpolku (pakattu tiedosto)", - "UserData_MessageLimitPerRequest": "Viesti rajoitus pyynnöstä", - "UserData_ProcessingFrequency": "Käsittelytaajuus (minuutti)", - "UserDataDownload": "Käyttäjän tietojen lataus", + "UserData_MessageLimitPerRequest": "Pyyntökohtainen viestirajoitus", + "UserData_ProcessingFrequency": "Käsittelytaajuus (minuuttia)", + "UserDataDownload": "Käyttäjätietojen lataus", "UserDataDownload_Description": "Määritykset, joilla sallitaan tai kielletään työtilan jäsenten työtilan tietojen lataaminen.", - "UserDataDownload_CompletedRequestExisted_Text": "Tietosi on jo luotu. Tarkista sähköpostitilisi latauslinkistä.", + "UserDataDownload_CompletedRequestExisted_Text": "Tietotiedostosi on jo luotu. Tarkista latauslinkki sähköpostitililtäsi.", "UserDataDownload_CompletedRequestExistedWithLink_Text": "Tiedostosi on jo luotu. Lataa se klikkaamalla tästä.", - "UserDataDownload_EmailBody": "Tiedostosi on nyt valmis ladattavaksi. Voit ladata sen napsauttamalla .", + "UserDataDownload_EmailBody": "Tietotiedostosi on nyt valmis ladattavaksi. Voit ladata sen napsauttamalla tätä.", "UserDataDownload_EmailSubject": "Tietosi on valmis ladattavaksi", "UserDataDownload_Requested": "Lataa pyydetty tiedosto", "UserDataDownload_Requested_Text": "Tiedostosi luodaan. Linkki sen lataamiseen lähetetään sähköpostiisi, kun se on valmis. Jonossa on __pending_operations__ operaatioita, jotka on tarkoitus suorittaa ennen sinun operaatiotasi.", "UserDataDownload_RequestExisted_Text": "Tiedostosi on jo luotu. Linkki sen lataamiseen lähetetään sähköpostiisi, kun se on valmis. Jonossa on __pending_operations__ operaatioita, jotka on tarkoitus suorittaa ennen sinun operaatiotasi.", "Username": "Käyttäjätunnus", - "Username_already_exist": "Käyttäjätunnus on jo olemassa. Yritä toista käyttäjätunnusta.", + "Username_already_exist": "Käyttäjätunnus on jo olemassa. Kokeile jotain muuta käyttäjätunnusta.", "Username_and_message_must_not_be_empty": "Käyttäjätunnus ja viesti eivät saa olla tyhjiä.", - "Username_cant_be_empty": "Käyttäjänimi ei voi olla tyhjä", - "Username_Change_Disabled": "Rocket.Chat ylläpitäjäsi on poistanut käyttäjätunnuksen vaihtamismahdollisuuden", - "Username_denied_the_OTR_session": "__username__ kieltäytyi Epävirallinen Keskustelu-istunnosta", + "Username_cant_be_empty": "Käyttäjätunnus ei voi olla tyhjä", + "Username_Change_Disabled": "Rocket.Chat-järjestelmänvalvoja on poistanut käytöstä käyttäjätunnusten vaihtamisen", + "Username_denied_the_OTR_session": "__username__ kielsi epävirallisen keskusteluistunnon", "Username_description": "Käyttäjätunnuksen avulla muut voivat mainita sinut viesteissä.", - "Username_doesnt_exist": "Käyttäjätunnusta `%s` ei ole olemassa.", + "Username_doesnt_exist": "Käyttäjätunnusta `%s` ei ole.", "Username_ended_the_OTR_session": "__username__ lopetti epävirallisen keskusteluistunnon", "Username_invalid": "%s ei ole kelvollinen käyttäjänimi,
 käytä vain kirjaimia, numeroita, pisteitä, yhdysviivoja ja alaviivoja", "Username_is_already_in_here": "`@%s` on jo täällä.", "Username_is_not_in_this_room": "Käyttäjä `#%s` ei ole tässä huoneessa.", - "Username_Placeholder": "Syötä käyttäjätunnukset..", + "Username_Placeholder": "Anna käyttäjätunnukset...", "Username_title": "Rekisteröi käyttäjätunnus", - "Username_wants_to_start_otr_Do_you_want_to_accept": "__username__ haluaa aloittaa Epävirallinen Keskustelu-keskustelun. Haluatko hyväksyä?", + "Username_wants_to_start_otr_Do_you_want_to_accept": "__username__ haluaa aloittaa epävirallisen keskustelun. Hyväksytkö sen?", "Users": "Käyttäjät", "Users must use Two Factor Authentication": "Käyttäjien on käytettävä kaksivaiheista todennusta", "Users_added": "Käyttäjät on lisätty", @@ -5038,35 +5093,38 @@ "Uses_left": "Käyttökertoja jäljellä", "UTC_Timezone": "UTC-aikavyöhyke", "Utilities": "Apuohjelmat", - "UTF8_Names_Slugify": "UTF8-nimien muunto slugiksi", + "UTF8_Names_Slugify": "Siisti UTF8-nimet", "UTF8_User_Names_Validation": "UTF8-käyttäjätunnusten validointi", "UTF8_User_Names_Validation_Description": "RegExp, jota käytetään käyttäjätunnusten validointiin", "UTF8_Channel_Names_Validation": "UTF8 kanavien nimien validointi", "UTF8_Channel_Names_Validation_Description": "RegExp, jota käytetään kanavien nimien validointiin", "Videocall_enabled": "Videopuhelu käytössä", - "Validate_email_address": "Validoi sähköpostiosoite", + "Validate_email_address": "Tarkista sähköpostiosoite", "Validation": "Validointi", "Value_messages": "__value__ viestit", "Value_users": "__value__ käyttäjät", "Verification": "Varmistus", - "Verification_Description": "Voit käyttää seuraavia paikanvaraajia:
  • [Verification_Url] vahvistus-URL-osoitteelle.
  • [nimi], [fname], [lname] käyttäjän koko nimen, etunimen tai sukunimen osalta.
  • [email] käyttäjän sähköposti.
  • [Sivuston nimi] ja [Sivusto_URL].
", - "Verification_Email": "Vahvista tilisi valitsemalla täällä.", + "Verification_Description": "Voit käyttää seuraavia paikkamerkkejä:
  • [Verification_Url] vahvistus-URL-osoitteen paikalla.
  • [name], [fname], [lname] käyttäjän koko nimen, etunimen tai sukunimen paikalla.
  • [email] käyttäjän sähköpostiosoitteen paikalla.
  • [Site_Name] ja [Site_URL] sovelluksen nimen ja URL-osoitteen paikalla.
", + "Verification_Email": "Vahvista sähköpostiosoitteesi napsauttamalla tätä.", "Verification_email_body": "Vahvista sähköpostiosoitteesi klikkaamalla alla olevaa painiketta.", "Verification_email_sent": "Vahvistusviesti lähetetty", - "Verification_Email_Subject": "[Site_Name] - vahvista tilisi", - "Verified": "Vahvistetut", - "Verify": "Varmista", + "Verification_Email_Subject": "[Site_Name] - sähköpostiosoitteen vahvistus", + "Verified": "Vahvistettu", + "Verify": "Vahvista", "Verify_your_email": "Vahvista sähköpostiosoitteesi", "Verify_your_email_for_the_code_we_sent": "Tarkista lähettämämme koodi sähköpostissasi", "Version": "Versio", "Version_version": "Versio __version__", + "App_version_incompatible_tooltip": "Sovellus ei ole yhteensopiva Rocket.Chat-version kanssa", "Video_Conference_Description": "Määritä konferenssipuhelut työtilaa varten.", "Video_Chat_Window": "Videokeskustelu", "Video_Conference": "Konferenssipuhelu", "Video_Conferences": "Konferenssipuhelut", + "Video_Conference_Info": "Kokoustiedot", + "Video_Conference_Url": "Kokouksen URL-osoite", "video-conf-provider-not-configured": "**Konferenssipuhelu ei ole käytössä**: Työtilan ylläpitäjän on ensin otettava konferenssipuhelut käyttöön.", "Video_message": "VIdeoviesti", - "Videocall_declined": "Videopuhelu hylätty", + "Videocall_declined": "Videopuhelu hylätty.", "Video_and_Audio_Call": "Video- ja äänipuhelu", "video_conference_started": "_Started a call._", "video_conference_started_by": "**__username__** _aloitti puhelun._", @@ -5100,8 +5158,8 @@ "view-import-operations_description": "Lupa tarkastella tuontitoimintoja", "view-omnichannel-contact-center": "Näytä Omnichannel Contact Center", "view-omnichannel-contact-center_description": "Lupa tarkastella Omnichannel Contact Centeriä ja olla vuorovaikutuksessa sen kanssa", - "View_Logs": "Katso lokit", - "View_mode": "Näkymätila", + "View_Logs": "Näytä lokit", + "View_mode": "Näytä tila", "View_original": "Näytä alkuperäinen", "View_the_Logs_for": "Näytä lokit: \"__name__\"", "view-all-teams": "Näytä kaikki Tiimit", @@ -5114,7 +5172,7 @@ "view-c-room_description": "Lupa tarkastella julkisia kanavia", "view-canned-responses": "Näytä esivalmistetut vastaukset", "view-canned-responses_description": "Lupa tarkastella esivalmistettuja vastauksia", - "view-d-room": "Näytä suoria viestejä", + "view-d-room": "Näytä suorat viestit", "view-d-room_description": "Lupa tarkastella suoria viestejä", "view-device-management": "Näytä laitehallinta", "view-device-management_description": "Lupa tarkastella laitehallinnan kojelautaa", @@ -5124,13 +5182,13 @@ "view-federation-data_description": "Lupa tarkastella federaation tietoja", "View_full_conversation": "Näytä koko keskustelu", "view-full-other-user-info": "Näytä täydelliset muut käyttäjän tiedot", - "view-full-other-user-info_description": "Käyttöoikeus muiden käyttäjien täydellisen profiilin katseluun, mukaan lukien tilin luomispäivä, viimeinen sisäänkirjautuminen jne.", + "view-full-other-user-info_description": "Oikeus tarkastella muiden käyttäjien täydellistä profiilia, mukaan lukien tilin luontipäivä, viimeinen sisäänkirjautuminen jne.", "view-history": "Näytä historia", - "view-history_description": "Lupa tarkastella kanavan historiaa", + "view-history_description": "Oikeus tarkastella kanavan historiaa", "view-join-code": "Näytä liittymiskoodi", - "view-join-code_description": "Lupa katsella kanavan liittymiskoodia", + "view-join-code_description": "Oikeus tarkastella kanavan liittymiskoodia", "view-joined-room": "Katso yhdistetty huone", - "view-joined-room_description": "Lupa tarkastella tällä hetkellä liittyneitä kanavia", + "view-joined-room_description": "Oikeus tarkastella tällä hetkellä liittyneitä kanavia", "view-l-room": "Näytä Omnichannel-huoneet Room", "view-l-room_description": "Lupa tarkastella Omnichannel-huoneita", "view-livechat-analytics": "Näytä Omnichannel-analytiikka", @@ -5170,21 +5228,21 @@ "view-livechat-unit": "Näytä Livechat-yksiköt", "view-logs": "Näytä lokit", "view-logs_description": "Palvelimen lokien katselulupa ", - "view-other-user-channels": "Katsele muita käyttäjäkanavia", - "view-other-user-channels_description": "Käyttöoikeus muiden käyttäjien omistamien kanavien katselemiseen", + "view-other-user-channels": "Näytä muiden käyttäjien kanavat", + "view-other-user-channels_description": "Oikeus tarkastella muiden käyttäjien omistamia kanavia", "view-outside-room": "Näkymä huoneen Room ulkopuolelle", "view-outside-room_description": "Lupa tarkastella nykyisen huoneen ulkopuolisia käyttäjiä", "view-p-room": "Näytä yksityinen huone Room", "view-p-room_description": "Lupa tarkastella yksityisiä kanavia", - "view-privileged-setting": "Näytä etuoikeutettu asetus", + "view-privileged-setting": "Näytä oikeusasetus", "view-privileged-setting_description": "Lupa tarkastella asetuksia", - "view-room-administration": "Katso huonehallinto", - "view-room-administration_description": "Lupa tarkastella julkisia, yksityisiä ja suoramainoksia. Ei sisällä kykyä tarkastella keskusteluja tai arkistoja", + "view-room-administration": "Näytä huoneen hallinta", + "view-room-administration_description": "Oikeus tarkastella julkisten, yksityisten ja suorien mainosten tilastoja. Ei sisällä oikeutta tarkastella keskusteluja tai arkistoja", "view-statistics": "Näytä tilastot", - "view-statistics_description": "Lupa tarkastella järjestelmätilastoja, kuten kirjautuneen käyttäjän lukumäärää, huoneiden lukumäärää, käyttöjärjestelmän tietoja", + "view-statistics_description": "Oikeus tarkastella järjestelmätilastoja, kuten kirjautuneiden käyttäjien määrää, huoneiden määrää tai käyttöjärjestelmätietoja", "view-user-administration": "Näytä käyttäjänhallinta", - "view-user-administration_description": "Käyttöoikeus osittaiseen, vain luku -tiluetteloon järjestelmään tällä hetkellä kirjautuvista käyttäjistä. Käyttäjätietojen tietoja ei ole käytettävissä tällä luvalla", - "Viewing_room_administration": "Katseluhuoneen hallinta", + "view-user-administration_description": "Oikeus osittaiseen vain luku -luettelonäkymään järjestelmään kirjautuneista käyttäjistä. Tällä oikeudella ei ole pääsyä käyttäjätilitietoihin", + "Viewing_room_administration": "Tarkastellaan huoneen hallintaa", "Visibility": "Näkyvyys", "Visible": "Näkyvä", "Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today": "Siirry osoitteeseen __Site_URL__ ja kokeile parasta avoimen lähdekoodin keskusteluratkaisua jo tänään!", @@ -5197,7 +5255,7 @@ "Visitor_does_not_exist": "Vierailijaa ei ole olemassa!", "Visitor_Navigation": "Vierasnavigointi", "Visitor_page_URL": "Vierailijan sivun URL", - "Visitor_time_on_site": "Vieraan aika sivustossa", + "Visitor_time_on_site": "Vierailijan aika sivustossa", "Voice_Call": "Äänipuhelu", "VoIP_Enable_Keep_Alive_For_Unstable_Networks": "Ota SIP-asetuksissa käyttöön Keep-Alive", "VoIP_Enable_Keep_Alive_For_Unstable_Networks_Description": "Seuraa useiden ulkoisten SIP-yhdyskäytävien tilaa lähettämällä säännöllisiä SIP OPTIONS -viestejä. Käytetään epävakaissa verkoissa.", @@ -5231,6 +5289,7 @@ "VoIP_Toggle": "VoIP:n ottaminen käyttöön/pois käytöstä", "Chat_opened_by_visitor": "Vierailijan avaama keskustelu", "Wait_activation_warning": "Ennen kuin voit kirjautua, järjestelmänvalvojan on aktivoitava tilisi manuaalisesti.", + "Waiting_for_answer": "Odotetaan vastausta", "Waiting_queue": "Odotusjonossa", "Waiting_queue_message": "Odotusjonon viesti", "Waiting_queue_message_description": "Viesti, joka näytetään kävijöille, kun he joutuvat jonoon", @@ -5256,28 +5315,28 @@ "webdav-account-saved": "WebDAV-tili tallennettu", "webdav-account-updated": "WebDAV-tili päivitetty", "Webhook_Details": "WebHookin tiedot", - "Webhook_URL": "Webhook URL", + "Webhook_URL": "Webhook-URL", "Webhooks": "Webhookit", "WebRTC": "WebRTC", "WebRTC_Description": "Lähetä ääni- ja/tai videomateriaalia sekä välitä mielivaltaista dataa selaimien välillä ilman välikäsiä.", "WebRTC_Call": "WebRTC-puhelu", "WebRTC_Call_unavailable_for_federation": "WebRTC-puhelu ei ole käytettävissä Federoiduissa-huoneissa", - "WebRTC_direct_audio_call_from_%s": "Suora äänipuhelu%s: stä", - "WebRTC_direct_video_call_from_%s": "Suora videopuhelu%s: stä", + "WebRTC_direct_audio_call_from_%s": "Suora äänipuhelu kohteesta %s", + "WebRTC_direct_video_call_from_%s": "Suora videopuhelu kohteesta %s", "WebRTC_Enable_Channel": "Ota käyttöön julkisilla kanavilla", - "WebRTC_Enable_Direct": "Ota käyttöön yksityisviesteissä", - "WebRTC_Enable_Private": "Ota käyttöön privaattiryhmissä", - "WebRTC_group_audio_call_from_%s": "Ryhmän äänipuhelu%s: stä", - "WebRTC_group_video_call_from_%s": "Ryhmän videopuhelu%s: stä", + "WebRTC_Enable_Direct": "Ota käyttöön suorissa viesteissä", + "WebRTC_Enable_Private": "Ota käyttöön yksityisillä kanavilla", + "WebRTC_group_audio_call_from_%s": "Ryhmän äänipuhelu kohteesta %s", + "WebRTC_group_video_call_from_%s": "Ryhmän videopuhelu kohteesta %s", "WebRTC_monitor_call_from_%s": "Seuraa puhelua %s: stä", "WebRTC_Servers": "STUN/TURN-palvelimet", "WebRTC_Servers_Description": "Pilkulla eroteltu luettelo STUN- ja TURN-palvelimista.
Käyttäjätunnus, salasana ja portti ovat sallittuja muodossa `username:password@stun:host:port` tai `username:password@turn:host:port`.", "WebRTC_call_ended_message": " Puhelu päättyi klo __endTime__ - Lasted __callDuration__", "WebRTC_call_declined_message": " Yhteyshenkilö hylkäsi puhelun.", - "Website": "Verkkosivusto", + "Website": "Sivusto", "Wednesday": "Keskiviikko", "Weekly_Active_Users": "Viikoittaiset aktiiviset käyttäjät", - "Welcome": "Tervetuloa %s.", + "Welcome": "Tervetuloa, %s.", "Welcome_to": "Tervetuloa sivustolle __Site_Name__", "Welcome_to_the": "Tervetuloa:", "When": "Kun", @@ -5285,14 +5344,14 @@ "When_is_the_chat_busier?": "Milloin chat on vilkkaampi?", "Where_are_the_messages_being_sent?": "Minne viestit lähetetään?", "Why_did_you_chose__score__": "Miksi valitsit __score__?", - "Why_do_you_want_to_report_question_mark": "Miksi haluat ilmoittaa viestistä?", + "Why_do_you_want_to_report_question_mark": "Miksi haluat tehdä ilmoituksen?", "Will_Appear_In_From": "Ilmestyy lähettämiesi sähköpostiviestien From: -otsikkoon.", "will_be_able_to": "mahdollistaa", "Will_be_available_here_after_saving": "Saatavilla täällä tallennuksen jälkeen.", "Without_priority": "Ilman prioriteettia", "Workspace_now_using_device_management": "Työtila käyttää nyt laitehallintaa", "Worldwide": "Maailmanlaajuinen", - "Would_you_like_to_return_the_inquiry": "Haluatko palauttaa tiedustelun?", + "Would_you_like_to_return_the_inquiry": "Haluatko palauttaa kyselyn?", "Would_you_like_to_return_the_queue": "Haluatteko siirtää tämän huoneen takaisin jonoon? Kaikki keskusteluhistoria säilyy huoneessa.", "Would_you_like_to_place_chat_on_hold": "Haluaisitko asettaa tämän keskustelun pitoon?", "Wrap_up_the_call": "Puhelun päättäminen", @@ -5302,13 +5361,13 @@ "Yes_archive_it": "Kyllä, arkistoi se!", "Yes_clear_all": "Kyllä, tyhjennä kaikki!", "Yes_deactivate_it": "Kyllä, kytke se pois päältä!", - "Yes_delete_it": "Kyllä, poista!", - "Yes_hide_it": "Kyllä, piilota!", + "Yes_delete_it": "Kyllä, poista se!", + "Yes_hide_it": "Kyllä, piilota se!", "Yes_leave_it": "Kyllä, poistu!", "Yes_mute_user": "Kyllä, mykistä käyttäjä!", - "Yes_prune_them": "Kyllä, leikkaa ne!", + "Yes_prune_them": "Kyllä, karsi ne!", "Yes_remove_user": "Kyllä, poista käyttäjä!", - "Yes_unarchive_it": "Kyllä, purkaa se uudelleen!", + "Yes_unarchive_it": "Kyllä, palauta se arkistosta!", "yesterday": "eilen", "Yesterday": "Eilen", "You": "Sinä", @@ -5323,24 +5382,24 @@ "you_are_in_preview_please_insert_the_password": "Syötä salasana", "you_are_in_preview_mode_of_incoming_livechat": "Olet tämän keskustelun esikatselutilassa", "You_are_logged_in_as": "Olet kirjautunut käyttäjänä", - "You_are_not_authorized_to_view_this_page": "Sinulla ei ole oikeuksia tarkastella tätä sivua.", + "You_are_not_authorized_to_view_this_page": "Sinulla ei ole valtuuksia tarkastella tätä sivua.", "You_can_change_a_different_avatar_too": "Voit ohittaa avatarin, jota käytetään julkaisemiseen tässä integraatiossa.", "You_can_close_this_window_now": "Voit sulkea tämän ikkunan nyt.", - "You_can_search_using_RegExp_eg": "Voit etsiä käyttämällä RegExp-lausekkeita esim. /^text$/i", + "You_can_search_using_RegExp_eg": "Voit käyttää haussa säännöllisiä lausekkeita. kuten /^text$/i", "You_can_try_to": "Voit yrittää", "You_can_use_an_emoji_as_avatar": "Voit käyttää avatarina myös emojia.", - "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Webhooksien avulla voit integroida monikanavan helposti asiakkuudenhallintaasi.", - "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Et voi lähteä livechat-huoneesta. Ole hyvä ja käytä \"sulje\"-painiketta.", + "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Webhookien avulla voit integroida Omnichannelin helposti asiakkuudenhallintaasi.", + "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Et voi poistua Omnichannel-huoneesta. Käytä sulkupainiketta.", "You_followed_this_message": "Seurasit tätä viestiä.", "You_have_a_new_message": "Sinulle on uusi viesti", "You_have_been_muted": "Sinut on mykistetty, et voi puhua tässä huoneessa", "You_have_joined_a_new_call_with": "Olet liittynyt uuteen puheluun", - "You_have_n_codes_remaining": "Sinulla on __number__ koodit jäljellä.", - "You_have_not_verified_your_email": "Et ole vahvistanut sähköpostiasi.", - "You_have_successfully_unsubscribed": "Sinut on poistettu onnistuneesti postituslistaltamme.", - "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Sinun on ensin määritettävä API-merkki, jotta integraatiota käytetään.", - "You_must_join_to_view_messages_in_this_channel": "Sinun täytyy liittyä katsomaan tämän kanavan viestejä", - "You_need_confirm_email": "Sinun tulee vahvistaa sähköpostiosoitteesi kirjautuaksesi!", + "You_have_n_codes_remaining": "Sinulla on jäljellä __number__ koodia.", + "You_have_not_verified_your_email": "Et ole vahvistanut sähköpostiosoitettasi.", + "You_have_successfully_unsubscribed": "Olet peruuttanut postituslistamme tilauksen.", + "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Määritä API-tunnus, jotta voit käyttää integraatiota.", + "You_must_join_to_view_messages_in_this_channel": "Kanavan viestien tarkastelu edellyttää liittymistä", + "You_need_confirm_email": "Vahvista sähköpostiosoitteesi, jotta voit kirjautua!", "You_need_install_an_extension_to_allow_screen_sharing": "Asenna laajennus, joka sallii näytön jakamisen", "You_need_to_change_your_password": "Sinun on vaihdettava salasanasi", "You_need_to_type_in_your_password_in_order_to_do_this": "Anna salasanasi, jotta voit tehdä tämän!", @@ -5352,7 +5411,7 @@ "You_should_name_it_to_easily_manage_your_integrations": "Helpota integrointiesi hallintaa nimeämällä se.", "You_unfollowed_this_message": "Lopetit tämän viestin seuraamisen.", "You_will_be_asked_for_permissions": "Sinulta kysytään lupia", - "You_will_not_be_able_to_recover": "Viestin palauttaminen ei ole mahdollista!", + "You_will_not_be_able_to_recover": "Et voi palauttaa tätä viestiä!", "You_will_not_be_able_to_recover_email_inbox": "Et voi palauttaa tätä sähköpostilaatikkoa", "You_will_not_be_able_to_recover_file": "Et voi palauttaa tätä tiedostoa!", "You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "Et saa sähköposti-ilmoituksia, koska et ole vahvistanut sähköpostiosoitettasi.", @@ -5362,7 +5421,7 @@ "Your_entry_has_been_deleted": "Merkintäsi on poistettu.", "Your_file_has_been_deleted": "Tiedostosi on poistettu.", "Your_invite_link_will_expire_after__usesLeft__uses": "Kutsulinkkisi vanhenee __usesLeft__ käyttökerran jälkeen.", - "Your_invite_link_will_expire_on__date__": null, + "Your_invite_link_will_expire_on__date__": "Kutsulinkkisi vanhenee __date__.", "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Kutsulinkkisi vanhenee __date__ tai __usesLeft__ käyttökerran jälkeen.", "Your_invite_link_will_never_expire": "Kutsulinkkisi ei vanhene koskaan.", "Your_mail_was_sent_to_s": "Sähköposti lähetettiin osoitteeseen %s", @@ -5373,22 +5432,40 @@ "Your_password_was_changed_by_an_admin": "Ylläpito on vaihtanut salasanasi.", "Your_push_was_sent_to_s_devices": "Push-viestisi lähetettiin %s laitteeseen", "Your_question": "Kysymyksesi", - "Your_server_link": "Palvelimesi linkki", + "Your_server_link": "Palvelinlinkkisi", "Your_temporary_password_is_password": "Väliaikainen salasanasi on [password].", "Your_TOTP_has_been_reset": "Kaksivaiheisen todennuksen TOTP on nollattu.", "Your_web_browser_blocked_Rocket_Chat_from_opening_tab": "Selaimesi esti chatsovellusta avaamasta uutta välilehteä.", "Your_workspace_is_ready": "Työtila on valmis käytettäväksi 🎉", "Zapier": "Zapier", "registration.page.login.errors.wrongCredentials": "Käyttäjää ei löydy tai virheellinen salasana", + "registration.page.login.errors.loginBlockedForIp": "Sisäänkirjautuminen tästä IP-osoitteesta on estetty tilapäisesti", + "registration.page.login.errors.loginBlockedForUser": "Tämän käyttäjän sisäänkirjautuminen on estetty tilapäisesti", + "registration.page.login.errors.licenseUserLimitReached": "Käyttäjien enimmäismäärä on saavutettu.", + "registration.page.login.errors.AppUserNotAllowedToLogin": "Sovelluksen käyttäjät eivät saa kirjautua sisään suoraan.", "registration.page.registration.waitActivationWarning": "Ennen kuin voit kirjautua, järjestelmänvalvojan on aktivoitava tilisi manuaalisesti.", - "registration.page.resetPassword.sent": "Jos tämä sähköposti on rekisteröity, lähetämme ohjeet salasanan palauttamiseen. Jos et saa sähköpostia pian, palaa takaisin ja yritä uudelleen.", + "registration.page.login.register": "Oletko uusi käyttäjä? <1>Luo tili", + "registration.page.login.forgot": "Unohditko salasanasi?", + "registration.page.register.back": "Takaisin kirjautumiseen", + "registration.page.emailVerification.subTitle": "Tämä palvelin edellyttää vahvistettuja sähköpostiosoitteita. Tarkista vahvistuslinkki sähköpostistasi.", + "registration.page.emailVerification.sent": "Vahvistusviesti on lähetetty, tarkista sähköpostisi.", + "registration.page.resetPassword.sent": "Jos tämä sähköpostiosoite on rekisteröity, lähetämme salasanan nollausohjeet. Jos et saa sähköpostia pian, palaa ja yritä uudelleen.", + "registration.page.resetPassword.sendInstructions": "Lähetä ohjeet", + "registration.page.resetPassword.errors.invalidEmail": "Virheellinen sähköpostiosoite", + "registration.page.poweredBy": "Palvelun tarjoaa <1>Rocket.Chat", + "registration.component.welcome": "Tervetuloa <1>Rocket.Chat-työtilaan", "registration.component.login": "Kirjaudu", "registration.component.login.userNotFound": "Käyttäjää ei löydy", + "registration.component.login.incorrectPassword": "Väärä salasana", + "registration.component.switchLanguage": "Vaihda kieleksi <1>en", "registration.component.resetPassword": "Nollaa salasana", - "registration.component.form.email": "Sähköposti", + "registration.component.form.emailOrUsername": "Sähköpostiosoite tai käyttäjätunnus", + "registration.component.form.email": "Sähköpostiosoite", + "registration.component.form.emailPlaceholder": "esimerkki@esimerkki.com", "registration.component.form.password": "Salasana", "registration.component.form.divider": "tai", "registration.component.form.submit": "Lähetä", + "registration.component.form.requiredField": "Tämä kenttä on pakollinen", "onboarding.component.form.requiredField": "Tämä kenttä on pakollinen", "onboarding.component.form.steps": "Vaihe {{currentStep}} / {{stepCount}}", "onboarding.component.form.action.back": "Takaisin", @@ -5465,5 +5542,9 @@ "onboarding.form.standaloneServerForm.publishOwnApp": "Jotta voit lähettää push-ilmoituksia, sinun on koottava ja julkaistava oma sovelluksesi Google Play- ja App Store -sovelluksissa", "onboarding.form.standaloneServerForm.manuallyIntegrate": "Tarve integroida manuaalisesti ulkoisiin palveluihin", "Something_Went_Wrong": "Jokin meni pieleen", - "Toolbox_room_actions": "Ensisijaiset huoneen Room toimet" + "Toolbox_room_actions": "Ensisijaiset huoneen Room toimet", + "Theme_light": "Vaalea", + "Theme_dark": "Tumma", + "Join_your_team": "Liity tiimiisi", + "Create_an_account": "Luo tili" } \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/fr.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/fr.i18n.json index 1948afbf950e..18efb3e847b2 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -292,7 +292,6 @@ "Additional_emails": "Adresses e-mail supplémentaires", "Additional_Feedback": "Commentaires supplémentaires", "additional_integrations_Bots": "Si vous cherchez à intégrer votre propre bot, alors ne cherchez pas plus loin que notre adaptateur Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Vous voulez intégrer d'autres logiciels et applications avec Rocket.Chat mais vous n'avez pas le temps de le faire manuellement ? Nous vous suggérons d'utiliser Zapier, que nous prenons entièrement en charge. Consultez notre documentation pour en savoir plus. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Votre administrateur n'a pas activé le chiffrement de bout en bout (E2E).", "Admin_Info": "Infos admin.", "Administration": "Administration", @@ -700,7 +699,6 @@ "Busy": "Occupé", "By": "Par", "by": "par", - "By_author": "Par __author__", "cache_cleared": "Cache effacé", "Call": "Appeler", "Calling": "Appel", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/hr.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/hr.i18n.json index 85d3bc3c61fd..9c1c66bee911 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/hr.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/hr.i18n.json @@ -224,7 +224,6 @@ "Additional_emails": "Dodatni Emailovi", "Additional_Feedback": "Dodatne povratne informacije", "additional_integrations_Bots": "Ako tražite kako integrirati svoj bot, nemojte gledati dalje od našeg Hubot adaptera. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Tražite li integrirati drugi softver i aplikacije s Rocket.Chat, ali nemate vremena za ručno raditi? Onda predlažemo da koristite Zapier koji u potpunosti podržavamo. Pročitajte više o našoj dokumentaciji. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Vaš administrator nije omogućio E2E šifriranje.", "Admin_Info": "Informacije o administratoru", "Administration": "Administracija", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/hu.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/hu.i18n.json index 444c52e1d494..23ebeb74411f 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/hu.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/hu.i18n.json @@ -317,7 +317,6 @@ "Additional_emails": "További e-mail-címek", "Additional_Feedback": "További visszajelzés", "additional_integrations_Bots": "Ha azt keresi, hogy saját robotokat hogyan lehet integrálni, akkor ne keresse tovább, hanem nézze meg a Hubot adapterünket: https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Szeretne más szoftvereket és alkalmazásokat integrálni a Rocket.Chat programmal, de nincs ideje kézzel megcsinálni? Akkor a Zapier használatát javasoljuk, amelyet teljes mértékben támogatunk. Tudjon meg többet róla a dokumentációnkból: https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Az adminisztrátor nem engedélyezte a végpontok közötti titkosítást.", "Admin_Info": "Adminisztrátor-információk", "admin-no-active-video-conf-provider": "**A konferenciahívás nincs engedélyezve**: állítsa be a konferenciahívásokat, hogy elérhetővé tegye ezen a munkaterületen.", @@ -774,7 +773,6 @@ "Buy": "Vétel", "By": "–", "by": "–", - "By_author": "– __author__", "cache_cleared": "Gyorsítótár törölve", "Call": "Hívás", "Call_back": "Visszahívás", @@ -5542,4 +5540,4 @@ "Theme_dark": "Sötét", "Join_your_team": "Csatlakozás csapathoz", "Create_an_account": "Fiók létrehozása" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/id.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/id.i18n.json index 097a36fec03c..dbee3ff3b8e2 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/id.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/id.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Email tambahan", "Additional_Feedback": "tambahan Masukan", "additional_integrations_Bots": "Jika Anda mencari cara mengintegrasikan bot Anda sendiri, maka tidak perlu mencari lebih jauh dari adaptor Hubot kami. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Apakah Anda ingin mengintegrasikan perangkat lunak dan aplikasi lain dengan Rocket.Chat tetapi Anda tidak punya waktu untuk melakukannya secara manual? Kemudian kami sarankan menggunakan Zapier yang kami dukung sepenuhnya. Baca lebih lanjut tentang itu di dokumentasi kami. https://rocket.chat/docs/administrator-guides/integrations / zapier / menggunakan-zaps /", "Admin_Info": "Info Admin", "Administration": "Administrasi", "Adult_images_are_not_allowed": "Gambar dewasa tidak diperbolehkan", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/it.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/it.i18n.json index 8e03385bf96b..d39b0025ba12 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/it.i18n.json @@ -239,7 +239,6 @@ "Additional_emails": "Email aggiuntive", "Additional_Feedback": "Feedback aggiuntivo", "additional_integrations_Bots": "Se stai cercando come integrare il tuo bot, non cercare oltre il nostro adattatore Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Stai cercando di integrare altri software e applicazioni con Rocket.Chat ma non hai il tempo di farlo manualmente? Quindi ti suggeriamo di utilizzare Zapier che supportiamo pienamente. Leggi di più sulla nostra documentazione. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Il tuo amministratotre non ha abilitato la criptazione E2E", "Admin_Info": "Informazioni di amministrazione", "Administration": "Amministrazione", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ja.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ja.i18n.json index 09c267f90499..9103f53d2980 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ja.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ja.i18n.json @@ -291,7 +291,6 @@ "Additional_emails": "追加のメールアドレス", "Additional_Feedback": "その他のフィードバック", "additional_integrations_Bots": "自前のボットを統合する方法をお探しの場合は、当社のHubotアダプターをご使用ください。https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "他のソフトウェアやアプリケーションをRocket.Chatと統合しようとしていますが、手作業で行う時間がない場合は、当社が完全にサポートしているZapierを使用することをお勧めします。詳細については、ドキュメントを参照してください。 https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "管理者はE2E暗号化を有効にしていません。", "Admin_Info": "管理者情報", "Administration": "管理", @@ -697,7 +696,6 @@ "Busy": "取り込み中", "By": "による", "by": "による", - "By_author": "__author__による", "cache_cleared": "キャッシュがクリアされました", "Call": "通話", "Calling": "通話中", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ka-GE.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ka-GE.i18n.json index c0e9b83f101c..ff6543f7cf30 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ka-GE.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ka-GE.i18n.json @@ -262,7 +262,6 @@ "Additional_emails": "დამატებითი ელ.ფოსტები", "Additional_Feedback": "დამატებითი უკუკავშირი", "additional_integrations_Bots": "თუ თქვენ ეძებთ თუ როგორ ჩაამატოთ თქვენი საკუთარი ბოტი, მაშინ ნახეთ ჩვენი ჰაბოტ გადამყვანი. https://github.com/RocketChat/hubot-rocketchat ", - "additional_integrations_Zapier": "გსურთ სხვა პროგრამული უზრუნველყოფისა და პროგრამების Rocket.Chat– ში ინტეგრაცია, მაგრამ ამის ხელით სკეთებლად დრო არ გაქვთ? ჩვენ გთავაზობთ Zapier გამოყენებას, რომელსაც ჩვენ სრულად ვუჭერთ მხარს. დაწვრილებით ამის შესახებ ჩვენს დოკუმენტაციაზე წაიკითხეთ. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/ ", "Admin_disabled_encryption": "თქვენს ადმინისტრატორს არ გაუაქტიურებია E2E დაშიფვრა.", "Admin_Info": "ადმინისტრატორის ინფორმაცია", "Administration": "ადმინისტრაცია", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/km.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/km.i18n.json index 9abf59eed3e9..4cc57c39a4a5 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/km.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/km.i18n.json @@ -246,7 +246,6 @@ "Additional_emails": "បន្ថែមអ៊ីមែល", "Additional_Feedback": "មតិបន្ថែម", "additional_integrations_Bots": "ប្រសិនបើអ្នកកំពុងស្វែងរកវិធីបញ្ចូលប៊ិចផ្ទាល់ខ្លួនរបស់អ្នកនោះអ្នកនឹងរកមើលបន្ថែមទៀតមិនមែនអាដាប់ធ័រ Hubot របស់យើងទេ។ https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "តើអ្នកកំពុងសម្លឹងរកមើលការរួមបញ្ចូលកម្មវិធីនិងកម្មវិធីផ្សេងៗជាមួយ Rocket.Chat ប៉ុន្តែអ្នកមិនមានពេលធ្វើដោយដៃទេ? បន្ទាប់យើងស្នើឱ្យប្រើ Zapier ដែលយើងគាំទ្រយ៉ាងពេញទំហឹង។ អានបន្ថែមអំពីវានៅលើឯកសាររបស់យើង។ https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "អ្នកគ្រប់គ្រងមិនបានបើកដំណើរការ ការបំលែងកូដ E2E។", "Admin_Info": "ព័ត៌មានអ្នកគ្រប់គ្រង", "Administration": "រដ្ឋបាល", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ko.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ko.i18n.json index 1b74d36467d8..d070fb9a8621 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ko.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ko.i18n.json @@ -279,7 +279,6 @@ "Additional_emails": "추가 이메일", "Additional_Feedback": "추가 의견", "additional_integrations_Bots": "직접 만든 봇을 통합하는 방법을 찾고 있다면 Hubot 어댑터를 사용해보세요. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "다른 소프트웨어 및 응용 프로그램을 Rocket.Chat과 통합하려고하지만 수동으로 수행 할 시간이 없습니까? 우리가 완전히 지원하는 Zapier를 사용하는 것이 좋습니다. 자세한 내용은 설명서를 참조하십시오. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "관리자가 E2E 암호화를 활성화하지 않았습니다.", "Admin_Info": "관리자 정보", "Administration": "관리", @@ -632,7 +631,6 @@ "busy": "바쁨", "Busy": "바쁨", "by": "으로", - "By_author": "작성자 __author__", "cache_cleared": "캐시가 삭제됨", "Call": "요청", "call-management": "통화 관리", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ku.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ku.i18n.json index cccf6442d432..7b7547822e4d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ku.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ku.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Additional E-mailên", "Additional_Feedback": "Feedback Additional", "additional_integrations_Bots": "Ger hûn li benda ku hûn çawa botê xwe bine hevgirtin, lê hingê bila adapterê Hubot nabînin. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Ma hûn li benda ku hûn bi Rocket re hevpeymanên din re bicih bikin. Lê belê wextê we ne ku mirov bixwe bikî? Piştre em bi karanîna Zapierê dikin ku em bi tevahî piştgirî dikin. Di derbarê belgeyên me de bêtir bixwînin. https://rocket.chat/docs/administrator-guides/integrations / zapier / bikaranîna zaps /", "Admin_Info": "Agahdariya Rêveberiyê", "Administration": "Birêvebirî", "Adult_images_are_not_allowed": "Wêneyên mezin têne destûr kirin", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/lo.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/lo.i18n.json index 0a3d3e201a42..f6a5687a1ad5 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/lo.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/lo.i18n.json @@ -226,7 +226,6 @@ "Additional_emails": "ອີເມລເພີ່ມເຕີມ", "Additional_Feedback": "ຜົນຕອບຮັບເພີ່ມເຕີມ", "additional_integrations_Bots": "ຖ້າທ່ານກໍາລັງຊອກຫາວິທີການບູລະນາການຂອງຕົນເອງ, ຫຼັງຈາກນັ້ນເບິ່ງບໍ່ມີຫຍັງອີກຕໍ່ໄປກ່ວາຕົວປ່ຽນ Hubot ຂອງພວກເຮົາ. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "ທ່ານກໍາລັງຊອກຫາການເຊື່ອມໂຍງກັບຊອບແວແລະແອັບພລິເຄຊັນອື່ນໆທີ່ມີ RocketChat ແຕ່ທ່ານບໍ່ມີເວລາທີ່ຈະເຮັດມັນດ້ວຍຕົນເອງບໍ? ຫຼັງຈາກນັ້ນ, ພວກເຮົາແນະນໍາໃຫ້ນໍາໃຊ້ Zapier ທີ່ພວກເຮົາສະຫນັບສະຫນູນຢ່າງເຕັມສ່ວນ. ອ່ານເພີ່ມເຕີມກ່ຽວກັບເອກະສານຂອງພວກເຮົາ. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Admin Info", "Administration": "ການບໍລິຫານ", "Adult_images_are_not_allowed": "ຮູບພາບຜູ້ໃຫຍ່ບໍ່ໄດ້ອະນຸຍາດໃຫ້", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/lt.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/lt.i18n.json index 715ad71e126d..6a4ebab1afbc 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/lt.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/lt.i18n.json @@ -247,7 +247,6 @@ "Additional_emails": "Papildomi el. Laiškai", "Additional_Feedback": "Papildoma nuomonė", "additional_integrations_Bots": "Jei ieškote, kaip integruoti savo robotą, tada atrodykite ne toliau, kaip mūsų Hubot adapteris. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Ar norite integruoti kitą programinę įrangą ir programas su \"Rocket.Chat\", bet jūs neturite laiko rankiniu būdu tai padaryti? Tada mes rekomenduojame naudoti \"Zapier\", kurį mes visiškai palaikome. Skaitykite daugiau apie tai mūsų dokumentuose. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Administratoriaus informacija", "Administration": "Administracija", "Adult_images_are_not_allowed": "Suaugusieji vaizdai neleidžiami", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/lv.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/lv.i18n.json index d2fd8f89bc30..7491f2d7164d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/lv.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/lv.i18n.json @@ -220,7 +220,6 @@ "Additional_emails": "Papildu e-pasta ziņojumi", "Additional_Feedback": "Papildu atsauksmes", "additional_integrations_Bots": "Ja Jūs meklējat, kā integrēt savu botu, tad varat nemeklēt tālāk par mūsu Hubot adapteri. https://github.com/RocketChat/hubot-rocketchat ", - "additional_integrations_Zapier": "Vai Jūs vēlaties integrēt citu programmatūru un lietotnes ar Rocket.Chat, bet jums nav laika to izdarīt manuāli? Tad mēs iesakām izmantot Zapier, kuru mēs pilnībā atbalstām. Uzziniet vairāk par to mūsu dokumentācijā. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Aministratora info", "Administration": "Administrācija", "Adult_images_are_not_allowed": "Pieauguša satura attēli nav atļauti", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/mn.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/mn.i18n.json index d98e574f4db1..ad8d0b1023f5 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/mn.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/mn.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Нэмэлт мэйлүүд", "Additional_Feedback": "Нэмэлт санал хүсэлт", "additional_integrations_Bots": "Хэрэв та өөрийн браузыг яаж нэгтгэхийг хайж байгаа бол манай Hubot адаптераас цааш харагдахгүй болно. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Та бусад програм хангамж, програмыг Рокеттай холбохыг хүсч байна уу? Та гараар үүнийг хийх цаг байхгүй байна уу? Дараа нь бид бүрэн дэмждэг Zapier-г ашиглахыг санал болгож байна. Манай баримтжуулалтын талаар дэлгэрэнгүй уншина уу. https://rocket.chat/docs/administrator-guides/integration / zapier / using-zaps /", "Admin_Info": "Admin Info", "Administration": "Захиргаа", "Adult_images_are_not_allowed": "Насанд хүрэгчдийн зургийг зөвшөөрөхгүй", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ms-MY.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ms-MY.i18n.json index 2f30af56200c..d73c8d760c48 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ms-MY.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ms-MY.i18n.json @@ -214,7 +214,6 @@ "Additional_emails": "Tambahan E-mel", "Additional_Feedback": "Maklum balas tambahan", "additional_integrations_Bots": "Sekiranya anda sedang mencari cara mengintegrasikan bot anda sendiri, maka jangan lagi melihat penyesuai Hubot kami. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Adakah anda ingin mengintegrasikan perisian dan aplikasi lain dengan Rocket.Chat tetapi anda tidak mempunyai masa untuk melakukannya secara manual? Kemudian kami cadangkan menggunakan Zapier yang kami menyokong sepenuhnya. Baca lebih lanjut mengenai dokumentasi kami. https://rocket.chat/docs/administrator-guides/integrations / zapier / menggunakan-zaps /", "Admin_Info": "Maklumat Admin", "Administration": "Pentadbiran", "Adult_images_are_not_allowed": "Imej dewasa tidak dibenarkan", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/nl.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/nl.i18n.json index 15a70736008a..33708b9fa316 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -292,7 +292,6 @@ "Additional_emails": "Extra e-mails", "Additional_Feedback": "Extra feedback", "additional_integrations_Bots": "Als je op zoek bent naar het integreren van je eigen bot, zoek dan niet verder dan onze Hubot-adapter. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Wilt u andere software en applicaties integreren met Rocket.Chat, maar heeft u geen tijd om het handmatig te doen? Dan stellen we voor om Zapier te gebruiken die we volledig ondersteunen. Lees er meer over in onze documentatie. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Uw beheerder heeft E2E-codering niet ingeschakeld.", "Admin_Info": "Admin info", "Administration": "Administratie", @@ -700,7 +699,6 @@ "Busy": "Bezig", "By": "Door", "by": "door", - "By_author": "Door __author__", "cache_cleared": "Cache gewist", "Call": "Bel", "Calling": "Bellen", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/no.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/no.i18n.json index 122f3c30c567..980b42f9780a 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/no.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/no.i18n.json @@ -249,7 +249,6 @@ "Additional_emails": "Ekstra e-postadresser", "Additional_Feedback": "Ekstra tilbakemelding", "additional_integrations_Bots": "Hvis du leter etter hvordan du integrerer din egen bot, så se ikke lenger enn vår Hubot-adapter. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Ser du etter å integrere annen programvare og applikasjoner med Rocket.Chat, men du har ikke tid til å manuelt gjøre det? Da foreslår vi å bruke Zapier som vi støtter fullt ut. Les mer om det på vår dokumentasjon. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Din administrator har ikke aktivert ende-til-ende kryptering.", "Admin_Info": "Admin Info", "Administration": "Administrasjon", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/pl.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/pl.i18n.json index fbbc0fb797e6..dfbb2f97bed8 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/pl.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/pl.i18n.json @@ -307,7 +307,6 @@ "Additional_emails": "Dodatkowe adresy e-mail", "Additional_Feedback": "Dodatkowy komentarz", "additional_integrations_Bots": "Jeśli szukasz sposobu na zintegrowanie własnego bota, nie szukaj dalej niż nasz adapter Hubota. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Czy chcesz zintegrować inne oprogramowanie i aplikacje z Rocket.Chat, ale nie masz czasu, aby zrobić to ręcznie? Proponujemy użycie narzędzia Zapier, które w pełni obsługujemy. Przeczytaj więcej na ten temat w naszej dokumentacji. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Administrator nie włączył szyfrowania E2E", "Admin_Info": "Informacje administracyjne", "admin-no-active-video-conf-provider": "**Połączenie konferencyjne nie jest włączone**: Skonfiguruj połączenia konferencyjne, aby były dostępne w tej przestrzeni roboczej.", @@ -754,7 +753,6 @@ "Buy": "Kup", "By": "Autor:", "by": "autor:", - "By_author": "Autor: __author__", "cache_cleared": "Pamięć podręczna wyczyszczona", "Call": "Zadzwoń", "Calling": "Dzwoni", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 92926eab258c..460f2fadeb3d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -42,6 +42,7 @@ "Account_SID": "SID da conta", "Account": "Conta", "Accounts": "Contas", + "Accounts_Description": "Modifique as configurações de membros dos seu workspace", "Accounts_Admin_Email_Approval_Needed_Default": "

O usuário [nome] ([email]) foi registrado.

Verifique \"Administração ->Usuários\" para ativá-lo ou excluí-lo.

", "Accounts_Admin_Email_Approval_Needed_Subject_Default": "Um novo usuário se registrou e precisa de aprovação", "Accounts_Admin_Email_Approval_Needed_With_Reason_Default": "

O usuário [nome] ([email]) foi registrado.

Razão: [razão]

Verifique \"Administração -> Usuários\" para ativá-lo ou excluí-lo.

", @@ -307,7 +308,6 @@ "Additional_emails": "E-mails adicionais", "Additional_Feedback": "Comentários Adicionais", "additional_integrations_Bots": "Se você está procurando como integrar seu próprio bot, então não procure além de nosso adaptador Hubot. https://github.com/RocketChat/hubot-rocketchat ", - "additional_integrations_Zapier": "Está procurando a integração de outros softwares e aplicações com o Rocket.Chat, mas não tem tempo para o fazer manualmente? Sugerimos usar o Zapier, que apoiamos totalmente. Leia mais sobre isto na nossa documentação. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/ ", "Admin_disabled_encryption": "Seu administrador não ativou a criptografia E2E.", "Admin_Info": "Informação de administração", "Administration": "Administração", @@ -356,8 +356,9 @@ "Almost_done": "Quase pronto", "Alphabetical": "Alfabética", "Also_send_to_channel": "Enviar também para canal", - "Always_open_in_new_window": "Sempra Abrir em Janela Nova", - "Analytics": "Analytics", + "Always_open_in_new_window": "Sempre Abrir em Nova Janela", + "Analytics": "Análise", + "Analytics_Description": "Veja como seus usuários interagem no seu workspace.", "Analytics_features_enabled": "Funcionalidades habilitadas", "Analytics_features_messages_Description": "Rastreia eventos personalizados relacionados a ações que um usuário faz em mensagens.", "Analytics_features_rooms_Description": "Rastreia eventos personalizados relacionados a ações em um canal ou grupo (criar, sair, apagar).", @@ -374,7 +375,7 @@ "API_Add_Personal_Access_Token": "Adicionar novo Código de acesso pessoal", "API_Allow_Infinite_Count": "Permitir obter tudo", "API_Allow_Infinite_Count_Description": "As chamadas para a API REST podem ter permissão para devolver tudo em uma única chamada?", - "API_Analytics": "Analytics", + "API_Analytics": "Análise", "API_CORS_Origin": "Origem CORS", "API_Default_Count": "Contagem padrão", "API_Default_Count_Description": "A contagem padrão para resultados da API REST se o consumidor não forneceu nenhum.", @@ -561,6 +562,7 @@ "archive-room": "Arquivar sala", "archive-room_description": "Permissão para arquivar um canal", "are_typing": "estão digitando", + "is_recording": "está gravando", "Are_you_sure": "Você tem certeza?", "Are_you_sure_you_want_to_clear_all_unread_messages": "Tem certeza de que deseja limpar todas as mensagens não lidas?", "Are_you_sure_you_want_to_close_this_chat": "Tem certeza de que deseja fechar esta conversa?", @@ -581,6 +583,7 @@ "at": "em", "At_least_one_added_token_is_required_by_the_user": "Pelo menos um token adicionado é necessário pelo usuário", "AtlassianCrowd": "Atlassian Crowd", + "AtlassianCrowd_Description": "Integrar com Atlassian Crowd.", "Attachment_File_Uploaded": "Arquivo carregado", "Attribute_handling": "Manipulação de atributos", "Audio": "Áudio", @@ -691,6 +694,7 @@ "BotHelpers_userFields": "Campos de usuário", "BotHelpers_userFields_Description": "CSV de campos de usuários que podem ser acessados por métodos auxiliares de bots.", "Bots": "Bots", + "Bots_Description": "Defina os campos que podem ser referenciados e usados ao desenvolver bots.", "Branch": "Branch", "Broadcast": "Transmissão", "Broadcast_channel": "Canal de transmissão", @@ -720,7 +724,6 @@ "Busy": "Ocupado", "By": "Por", "by": "por", - "By_author": "Por __author__", "cache_cleared": "Cache limpo", "Call": "Ligação", "Calling": "Chamando", @@ -1899,7 +1902,7 @@ "Favorite": "Adicionar aos Favoritos", "Favorite_Rooms": "Ativar salas favoritas", "Favorites": "Favoritos", - "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Essa funcionalidade depende do provedor de chamada selecionado acima para ser habilitado nas configurações administrativas.
Para **Jitsi**, certifique-se de que o jitsi esteja habilitado em Admin -> Videoconferência -> jitsi -> Habilitado.
Para **WebRTC**, certifique-se de que o WebRTC esteja habilitado em Admin -> WebRTC -> Habilitado.", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Esta funcionalidade depende do provedor de chamada selecionado acima para ser habilitado nas configurações administrativas (Admin -> Videoconferência).", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Esse recurso depende de \"Enviar histórico de navegação do visitante como uma mensagem\" para ser ativado.", "Feature_Limiting": "Limitação de funcionalidades", "Features": "Funcionalidades", @@ -2098,6 +2101,7 @@ "Full_Screen": "Tela cheia", "Gaming": "Jogos", "General": "Geral", + "General_Description": "Configurações gerais do seu workspace", "General_Settings": "Configurações Gerais", "Generate_new_key": "Gerar uma nova chave", "Generate_New_Link": "Gerar novo link", @@ -2445,7 +2449,7 @@ "Keyboard_Shortcuts_Move_To_Beginning_Of_Message": "Mude para o início da mensagem", "Keyboard_Shortcuts_Move_To_End_Of_Message": "Mover para o final da mensagem", "Keyboard_Shortcuts_New_Line_In_Message": "Nova linha na mensagem compor a entrada", - "Keyboard_Shortcuts_Open_Channel_Slash_User_Search": "Abrir pesquisa de Canal / Usuário", + "Keyboard_Shortcuts_Open_Channel_Slash_User_Search": "Abrir Canal / Pesquisa de usuário", "Keyboard_Shortcuts_Title": "Atalhos do teclado", "Knowledge_Base": "Base de conhecimento", "Label": "Rótulo", @@ -3300,7 +3304,7 @@ "Only_invited_users_can_acess_this_channel": "Apenas usuários convidados podem acessar este canal", "Oops_page_not_found": "Ops, página não encontrada", "Oops!": "Ops", - "Open": "Aberto", + "Open": "Abrir", "Open_channel_user_search": "`%s` - Abrir canal/Pesquisa de usuário", "Open_conversations": "Conversas abertas", "Open_Days": "Dias abertos", @@ -3962,6 +3966,7 @@ "Settings": "Configurações", "Settings_updated": "Configurações atualizadas", "Setup_Wizard": "Assistente de configuração", + "Setup_Wizard_Description": "Informações básicas do seu workspace como organização, nome e país.", "Setup_Wizard_Info": "Vamos apoiar na configuração do seu primeiro usuário administrador, na configuração da sua organização e no registro do servidor, para que possa receber notificações push gratuitas e muito mais.", "Share_Location_Title": "Compartilhar localização?", "Share_screen": "Compartilhar tela", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json index ca3ace24398b..e8fc6e6ff4e9 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json @@ -249,7 +249,6 @@ "Additional_emails": "E-mails adicionais", "Additional_Feedback": "Comentários Adicionais", "additional_integrations_Bots": "Está a procurar integrar integrar seu próprio bot, então não procure mais do que o nosso adaptador Hubot. https://github.com/RocketChat/hubot-rocketchat ", - "additional_integrations_Zapier": "Está a procurar integrar outros softwares e aplicações com o Rocket.Chat, mas não tem tempo para o fazer manualmente? Sugerimos usar o Zapier, que apoiamos totalmente. Leia mais sobre isto na nossa documentação. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/ ", "Admin_disabled_encryption": "Seu administrador não ativou a criptografia E2E.", "Admin_Info": "Informação de administração", "Administration": "Administração", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ro.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ro.i18n.json index 11258ecda232..17b2dda320d1 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ro.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ro.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "E-mail-uri suplimentare", "Additional_Feedback": "Feedback suplimentar", "additional_integrations_Bots": "Dacă sunteți în căutarea pentru modul în care să vă integrați propriul bot, atunci nu căutați mai departe decât adaptorul nostru Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Încercați să integrați alte aplicații și aplicații cu Rocket.Chat, dar nu aveți timp să faceți acest lucru manual? Apoi vă sugerăm să utilizați Zapier pe care îl susținem pe deplin. Citiți mai multe despre documentația noastră. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Admin Info", "Administration": "Administrare", "Adult_images_are_not_allowed": "Imaginile adulte nu sunt permise", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ru.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ru.i18n.json index 610a09e29ace..be186de3dd0d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -312,7 +312,6 @@ "Additional_emails": "Дополнительные адреса электронной почты", "Additional_Feedback": "Дополнительная обратная связь", "additional_integrations_Bots": "Если вы ищете, как интегрировать собственного бота, посмотрите наш адаптер Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Вы хотите интегрировать другое программное обеспечение или приложения с Rocket.Chat, но у вас нет времени, чтобы сделать это вручную? Мы предлагаем вам использовать Zapier, который мы полностью поддерживаем. Подробнее об этом читайте в нашей документации. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/ ", "Admin_disabled_encryption": "Ваш администратор не включил шифрование E2E.", "Admin_Info": "Информация Администратора", "admin-no-active-video-conf-provider": "**Функция звонков не включена**: Настройте звонки, чтобы сделать их доступными для вашего сервера.", @@ -763,7 +762,6 @@ "Buy": "Купить", "By": "От", "by": "по", - "By_author": "От __author__", "cache_cleared": "Кэш очищен", "Call": "Звонок", "Call_back": "Обратный звонок", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sk-SK.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sk-SK.i18n.json index 6f7411cd0809..41951553a826 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sk-SK.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sk-SK.i18n.json @@ -218,7 +218,6 @@ "Additional_emails": "Ďalšie e-maily", "Additional_Feedback": "Ďalšia spätná väzba", "additional_integrations_Bots": "Ak hľadáte spôsob, ako integrovať svojho vlastného bota, potom nehľadajte nič iné než náš adaptér Hubot. https://github.com/RocketChat/hubot-rocketchat ", - "additional_integrations_Zapier": "Hľadáte integráciu iného softvéru a aplikácií s Rocket.Chat, ale nemáte čas to manuálne zariadiť? V takom prípade odporúčame použiť Zapier, ktorý plne podporujeme. Prečítajte si viac o ňom v našej dokumentácii. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Informácia o Adminovi", "Administration": "Administrácia", "Adult_images_are_not_allowed": "Obrázky pre dospelých nie sú povolené", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sl-SI.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sl-SI.i18n.json index 4811ff09c37b..0e860cf223e3 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sl-SI.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sl-SI.i18n.json @@ -214,7 +214,6 @@ "Additional_emails": "Dodatni e-poštni naslovi", "Additional_Feedback": "Dodaten odziv", "additional_integrations_Bots": "Če iščete, kako vključiti svoj bot, potem ne gledajte dlje od našega adapterja Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Želite integrirati drugo programsko opremo in aplikacije z Rocket.Chat, vendar nimate časa, da ročno naredite to? Potem predlagamo uporabo Zapierja, ki ga v celoti podpiramo. Več o tem preberite v naši dokumentaciji. https://rocket.chat/docs/administratorguides/integrations / zapier / using-zaps /", "Admin_Info": "Admin Info", "Administration": "Skrbništvo", "Adult_images_are_not_allowed": "Slike z neprimerno vsebino niso dovoljene", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sq.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sq.i18n.json index 796a66e32db8..ffbe7c96c8d6 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sq.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sq.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "Shtesë E-mail", "Additional_Feedback": "Feedback shtesë", "additional_integrations_Bots": "Nëse jeni duke kërkuar për integrimin e botit tuaj, atëherë shikoni më tej se adaptori ynë Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "A jeni duke kërkuar për të integruar programe dhe aplikacione të tjera me Rocket.Chat, por ju nuk keni kohë për ta bërë atë manualisht? Pastaj ne sugjerojmë përdorimin e Zapier të cilën e mbështesim plotësisht. Lexoni më shumë në lidhje me dokumentacionin tonë. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "Info Admin", "Administration": "administratë", "Adult_images_are_not_allowed": "Imazhet e të rriturve nuk lejohen", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sv.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sv.i18n.json index b4ad6e999eb3..0e2074720ecb 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sv.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sv.i18n.json @@ -317,7 +317,6 @@ "Additional_emails": "Ytterligare e-post", "Additional_Feedback": "Ytterligare Feedback", "additional_integrations_Bots": "Om du letar efter hur du integrerar din egen bot, kika på vår Hubot-adapter. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Vill du integrera andra program och applikationer med Rocket.Chat, men du har inte tid att göra det för hand? Då föreslår vi att du använder Zapier som har fullt stöd. Läs mer om det i dokumentationen. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Administratören har inte aktiverat E2E-kryptering.", "Admin_Info": "Admin-info", "admin-no-active-video-conf-provider": "**Konferenssamtal är inte aktiverade**: Konfigurera konferenssamtal så att de är tillgängliga i arbetsytan.", @@ -774,7 +773,6 @@ "Buy": "Köp", "By": "Av", "by": "av", - "By_author": "Av __author__", "cache_cleared": "Cachen rensad", "Call": "Ring upp", "Call_back": "Ring tillbaka", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ta-IN.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ta-IN.i18n.json index f974bbc06b84..58952a153c4f 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ta-IN.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ta-IN.i18n.json @@ -215,7 +215,6 @@ "Additional_emails": "கூடுதல் மின் அஞ்சல்", "Additional_Feedback": "கூடுதல் கருத்துத்", "additional_integrations_Bots": "உங்கள் சொந்த பாட்டை ஒருங்கிணைக்க எப்படி தேடுகிறீர்கள் எனில், எங்கள் Hubot adapter ஐப் பார்க்க வேண்டாம். https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "நீங்கள் Rocket.Chat மற்ற மென்பொருள் மற்றும் பயன்பாடுகள் ஒருங்கிணைக்க பார்க்கிறாய் ஆனால் நீங்கள் கைமுறையாக அதை செய்ய நேரம் இல்லை? பின்னர் நாம் முழுமையாக ஆதரிக்கும் Zapier ஐப் பரிந்துரைக்கிறோம். எங்கள் ஆவணத்தில் அதைப் பற்றி மேலும் அறியவும். https://rocket.chat/docs/administrator-guides/integrations / ஜாப்பியர் / யூஸ்-ஜாப்ஸ் /", "Admin_Info": "நிர்வாகம் தகவல்", "Administration": "நிர்வாகம்", "Adult_images_are_not_allowed": "வயது வந்தோருக்கான படங்கள் அனுமதிக்கப்படவில்லை", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/th-TH.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/th-TH.i18n.json index ac2d48f9ea3d..010b757ddf8b 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/th-TH.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/th-TH.i18n.json @@ -214,7 +214,6 @@ "Additional_emails": "อีเมลเพิ่มเติม", "Additional_Feedback": "ข้อเสนอแนะเพิ่มเติม", "additional_integrations_Bots": "หากคุณกำลังมองหาวิธีการรวมบอทของคุณเองออกไปให้ดูที่ Hubot adapter ของเรา https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "คุณต้องการผสานรวมซอฟต์แวร์และแอพพลิเคชันอื่นเข้ากับ Rocket.Chat แต่คุณไม่มีเวลาทำด้วยตนเองหรือไม่? จากนั้นเราขอแนะนำให้ใช้ Zapier ซึ่งเราสนับสนุนอย่างเต็มที่ อ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ในเอกสารของเรา https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "ข้อมูลผู้ดูแลระบบ", "Administration": "การบริหาร", "Adult_images_are_not_allowed": "ภาพผู้ใหญ่ไม่ได้รับอนุญาต", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/tr.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/tr.i18n.json index 514605e1c34a..ba37b324d854 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/tr.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/tr.i18n.json @@ -239,7 +239,6 @@ "Additional_emails": "Ek E-postalar", "Additional_Feedback": "Ek Geri Bildirim", "additional_integrations_Bots": "Kendi botunuzu nasıl entegre edeceğinizi arıyorsanız, o zaman Hubot adaptörümüzden başka bir yere bakmayın. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Diğer yazılımları ve uygulamaları Rocket ile entegre etmek istiyor musunuz.Ancak, ancak bunu elle yapmak için zamanınız yok mu? Sonra tamamen desteklediğimiz Zapier'i kullanmanızı öneririz. Dokümanlarımızda daha fazla bilgi edinin. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Yöneticiniz uçtan uca şifrelemeyi devre dışı bıraktı.", "Admin_Info": "Yönetici Bilgisi", "Administration": "Yönetim", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/uk.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/uk.i18n.json index f87f893415bb..f911aa61693a 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/uk.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/uk.i18n.json @@ -270,7 +270,6 @@ "Additional_emails": "Додаткові електронні адреси", "Additional_Feedback": "Додатковий зворотній зв'язок", "additional_integrations_Bots": "Якщо ви шукаєте, як інтегрувати свій власний бот, то шукайте не більше, ніж наш адаптер Hubot. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Ви хочете інтегрувати інше програмне забезпечення та програми з Rocket.Chat, але у вас немає часу вручну зробити це? Тоді ми пропонуємо використовувати Zapier, який ми повністю підтримуємо. Докладніше про це читайте в нашій документації. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Ваш адміністратор не ввімкнув шифрування E2E.", "Admin_Info": "Інформація адміністратора", "Administration": "Адміністрування", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/vi-VN.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/vi-VN.i18n.json index e3f0e109ade0..3de7c5c531b8 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/vi-VN.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/vi-VN.i18n.json @@ -267,7 +267,6 @@ "Additional_emails": "Email bổ sung", "Additional_Feedback": "Phản hồi bổ sung", "additional_integrations_Bots": "Nếu bạn đang tìm cách để tích hợp Bot của riêng bạn vậy thì bạn nên xem qua phần Hubot adapter. https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "Có phải bạn đang tìm cách tích hợp các phần mềm và ứng dụng khác vào Rocket.Chat nhưng lại không có thời gian để tự tay thực hiện nó? Nếu đúng như thế, chúng tôi nghĩ bạn nên sử dụng một công cụ được chúng tôi hỗ trợ đó là Zapier. Bạn có thể đọc thêm về Zapier ở đây ", "Admin_disabled_encryption": "Quản trị viên của bạn không bật mã hóa E2E.", "Admin_Info": "Thông tin quản trị", "Administration": "Quản trị", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/zh-HK.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/zh-HK.i18n.json index c547fb5b2d7e..68f4a2dc6f3a 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/zh-HK.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/zh-HK.i18n.json @@ -228,7 +228,6 @@ "Additional_emails": "其他电子邮件", "Additional_Feedback": "其他反馈", "additional_integrations_Bots": "如果你正在寻找如何整合你自己的机器人,那么看看我们的Hubot适配器。 https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "您是否希望将其他软件和应用程序与Rocket.Chat集成,但您没有时间手动完成它?那么我们建议使用我们完全支持的Zapier。请阅读我们的文档了解更多信息。 https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_Info": "管理员信息", "Administration": "管理", "Adult_images_are_not_allowed": "成人图像是不允许的", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/zh-TW.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/zh-TW.i18n.json index 1cc458a93c32..f93413dd743d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/zh-TW.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/zh-TW.i18n.json @@ -291,7 +291,6 @@ "Additional_emails": "其他電子郵件", "Additional_Feedback": "其他意見", "additional_integrations_Bots": "如果您正在尋找如何整合自己的機器人,那麼請選擇我們的Hubot介面。 https://github.com/RocketChat/hubot-rocketchat ", - "additional_integrations_Zapier": "您是否希望將其他軟體和應用程式與Rocket.Chat整合,但您沒有時間手動執行此動作?然後我們建議使用我們完全支援的Zapier。在我們的文件中閱讀更多相關訊息。 https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/ ", "Admin_disabled_encryption": "您的管理員無法啟用 E2E 加密。", "Admin_Info": "管理員訊息", "Administration": "管理", @@ -697,7 +696,6 @@ "Busy": "忙碌", "By": "通過", "by": "通過", - "By_author": "由__author__", "cache_cleared": "快取已清除", "Call": "呼叫", "Calling": "正在通話", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/zh.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/zh.i18n.json index 9b51ddc5c4e3..a3f1c1543d94 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -274,7 +274,6 @@ "Additional_emails": "其他 Email", "Additional_Feedback": "其他反馈", "additional_integrations_Bots": "如果你正在研究如何整合你自己的机器人,那么可以看看我们的 Hubot 适配器。 https://github.com/RocketChat/hubot-rocketchat", - "additional_integrations_Zapier": "您是否希望将其他软件和应用程序与 Rocket.Chat 集成,但您没有时间手动完成?那么我们建议使用我们完全支持的 Zapier。请阅读我们的文档了解更多信息。 https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "您的管理员并没有启用端到端加密", "Admin_Info": "管理员信息", "Administration": "管理", @@ -628,7 +627,6 @@ "busy": "忙碌", "Busy": "忙碌", "by": "通过", - "By_author": "由 __author__", "cache_cleared": "缓存已清理", "Call": "调用", "call-management": "呼叫管理", diff --git a/apps/meteor/tests/data/livechat/department.ts b/apps/meteor/tests/data/livechat/department.ts index 48b9acb7ebfe..9de438d02469 100644 --- a/apps/meteor/tests/data/livechat/department.ts +++ b/apps/meteor/tests/data/livechat/department.ts @@ -57,7 +57,10 @@ new Promise((resolve, reject) => { }); }); -export const createDepartmentWithAnOnlineAgent = async (): Promise<{department: ILivechatDepartment, agent: IUser}> => { +export const createDepartmentWithAnOnlineAgent = async (): Promise<{department: ILivechatDepartment, agent: { + credentials: { 'X-Auth-Token': string; 'X-User-Id': string; }; + user: IUser; +}}> => { const agent: IUser = await createUser(); const createdUserCredentials = await login(agent.username, password); await createAgent(agent.username); @@ -69,7 +72,10 @@ export const createDepartmentWithAnOnlineAgent = async (): Promise<{department: return { department, - agent, + agent: { + credentials: createdUserCredentials, + user: agent, + } }; }; diff --git a/apps/meteor/tests/data/subscriptions.ts b/apps/meteor/tests/data/subscriptions.ts new file mode 100644 index 000000000000..04e0f48c98e4 --- /dev/null +++ b/apps/meteor/tests/data/subscriptions.ts @@ -0,0 +1,15 @@ +import type { ISubscription } from "@rocket.chat/core-typings"; +import { api, credentials, request } from "./api-data"; + +export const getSubscriptionForRoom = async (roomId: string, overrideCredential?: { 'X-Auth-Token': string; 'X-User-Id': string; }): Promise => { + const response = await request + .get(api('subscriptions.getOne')) + .set(overrideCredential || credentials) + .query({ roomId }) + .expect('Content-Type', 'application/json') + .expect(200); + + const { subscription } = response.body; + + return subscription; +} diff --git a/apps/meteor/tests/e2e/config/global-setup.ts b/apps/meteor/tests/e2e/config/global-setup.ts index ac24b4e953a4..42e2e3f62f54 100644 --- a/apps/meteor/tests/e2e/config/global-setup.ts +++ b/apps/meteor/tests/e2e/config/global-setup.ts @@ -34,21 +34,6 @@ export default async function (): Promise { await page.waitForTimeout(1000); - if (page.url().includes('setup-wizard')) { - await page.locator('[name="organizationName"]').type('any_name'); - await page.locator('[name="organizationType"]').click(); - await page.locator('.rcx-options .rcx-option:first-child >> text="Community"').click(); - await page.locator('[name="organizationIndustry"]').click(); - await page.locator('.rcx-options .rcx-option:first-child >> text="Aerospace & Defense"').click(); - await page.locator('[name="organizationSize"]').click(); - await page.locator('.rcx-options .rcx-option:first-child >> text="1-10 people"').click(); - await page.locator('[name="country"]').click(); - await page.locator('.rcx-options .rcx-option:first-child >> text="Afghanistan"').click(); - await page.locator('.rcx-button--primary.rcx-button >> text="Next"').click(); - await page.locator('a.rcx-box.rcx-box--full >> text="Continue as standalone"').click(); - await page.locator('.rcx-button--primary.rcx-button >> text="Confirm"').click(); - } - await page.context().storageState({ path: `admin-session.json` }); await browser.close(); diff --git a/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts b/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts index 962b352e929f..dceffcc3acc3 100644 --- a/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts +++ b/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts @@ -14,10 +14,47 @@ export default async function injectInitialData() { ), ); - await connection - .db() - .collection('rocketchat_settings') - .updateOne({ _id: 'API_Enable_Rate_Limiter_Dev' }, { $set: { value: false } }); + await Promise.all( + [ + { + _id: 'API_Enable_Rate_Limiter_Dev', + value: false, + }, + { + _id: 'Show_Setup_Wizard', + value: 'completed', + }, + { + _id: 'Country', + value: 'brazil', + }, + { + _id: 'Organization_Type', + value: 'community', + }, + { + _id: 'Industry', + value: 'aerospaceDefense', + }, + { + _id: 'Size', + value: 0, + }, + { + _id: 'Organization_Name', + value: 'any_name', + }, + { + _id: 'API_Enable_Rate_Limiter_Dev', + value: false, + }, + ].map((setting) => + connection + .db() + .collection('rocketchat_settings') + .updateOne({ _id: setting._id }, { $set: { value: setting.value } }), + ), + ); return { usersFixtures }; } diff --git a/apps/meteor/tests/e2e/message-mentions.spec.ts b/apps/meteor/tests/e2e/message-mentions.spec.ts index a8fc56bfe11a..087522993142 100644 --- a/apps/meteor/tests/e2e/message-mentions.spec.ts +++ b/apps/meteor/tests/e2e/message-mentions.spec.ts @@ -16,7 +16,7 @@ test.describe.serial('message-mentions', () => { await poHomeChannel.sidenav.openChat('general'); await poHomeChannel.content.inputMessage.type('@'); - await expect(poHomeChannel.content.messagePopUpItems.locator('strong >> text=all')).toBeVisible(); - await expect(poHomeChannel.content.messagePopUpItems.locator('strong >> text=here')).toBeVisible(); + await expect(poHomeChannel.content.messagePopUpItems.locator('strong >> text="all"')).toBeVisible(); + await expect(poHomeChannel.content.messagePopUpItems.locator('strong >> text="here"')).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts index 7d4ced3ddabb..a419420152e1 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -198,7 +198,7 @@ export class HomeContent { } get resumeOnHoldOmnichannelChatButton(): Locator { - return this.page.locator('button.rcx-button--primary >> text=Resume'); + return this.page.locator('button.rcx-button--primary >> text="Resume"'); } get btnOnHold(): Locator { diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-channels.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-channels.ts index 3918ecd1a6c7..b09e90d0cd61 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-channels.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-channels.ts @@ -16,6 +16,6 @@ export class HomeFlextabChannels { } get btnAdd(): Locator { - return this.page.locator('#modal-root button:has-text("Add")'); + return this.page.locator('role=dialog >> role=group >> role=button[name=Add]'); } } diff --git a/apps/meteor/tests/e2e/page-objects/home-team.ts b/apps/meteor/tests/e2e/page-objects/home-team.ts index 9669a0b688ed..24e8396979c9 100644 --- a/apps/meteor/tests/e2e/page-objects/home-team.ts +++ b/apps/meteor/tests/e2e/page-objects/home-team.ts @@ -28,6 +28,6 @@ export class HomeTeam { } get btnTeamCreate(): Locator { - return this.page.locator('#modal-root button:has-text("Create")'); + return this.page.locator('role=dialog >> role=group >> role=button[name=Create]'); } } diff --git a/apps/meteor/tests/e2e/page-objects/utils.ts b/apps/meteor/tests/e2e/page-objects/utils.ts index a9319cd4d36c..d71eaf55daab 100644 --- a/apps/meteor/tests/e2e/page-objects/utils.ts +++ b/apps/meteor/tests/e2e/page-objects/utils.ts @@ -20,6 +20,6 @@ export class Utils { } get btnModalConfirmDelete() { - return this.page.locator('.rcx-modal >> button >> text=Delete'); + return this.page.locator('.rcx-modal >> button >> text="Delete"'); } } diff --git a/apps/meteor/tests/e2e/settings-account-profile.spec.ts b/apps/meteor/tests/e2e/settings-account-profile.spec.ts index b26a379befd4..4d0cdb7625e7 100644 --- a/apps/meteor/tests/e2e/settings-account-profile.spec.ts +++ b/apps/meteor/tests/e2e/settings-account-profile.spec.ts @@ -51,7 +51,7 @@ test.describe.serial('settings-account-profile', () => { await poAccountProfile.inputToken.type(token); await poAccountProfile.btnTokensAdd.click(); await expect(poAccountProfile.tokenAddedModal).toBeVisible(); - await page.locator('button:has-text("Ok")').click(); + await page.locator('role=button[name=Ok]').click(); }); await test.step('expect not allow add new personal token with same name', async () => { @@ -64,7 +64,7 @@ test.describe.serial('settings-account-profile', () => { await poAccountProfile.tokenInTable(token).locator('button >> nth=0').click(); await poAccountProfile.btnRegenerateTokenModal.click(); await expect(poAccountProfile.tokenAddedModal).toBeVisible(); - await page.locator('button:has-text("Ok")').click(); + await page.locator('role=button[name=Ok]').click(); }); await test.step('expect delete personal token', async () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 7072a6a668a3..db77192a4eea 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -25,6 +25,7 @@ import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/depart import { sleep } from '../../../data/livechat/utils'; import { IS_EE } from '../../../e2e/config/constants'; import { createCustomField } from '../../../data/livechat/custom-fields'; +import { getSubscriptionForRoom } from '../../../data/subscriptions'; describe('LIVECHAT - rooms', function () { this.retries(0); @@ -1446,4 +1447,62 @@ describe('LIVECHAT - rooms', function () { expect(response.body.filters.find((f: IOmnichannelRoom['source']) => f.type === 'api')).to.not.be.undefined; }); }); + + describe('it should mark room as unread when a new message arrives and the config is activated', () => { + let room: IOmnichannelRoom; + let visitor: ILivechatVisitor; + let totalMessagesSent = 0; + let departmentWithAgent: Awaited>; + + before(async () => { + await updateSetting('Livechat_Routing_Method', 'Auto_Selection'); + await updateSetting('Unread_Count_Omni', 'all_messages'); + }); + + it('it should prepare the required data for further tests', async () => { + departmentWithAgent = await createDepartmentWithAnOnlineAgent(); + visitor = await createVisitor(departmentWithAgent.department._id); + room = await createLivechatRoom(visitor.token); + + await sendMessage(room._id, 'message 1', visitor.token); + await sendMessage(room._id, 'message 2', visitor.token); + + // 1st message is for the room creation, so we need to add 1 to the total messages sent + totalMessagesSent = 3; + }); + + it("room's subscription should have correct unread count", async () => { + const { unread } = await getSubscriptionForRoom(room._id, departmentWithAgent.agent.credentials); + expect(unread).to.equal(totalMessagesSent); + }); + }); + + describe('it should NOT mark room as unread when a new message arrives and the config is deactivated', () => { + let room: IOmnichannelRoom; + let visitor: ILivechatVisitor; + let totalMessagesSent = 0; + let departmentWithAgent: Awaited>; + + before(async () => { + await updateSetting('Livechat_Routing_Method', 'Auto_Selection'); + await updateSetting('Unread_Count_Omni', 'mentions_only'); + }); + + it('it should prepare the required data for further tests', async () => { + departmentWithAgent = await createDepartmentWithAnOnlineAgent(); + visitor = await createVisitor(departmentWithAgent.department._id); + room = await createLivechatRoom(visitor.token); + + await sendMessage(room._id, 'message 1', visitor.token); + await sendMessage(room._id, 'message 2', visitor.token); + + // 1st message is for the room creation, so we need to add 1 to the total messages sent + totalMessagesSent = 1; + }); + + it("room's subscription should have correct unread count", async () => { + const { unread } = await getSubscriptionForRoom(room._id, departmentWithAgent.agent.credentials); + expect(unread).to.equal(totalMessagesSent); + }); + }); }); diff --git a/ee/apps/ddp-streamer/src/Server.ts b/ee/apps/ddp-streamer/src/Server.ts index c4c6a53c8e16..e3fdc3add509 100644 --- a/ee/apps/ddp-streamer/src/Server.ts +++ b/ee/apps/ddp-streamer/src/Server.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; -import type WebSocket from 'ws'; +import WebSocket from 'ws'; import ejson from 'ejson'; import { v1 as uuidv1 } from 'uuid'; import { MeteorService, isMeteorError, MeteorError } from '@rocket.chat/core-services'; @@ -53,6 +53,10 @@ export class Server extends EventEmitter { }; async call(client: Client, packet: IPacket): Promise { + // if client is not connected we don't need to do anything + if (client.ws.readyState !== WebSocket.OPEN) { + return; + } try { // if method was not defined on DDP Streamer we fall back to Meteor if (!this._methods.has(packet.method)) { @@ -86,6 +90,10 @@ export class Server extends EventEmitter { } async subscribe(client: Client, packet: IPacket): Promise { + // if client is not connected we don't need to do anything + if (client.ws.readyState !== WebSocket.OPEN) { + return; + } try { if (!this._subscriptions.has(packet.name)) { throw new MeteorError(404, `Subscription '${packet.name}' not found`); diff --git a/ee/apps/ddp-streamer/src/Streamer.ts b/ee/apps/ddp-streamer/src/Streamer.ts index fdde06e7a5d5..5a0ec6173a6f 100644 --- a/ee/apps/ddp-streamer/src/Streamer.ts +++ b/ee/apps/ddp-streamer/src/Streamer.ts @@ -57,11 +57,22 @@ export class Stream extends Streamer { }; for await (const { subscription } of subscriptions) { + // if the connection state is not open anymore, it somehow got to a weird state, + // we'll emit close so it can clean up the weird state, and so we stop emitting to it + if (subscription.client.ws.readyState !== WebSocket.OPEN) { + subscription.client.ws.emit('close'); + continue; + } + if (this.retransmitToSelf === false && origin && origin === subscription.connection) { continue; } - if (await this.isEmitAllowed(subscription, eventName, ...args)) { + if (!(await this.isEmitAllowed(subscription, eventName, ...args))) { + continue; + } + + try { await new Promise((resolve, reject) => { const frame = data[subscription.client.meteorClient ? 'meteor' : 'normal']; @@ -72,6 +83,16 @@ export class Stream extends Streamer { resolve(); }); }); + } catch (error: any) { + if (error.code === 'ERR_STREAM_DESTROYED') { + console.warn('Trying to send data to destroyed stream, closing connection.'); + + // if we still tried to send data to a destroyed stream, we'll try again to close the connection + if (subscription.client.ws.readyState !== WebSocket.OPEN) { + subscription.client.ws.emit('close'); + } + } + console.error('Error trying to send data to stream.', error); } } } diff --git a/packages/core-typings/src/IInstanceStatus.ts b/packages/core-typings/src/IInstanceStatus.ts index f2b57a142d86..687774baeacb 100644 --- a/packages/core-typings/src/IInstanceStatus.ts +++ b/packages/core-typings/src/IInstanceStatus.ts @@ -1,5 +1,6 @@ -export interface IInstanceStatus { - _id: string; +import type { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IInstanceStatus extends IRocketChatRecord { extraInformation?: { port?: number; }; diff --git a/packages/core-typings/src/IPermission.ts b/packages/core-typings/src/IPermission.ts index be119d7bacee..b53ef0c12b33 100644 --- a/packages/core-typings/src/IPermission.ts +++ b/packages/core-typings/src/IPermission.ts @@ -1,6 +1,6 @@ export interface IPermission { _id: string; - _updatedAt?: Date; + _updatedAt: Date; roles: string[]; group?: string; groupPermissionId?: string; diff --git a/packages/core-typings/src/ISetting.ts b/packages/core-typings/src/ISetting.ts index f18d303df5b4..82f89ec91f35 100644 --- a/packages/core-typings/src/ISetting.ts +++ b/packages/core-typings/src/ISetting.ts @@ -25,6 +25,7 @@ type EnableQuery = string | { _id: string; value: any } | { _id: string; value: export interface ISettingBase { _id: SettingId; + _updatedAt: Date; type: | 'boolean' | 'timezone' @@ -70,7 +71,6 @@ export interface ISettingBase { meteorSettingsValue?: SettingValue; ts: Date; createdAt: Date; - _updatedAt?: Date; multiline?: boolean; values?: Array; placeholder?: string; diff --git a/packages/ui-contexts/src/ServerContext/methods.ts b/packages/ui-contexts/src/ServerContext/methods.ts index 02b4ccb87df0..0ee4f5b6c178 100644 --- a/packages/ui-contexts/src/ServerContext/methods.ts +++ b/packages/ui-contexts/src/ServerContext/methods.ts @@ -1,7 +1,9 @@ import type { AtLeast, ICreatedRoom, + IInstanceStatus, IMessage, + IPermission, IRoom, ISetting, ISubscription, @@ -144,14 +146,14 @@ export interface ServerMethods { 'ignoreUser': (...args: any[]) => any; 'insertOrUpdateSound': (args: { previousName?: string; name?: string; _id?: string; extension: string }) => string; 'insertOrUpdateUserStatus': (...args: any[]) => any; - 'instances/get': (...args: any[]) => any; + 'instances/get': () => IInstanceStatus[]; 'joinRoom': JoinRoomMethod; 'leaveRoom': (...args: any[]) => any; 'loadHistory': ( rid: IRoom['_id'], ts?: Date, limit?: number, - ls?: number, + ls?: string, showThreadMessages?: boolean, ) => { messages: IMessage[]; @@ -259,6 +261,11 @@ export interface ServerMethods { enabled: boolean; policy: [name: TranslationKey, options?: Record][]; }; + 'rooms/get': (updatedSince?: Date) => IRoom[] | { update: IRoom[]; remove: IRoom[] }; + 'subscriptions/get': (updatedSince?: Date) => ISubscription[] | { update: ISubscription[]; remove: ISubscription[] }; + 'permissions/get': (updatedSince?: Date) => IPermission[] | { update: IPermission[]; remove: IPermission[] }; + 'public-settings/get': (updatedSince?: Date) => ISetting[] | { update: ISetting[]; remove: ISetting[] }; + 'private-settings/get': (updatedSince?: Date) => ISetting[] | { update: ISetting[]; remove: ISetting[] }; } export type ServerMethodName = keyof ServerMethods; diff --git a/packages/ui-contexts/src/UserContext.ts b/packages/ui-contexts/src/UserContext.ts index 9fa335e95ce1..178908f010de 100644 --- a/packages/ui-contexts/src/UserContext.ts +++ b/packages/ui-contexts/src/UserContext.ts @@ -1,5 +1,5 @@ import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; -import type { ObjectId, Filter } from 'mongodb'; +import type { ObjectId, Filter, FindOptions as MongoFindOptions, Document } from 'mongodb'; import { createContext } from 'react'; export type SubscriptionQuery = @@ -14,17 +14,13 @@ export type SubscriptionQuery = } | object; -export type Fields = { - [key: string]: boolean; -}; +export type Fields = Exclude['projection'], undefined>; -export type Sort = { - [key: string]: -1 | 1 | number; -}; +export type Sort = Exclude['sort'], undefined>; -export type FindOptions = { - fields?: Fields; - sort?: Sort; +export type FindOptions = { + fields?: Fields; + sort?: Sort; }; export type LoginService = { @@ -46,8 +42,8 @@ export type UserContextValue = { ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => T | undefined]; querySubscription: ( query: Filter>, - fields?: Fields, - sort?: Sort, + fields?: MongoFindOptions['projection'], + sort?: MongoFindOptions['sort'], ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISubscription | undefined]; queryRoom: ( query: Filter>,