Merge branch 'develop' into bwindels/redesign

This commit is contained in:
Bruno Windels 2018-10-16 11:57:44 +02:00
commit 6878ce3c6a
89 changed files with 2635 additions and 1287 deletions

View file

@ -39,7 +39,6 @@ src/components/views/elements/InlineSpinner.js
src/components/views/elements/MemberEventListSummary.js
src/components/views/elements/Spinner.js
src/components/views/elements/TintableSvg.js
src/components/views/elements/UserInfo.js
src/components/views/elements/UserSelector.js
src/components/views/globals/MatrixToolbar.js
src/components/views/globals/NewVersionBar.js
@ -54,7 +53,6 @@ src/components/views/messages/RoomAvatarEvent.js
src/components/views/messages/TextualBody.js
src/components/views/room_settings/AliasSettings.js
src/components/views/room_settings/ColorSettings.js
src/components/views/room_settings/UrlPreviewSettings.js
src/components/views/rooms/Autocomplete.js
src/components/views/rooms/AuxPanel.js
src/components/views/rooms/EntityTile.js
@ -66,7 +64,6 @@ src/components/views/rooms/MemberTile.js
src/components/views/rooms/MessageComposer.js
src/components/views/rooms/MessageComposerInput.js
src/components/views/rooms/PinnedEventTile.js
src/components/views/rooms/RoomDropTarget.js
src/components/views/rooms/RoomList.js
src/components/views/rooms/RoomPreviewBar.js
src/components/views/rooms/RoomSettings.js
@ -92,7 +89,6 @@ src/Markdown.js
src/MatrixClientPeg.js
src/Modal.js
src/notifications/ContentRules.js
src/notifications/NotificationUtils.js
src/notifications/PushRuleVectorState.js
src/notifications/StandardActions.js
src/notifications/VectorPushRulesDefinitions.js
@ -102,7 +98,6 @@ src/Presence.js
src/rageshake/rageshake.js
src/rageshake/submit-rageshake.js
src/ratelimitedfunc.js
src/RichText.js
src/Roles.js
src/Rooms.js
src/ScalarAuthClient.js

View file

@ -1,3 +1,186 @@
Changes in [0.14.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.0) (2018-10-16)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0-rc.1...v0.14.0)
* Phased rollout of lazy loading
[\#2218](https://github.com/matrix-org/matrix-react-sdk/pull/2218)
Changes in [0.14.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.0-rc.1) (2018-10-11)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.6...v0.14.0-rc.1)
* turn LL on by default!
[\#2209](https://github.com/matrix-org/matrix-react-sdk/pull/2209)
* Update from Weblate.
[\#2207](https://github.com/matrix-org/matrix-react-sdk/pull/2207)
* Fix quote post slate update
[\#2206](https://github.com/matrix-org/matrix-react-sdk/pull/2206)
* Handle InvalidStoreError from js-sdk
[\#2205](https://github.com/matrix-org/matrix-react-sdk/pull/2205)
* Fall back to default avatar in RR when member isn't loaded yet
[\#2204](https://github.com/matrix-org/matrix-react-sdk/pull/2204)
* Update to new version of slate
[\#2202](https://github.com/matrix-org/matrix-react-sdk/pull/2202)
* Update karma to webpack 4
[\#2203](https://github.com/matrix-org/matrix-react-sdk/pull/2203)
* More accessible buttons - take 2
[\#2194](https://github.com/matrix-org/matrix-react-sdk/pull/2194)
* log correct error code when opening log idb
[\#2200](https://github.com/matrix-org/matrix-react-sdk/pull/2200)
* show warning when LL is disabled but was enabled before
[\#2201](https://github.com/matrix-org/matrix-react-sdk/pull/2201)
* Fall back to another store if indexeddb start fails
[\#2195](https://github.com/matrix-org/matrix-react-sdk/pull/2195)
* Silence bluebird warnings
[\#2198](https://github.com/matrix-org/matrix-react-sdk/pull/2198)
* Use createObjectURL instead of readAsDataURL for videos
[\#2197](https://github.com/matrix-org/matrix-react-sdk/pull/2197)
* Revert "Use createObjectURL instead of readAsDataURL for videos"
[\#2196](https://github.com/matrix-org/matrix-react-sdk/pull/2196)
* Track how far the user travels before dismissing their user settings
[\#2183](https://github.com/matrix-org/matrix-react-sdk/pull/2183)
* Drop (IRC) suffix hacks
[\#2193](https://github.com/matrix-org/matrix-react-sdk/pull/2193)
* Use createObjectURL instead of readAsDataURL for videos
[\#2176](https://github.com/matrix-org/matrix-react-sdk/pull/2176)
* Remove old migration code
[\#2192](https://github.com/matrix-org/matrix-react-sdk/pull/2192)
* Fix brace style in TextForEvent.js
[\#2191](https://github.com/matrix-org/matrix-react-sdk/pull/2191)
* Fix error logging
[\#2190](https://github.com/matrix-org/matrix-react-sdk/pull/2190)
* Fix Promise.defer warning in ScalarAuthClient.js
[\#2188](https://github.com/matrix-org/matrix-react-sdk/pull/2188)
* Communicate early that a 3pid is required during registration if needed
[\#2180](https://github.com/matrix-org/matrix-react-sdk/pull/2180)
* try to encourage people to attach logs to bugs
[\#2185](https://github.com/matrix-org/matrix-react-sdk/pull/2185)
* Show the 'homeserver unavailable' warning when the first sync fails
[\#2182](https://github.com/matrix-org/matrix-react-sdk/pull/2182)
* allow passing initial is_url like hs_url in query params
[\#2083](https://github.com/matrix-org/matrix-react-sdk/pull/2083)
* Update karma
[\#2177](https://github.com/matrix-org/matrix-react-sdk/pull/2177)
* fudge hangup reasons
[\#2184](https://github.com/matrix-org/matrix-react-sdk/pull/2184)
* Provide more helpful errors when i18n generation fails
[\#2181](https://github.com/matrix-org/matrix-react-sdk/pull/2181)
Changes in [0.14.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.0-rc.1) (2018-10-11)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.6...v0.14.0-rc.1)
* turn LL on by default!
[\#2209](https://github.com/matrix-org/matrix-react-sdk/pull/2209)
* Update from Weblate.
[\#2207](https://github.com/matrix-org/matrix-react-sdk/pull/2207)
* Fix quote post slate update
[\#2206](https://github.com/matrix-org/matrix-react-sdk/pull/2206)
* Handle InvalidStoreError from js-sdk
[\#2205](https://github.com/matrix-org/matrix-react-sdk/pull/2205)
* Fall back to default avatar in RR when member isn't loaded yet
[\#2204](https://github.com/matrix-org/matrix-react-sdk/pull/2204)
* Update to new version of slate
[\#2202](https://github.com/matrix-org/matrix-react-sdk/pull/2202)
* Update karma to webpack 4
[\#2203](https://github.com/matrix-org/matrix-react-sdk/pull/2203)
* More accessible buttons - take 2
[\#2194](https://github.com/matrix-org/matrix-react-sdk/pull/2194)
* log correct error code when opening log idb
[\#2200](https://github.com/matrix-org/matrix-react-sdk/pull/2200)
* show warning when LL is disabled but was enabled before
[\#2201](https://github.com/matrix-org/matrix-react-sdk/pull/2201)
* Fall back to another store if indexeddb start fails
[\#2195](https://github.com/matrix-org/matrix-react-sdk/pull/2195)
* Silence bluebird warnings
[\#2198](https://github.com/matrix-org/matrix-react-sdk/pull/2198)
* Use createObjectURL instead of readAsDataURL for videos
[\#2197](https://github.com/matrix-org/matrix-react-sdk/pull/2197)
* Revert "Use createObjectURL instead of readAsDataURL for videos"
[\#2196](https://github.com/matrix-org/matrix-react-sdk/pull/2196)
* Track how far the user travels before dismissing their user settings
[\#2183](https://github.com/matrix-org/matrix-react-sdk/pull/2183)
* Drop (IRC) suffix hacks
[\#2193](https://github.com/matrix-org/matrix-react-sdk/pull/2193)
* Use createObjectURL instead of readAsDataURL for videos
[\#2176](https://github.com/matrix-org/matrix-react-sdk/pull/2176)
* Remove old migration code
[\#2192](https://github.com/matrix-org/matrix-react-sdk/pull/2192)
* Fix brace style in TextForEvent.js
[\#2191](https://github.com/matrix-org/matrix-react-sdk/pull/2191)
* Fix error logging
[\#2190](https://github.com/matrix-org/matrix-react-sdk/pull/2190)
* Fix Promise.defer warning in ScalarAuthClient.js
[\#2188](https://github.com/matrix-org/matrix-react-sdk/pull/2188)
* Communicate early that a 3pid is required during registration if needed
[\#2180](https://github.com/matrix-org/matrix-react-sdk/pull/2180)
* try to encourage people to attach logs to bugs
[\#2185](https://github.com/matrix-org/matrix-react-sdk/pull/2185)
* Show the 'homeserver unavailable' warning when the first sync fails
[\#2182](https://github.com/matrix-org/matrix-react-sdk/pull/2182)
* allow passing initial is_url like hs_url in query params
[\#2083](https://github.com/matrix-org/matrix-react-sdk/pull/2083)
* Update karma
[\#2177](https://github.com/matrix-org/matrix-react-sdk/pull/2177)
* fudge hangup reasons
[\#2184](https://github.com/matrix-org/matrix-react-sdk/pull/2184)
* Provide more helpful errors when i18n generation fails
[\#2181](https://github.com/matrix-org/matrix-react-sdk/pull/2181)
Changes in [0.13.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.13.6) (2018-10-08)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.5...v0.13.6)
* Fix resuming session in Firefox private mode/Tor browser being broken
[\#2195](https://github.com/matrix-org/matrix-react-sdk/pull/2195)
* Show warning about using lazy-loading/non-lazy-loading versions simultaneously (/app & /develop)
[\#2201](https://github.com/matrix-org/matrix-react-sdk/pull/2201)
Changes in [0.13.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.13.5) (2018-10-01)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.5-rc.1...v0.13.5)
* No changes since rc.1
Changes in [0.13.5-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.13.5-rc.1) (2018-09-27)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.4...v0.13.5-rc.1)
* resync when LL is toggled, show message when enabled
[\#2178](https://github.com/matrix-org/matrix-react-sdk/pull/2178)
* Update from Weblate.
[\#2179](https://github.com/matrix-org/matrix-react-sdk/pull/2179)
* Split npm start into an init and watch script
[\#2175](https://github.com/matrix-org/matrix-react-sdk/pull/2175)
* show canonical aliases in timeline, and set/remove implicit ones
[\#2171](https://github.com/matrix-org/matrix-react-sdk/pull/2171)
* Fix stale RR and improve LL reliability in RoomView & MemberList.
[\#2168](https://github.com/matrix-org/matrix-react-sdk/pull/2168)
* pass --travis flag to e2e tests to disable tests known not to work Travis CI
[\#2170](https://github.com/matrix-org/matrix-react-sdk/pull/2170)
* Add m.room.aliases to the timeline
[\#2167](https://github.com/matrix-org/matrix-react-sdk/pull/2167)
* postpone loading the members until the user joined the room
[\#2165](https://github.com/matrix-org/matrix-react-sdk/pull/2165)
* Allow translation tags object to be a variable
[\#2166](https://github.com/matrix-org/matrix-react-sdk/pull/2166)
* Don't try to exit fullscreen if not fullscreen
[\#2164](https://github.com/matrix-org/matrix-react-sdk/pull/2164)
* avoid updating the memberlist while the spinner is shown
[\#2161](https://github.com/matrix-org/matrix-react-sdk/pull/2161)
* fix logging room id when LL members fail
[\#2163](https://github.com/matrix-org/matrix-react-sdk/pull/2163)
* dont keep the spinner in the memberlist when fetching /members fails
[\#2162](https://github.com/matrix-org/matrix-react-sdk/pull/2162)
* only dispatch an action for self-membership
[\#2160](https://github.com/matrix-org/matrix-react-sdk/pull/2160)
* avoid unneeded lookups in memberDict
[\#2153](https://github.com/matrix-org/matrix-react-sdk/pull/2153)
* Update from Weblate.
[\#2157](https://github.com/matrix-org/matrix-react-sdk/pull/2157)
* avoid memberlist refresh for events related to rooms other but the current
[\#2156](https://github.com/matrix-org/matrix-react-sdk/pull/2156)
Changes in [0.13.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.13.4) (2018-09-10)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.4-rc.1...v0.13.4)
@ -859,7 +1042,7 @@ Changes in [0.12.0-rc.7](https://github.com/matrix-org/matrix-react-sdk/releases
[\#1816](https://github.com/matrix-org/matrix-react-sdk/pull/1816)
* Improve group joining/leaving feedback
[\#1831](https://github.com/matrix-org/matrix-react-sdk/pull/1831)
Changes in [0.12.0-rc.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.0-rc.6) (2018-04-09)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.0-rc.5...v0.12.0-rc.6)

View file

@ -93,6 +93,16 @@ Simply call `SettingsStore.getDisplayName`. The appropriate display name will be
Occasionally some parts of the application may be undergoing testing and are not quite production ready. These are commonly known to be behind a "labs flag". Features behind lab flags must go through the granular settings system, and look and act very much normal settings. The exception is that they must supply `isFeature: true` as part of the setting definition and should go through the helper functions on `SettingsStore`.
Although features have levels and a default value, the calculation of those options is blocked by the feature's state. A feature's state is determined from the `SdkConfig` and is a little complex. If `enableLabs` (a legacy flag) is `true` then the feature's state is `labs`, if it is `false`, the state is `disable`. If `enableLabs` is not set then the state is determined from the `features` config, such as in the following:
```json
"features": {
"feature_lazyloading": "labs"
}
```
In this example, `feature_lazyloading` is in the `labs` state. It may also be in the `enable` or `disable` state with a similar approach. If the state is invalid, the feature is in the `disable` state. A feature's levels are only calculated if it is in the `labs` state, therefore the default only applies in that scenario. If the state is `enable`, the feature is always-on.
Once a feature flag has served its purpose, it is generally recommended to remove it and the associated feature flag checks. This would enable the feature implicitly as it is part of the application now.
### Determining if a feature is enabled
A simple call to `SettingsStore.isFeatureEnabled` will tell you if the feature is enabled. This will perform all the required calculations to determine if the feature is enabled based upon the configuration and user selection.

View file

@ -23,6 +23,7 @@ var fs = require('fs');
//
var testFile = process.env.KARMA_TEST_FILE || 'test/all-tests.js';
process.env.PHANTOMJS_BIN = 'node_modules/.bin/phantomjs';
function fileExists(name) {
@ -160,10 +161,9 @@ module.exports = function (config) {
webpack: {
module: {
loaders: [
{ test: /\.json$/, loader: "json" },
rules: [
{
test: /\.js$/, loader: "babel",
test: /\.js$/, loader: "babel-loader",
include: [path.resolve('./src'),
path.resolve('./test'),
]
@ -200,8 +200,9 @@ module.exports = function (config) {
'matrix-react-sdk': path.resolve('test/skinned-sdk.js'),
'sinon': 'sinon/pkg/sinon.js',
},
root: [
modules: [
path.resolve('./test'),
"node_modules"
],
},
devtool: 'inline-source-map',
@ -210,6 +211,8 @@ module.exports = function (config) {
// (the 'commonjs' here means it will output a 'require')
"electron": "commonjs electron",
},
// make sure we're flagged as development to avoid wasting time optimising
mode: 'development',
},
webpackMiddleware: {

View file

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "0.13.4",
"version": "0.14.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@ -38,10 +38,12 @@
"reskindex:watch": "node scripts/reskindex.js -h header -w",
"i18n": "matrix-gen-i18n",
"prunei18n": "matrix-prune-i18n",
"build": "npm run reskindex && babel src -d lib --source-maps --copy-files",
"build:watch": "babel src -w -d lib --source-maps --copy-files",
"build": "npm run reskindex && npm run start:init",
"build:watch": "babel src -w --skip-initial-build -d lib --source-maps --copy-files",
"emoji-data-strip": "node scripts/emoji-data-strip.js",
"start": "parallelshell \"npm run build:watch\" \"npm run reskindex:watch\"",
"start": "npm run start:init && npm run start:all",
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"npm run build:watch\" \"npm run reskindex:watch\"",
"start:init": "babel src -d lib --source-maps --copy-files",
"lint": "eslint src/",
"lintall": "eslint src/ test/",
"lintwithexclusions": "eslint --max-warnings 20 --ignore-path .eslintignore.errorfiles src test",
@ -51,7 +53,7 @@
"test-multi": "karma start"
},
"dependencies": {
"babel-runtime": "^6.11.6",
"babel-runtime": "^6.26.0",
"bluebird": "^3.5.0",
"blueimp-canvas-to-blob": "^3.5.0",
"browser-encrypt-attachment": "^0.3.0",
@ -73,7 +75,7 @@
"linkifyjs": "^2.1.6",
"lodash": "^4.13.1",
"lolex": "2.3.2",
"matrix-js-sdk": "0.11.0",
"matrix-js-sdk": "0.12.0",
"optimist": "^0.6.1",
"pako": "^1.0.5",
"prop-types": "^15.5.8",
@ -86,60 +88,60 @@
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
"resize-observer-polyfill": "^1.5.0",
"sanitize-html": "^1.18.4",
"slate": "0.34.7",
"slate": "^0.41.2",
"slate-html-serializer": "^0.6.1",
"slate-md-serializer": "matrix-org/slate-md-serializer#f7c4ad3",
"slate-react": "^0.12.4",
"slate-react": "^0.18.10",
"text-encoding-utf-8": "^1.0.1",
"url": "^0.11.0",
"velocity-vector": "vector-im/velocity#059e3b2",
"whatwg-fetch": "^1.1.1"
},
"devDependencies": {
"babel-cli": "^6.5.2",
"babel-core": "^6.14.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^6.1.2",
"babel-loader": "^6.2.5",
"babel-loader": "^7.1.5",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
"babel-plugin-transform-class-properties": "^6.16.0",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-polyfill": "^6.5.0",
"babel-preset-es2015": "^6.14.0",
"babel-preset-es2016": "^6.11.3",
"babel-preset-es2017": "^6.14.0",
"babel-preset-react": "^6.11.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-es2016": "^6.24.1",
"babel-preset-es2017": "^6.24.1",
"babel-preset-react": "^6.24.1",
"chokidar": "^1.6.1",
"concurrently": "^4.0.1",
"eslint": "^3.13.1",
"eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^4.0.1",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-react": "^7.7.0",
"estree-walker": "^0.5.0",
"expect": "^1.16.0",
"flow-parser": "^0.57.3",
"json-loader": "^0.5.3",
"karma": "^1.7.0",
"karma": "^3.0.0",
"karma-chrome-launcher": "^0.2.3",
"karma-cli": "^0.1.2",
"karma-junit-reporter": "^0.4.1",
"karma-cli": "^1.0.1",
"karma-junit-reporter": "^0.4.2",
"karma-logcapture-reporter": "0.0.1",
"karma-mocha": "^0.2.2",
"karma-mocha": "^1.3.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.31",
"karma-summary-reporter": "^1.3.3",
"karma-webpack": "^1.7.0",
"karma-summary-reporter": "^1.5.1",
"karma-webpack": "^4.0.0-beta.0",
"matrix-mock-request": "^1.2.1",
"matrix-react-test-utils": "^0.1.1",
"mocha": "^5.0.5",
"parallelshell": "3.0.1",
"react-addons-test-utils": "^15.4.0",
"require-json": "0.0.1",
"rimraf": "^2.4.3",
"sinon": "^5.0.7",
"source-map-loader": "^0.2.3",
"walk": "^2.3.9",
"webpack": "^1.12.14"
"webpack": "^4.20.2",
"webpack-cli": "^3.1.1"
}
}

View file

@ -158,6 +158,7 @@ function getTranslationsJs(file) {
} catch (e) {
console.log();
console.error(`ERROR: ${file}:${node.loc.start.line} ${tKey}`);
console.error(e);
process.exit(1);
}
}

View file

@ -31,6 +31,7 @@ import RtsClient from './RtsClient';
import Modal from './Modal';
import sdk from './index';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
import PlatformPeg from "./PlatformPeg";
/**
* Called at startup, to attempt to build a logged-in Matrix session. It tries
@ -158,6 +159,40 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
});
}
export function handleInvalidStoreError(e) {
if (e.reason === Matrix.InvalidStoreError.TOGGLED_LAZY_LOADING) {
return Promise.resolve().then(() => {
const lazyLoadEnabled = e.value;
if (lazyLoadEnabled) {
const LazyLoadingResyncDialog =
sdk.getComponent("views.dialogs.LazyLoadingResyncDialog");
return new Promise((resolve) => {
Modal.createDialog(LazyLoadingResyncDialog, {
onFinished: resolve,
});
});
} else {
// show warning about simultaneous use
// between LL/non-LL version on same host.
// as disabling LL when previously enabled
// is a strong indicator of this (/develop & /app)
const LazyLoadingDisabledDialog =
sdk.getComponent("views.dialogs.LazyLoadingDisabledDialog");
return new Promise((resolve) => {
Modal.createDialog(LazyLoadingDisabledDialog, {
onFinished: resolve,
host: window.location.host,
});
});
}
}).then(() => {
return MatrixClientPeg.get().store.deleteAllData();
}).then(() => {
PlatformPeg.get().reload();
});
}
}
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
console.log(`Doing guest login on ${hsUrl}`);

View file

@ -18,12 +18,15 @@ limitations under the License.
'use strict';
import Matrix from 'matrix-js-sdk';
import utils from 'matrix-js-sdk/lib/utils';
import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline';
import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
import createMatrixClient from './utils/createMatrixClient';
import SettingsStore from './settings/SettingsStore';
import MatrixActionCreators from './actions/MatrixActionCreators';
import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
interface MatrixClientCreds {
homeserverUrl: string,
@ -51,6 +54,9 @@ class MatrixClientPeg {
this.opts = {
initialSyncLimit: 20,
};
// the credentials used to init the current client object.
// used if we tear it down & recreate it with a different store
this._currentClientCreds = null;
}
/**
@ -79,10 +85,30 @@ class MatrixClientPeg {
* Home Server / Identity Server URLs and active credentials
*/
replaceUsingCreds(creds: MatrixClientCreds) {
this._currentClientCreds = creds;
this._createClient(creds);
}
async start() {
for (const dbType of ['indexeddb', 'memory']) {
try {
const promise = this.matrixClient.store.startup();
console.log("MatrixClientPeg: waiting for MatrixClient store to initialise");
await promise;
break;
} catch (err) {
if (dbType === 'indexeddb') {
console.error('Error starting matrixclient store - falling back to memory store', err);
this.matrixClient.store = new Matrix.MatrixInMemoryStore({
localStorage: global.localStorage,
});
} else {
console.error('Failed to start memory store!', err);
throw err;
}
}
}
// try to initialise e2e on the new client
try {
// check that we have a version of the js-sdk which includes initCrypto
@ -99,22 +125,14 @@ class MatrixClientPeg {
// the react sdk doesn't work without this, so don't allow
opts.pendingEventOrdering = "detached";
if (SettingsStore.isFeatureEnabled('feature_lazyloading')) {
opts.lazyLoadMembers = true;
const LAZY_LOADING_FEATURE = "feature_lazyloading";
if (SettingsStore.isFeatureEnabled(LAZY_LOADING_FEATURE)) {
const userId = this.matrixClient.credentials.userId;
if (phasedRollOutExpiredForUser(userId, LAZY_LOADING_FEATURE, Date.now())) {
opts.lazyLoadMembers = true;
}
}
try {
const promise = this.matrixClient.store.startup();
console.log(`MatrixClientPeg: waiting for MatrixClient store to initialise`);
await promise;
} catch (err) {
// log any errors when starting up the database (if one exists)
console.error(`Error starting matrixclient store: ${err}`);
}
// regardless of errors, start the client. If we did error out, we'll
// just end up doing a full initial /sync.
// Connect the matrix client to the dispatcher
MatrixActionCreators.start(this.matrixClient);
@ -147,7 +165,7 @@ class MatrixClientPeg {
return matches[1];
}
_createClient(creds: MatrixClientCreds) {
_createClient(creds: MatrixClientCreds, useIndexedDb) {
const opts = {
baseUrl: creds.homeserverUrl,
idBaseUrl: creds.identityServerUrl,
@ -158,7 +176,7 @@ class MatrixClientPeg {
forceTURN: SettingsStore.getValue('webRtcForceTURN', false),
};
this.matrixClient = createMatrixClient(opts, this.indexedDbWorkerScript);
this.matrixClient = createMatrixClient(opts, useIndexedDb);
// we're going to add eventlisteners for each matrix event tile, so the
// potential number of event listeners is quite high.

65
src/PhasedRollOut.js Normal file
View file

@ -0,0 +1,65 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SdkConfig from './SdkConfig';
function hashCode(str) {
let hash = 0;
let i;
let chr;
if (str.length === 0) {
return hash;
}
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
}
return Math.abs(hash);
}
export function phasedRollOutExpiredForUser(username, feature, now, rollOutConfig = SdkConfig.get().phasedRollOut) {
if (!rollOutConfig) {
console.log(`no phased rollout configuration, so enabling ${feature}`);
return true;
}
const featureConfig = rollOutConfig[feature];
if (!featureConfig) {
console.log(`${feature} doesn't have phased rollout configured, so enabling`);
return true;
}
if (!Number.isFinite(featureConfig.offset) || !Number.isFinite(featureConfig.period)) {
console.error(`phased rollout of ${feature} is misconfigured, ` +
`offset and/or period are not numbers, so disabling`, featureConfig);
return false;
}
const hash = hashCode(username);
//ms -> min, enable users at minute granularity
const bucketRatio = 1000 * 60;
const bucketCount = featureConfig.period / bucketRatio;
const userBucket = hash % bucketCount;
const userMs = userBucket * bucketRatio;
const enableAt = featureConfig.offset + userMs;
const result = now >= enableAt;
const bucketStr = `(bucket ${userBucket}/${bucketCount})`;
if (result) {
console.log(`${feature} enabled for ${username} ${bucketStr}`);
} else {
console.log(`${feature} will be enabled for ${username} in ${Math.ceil((enableAt - now)/1000)}s ${bucketStr}`);
}
return result;
}

View file

@ -56,32 +56,31 @@ class ScalarAuthClient {
// Something went wrong - try to get a new token.
console.warn("Registering for new scalar token");
return this.registerForToken();
})
});
}
}
validateToken(token) {
let url = SdkConfig.get().integrations_rest_url + "/account";
const url = SdkConfig.get().integrations_rest_url + "/account";
const defer = Promise.defer();
request({
method: "GET",
uri: url,
qs: {scalar_token: token},
json: true,
}, (err, response, body) => {
if (err) {
defer.reject(err);
} else if (response.statusCode / 100 !== 2) {
defer.reject({statusCode: response.statusCode});
} else if (!body || !body.user_id) {
defer.reject(new Error("Missing user_id in response"));
} else {
defer.resolve(body.user_id);
}
return new Promise(function(resolve, reject) {
request({
method: "GET",
uri: url,
qs: {scalar_token: token},
json: true,
}, (err, response, body) => {
if (err) {
reject(err);
} else if (response.statusCode / 100 !== 2) {
reject({statusCode: response.statusCode});
} else if (!body || !body.user_id) {
reject(new Error("Missing user_id in response"));
} else {
resolve(body.user_id);
}
});
});
return defer.promise;
}
registerForToken() {
@ -96,56 +95,54 @@ class ScalarAuthClient {
}
exchangeForScalarToken(openid_token_object) {
const defer = Promise.defer();
const scalar_rest_url = SdkConfig.get().integrations_rest_url;
request({
method: 'POST',
uri: scalar_rest_url+'/register',
body: openid_token_object,
json: true,
}, (err, response, body) => {
if (err) {
defer.reject(err);
} else if (response.statusCode / 100 !== 2) {
defer.reject({statusCode: response.statusCode});
} else if (!body || !body.scalar_token) {
defer.reject(new Error("Missing scalar_token in response"));
} else {
defer.resolve(body.scalar_token);
}
});
return defer.promise;
return new Promise(function(resolve, reject) {
request({
method: 'POST',
uri: scalar_rest_url+'/register',
body: openid_token_object,
json: true,
}, (err, response, body) => {
if (err) {
reject(err);
} else if (response.statusCode / 100 !== 2) {
reject({statusCode: response.statusCode});
} else if (!body || !body.scalar_token) {
reject(new Error("Missing scalar_token in response"));
} else {
resolve(body.scalar_token);
}
});
});
}
getScalarPageTitle(url) {
const defer = Promise.defer();
let scalarPageLookupUrl = SdkConfig.get().integrations_rest_url + '/widgets/title_lookup';
scalarPageLookupUrl = this.getStarterLink(scalarPageLookupUrl);
scalarPageLookupUrl += '&curl=' + encodeURIComponent(url);
request({
method: 'GET',
uri: scalarPageLookupUrl,
json: true,
}, (err, response, body) => {
if (err) {
defer.reject(err);
} else if (response.statusCode / 100 !== 2) {
defer.reject({statusCode: response.statusCode});
} else if (!body) {
defer.reject(new Error("Missing page title in response"));
} else {
let title = "";
if (body.page_title_cache_item && body.page_title_cache_item.cached_title) {
title = body.page_title_cache_item.cached_title;
}
defer.resolve(title);
}
});
return defer.promise;
return new Promise(function(resolve, reject) {
request({
method: 'GET',
uri: scalarPageLookupUrl,
json: true,
}, (err, response, body) => {
if (err) {
reject(err);
} else if (response.statusCode / 100 !== 2) {
reject({statusCode: response.statusCode});
} else if (!body) {
reject(new Error("Missing page title in response"));
} else {
let title = "";
if (body.page_title_cache_item && body.page_title_cache_item.cached_title) {
title = body.page_title_cache_item.cached_title;
}
resolve(title);
}
});
});
}
/**

View file

@ -228,14 +228,12 @@ function textForRoomAliasesEvent(ev) {
removedAddresses: removedAliases.join(', '),
});
} else {
const args = {
senderName: senderName,
addedAddresses: addedAliases.join(', '),
removedAddresses: removedAliases.join(', '),
};
return _t(
'%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.',
args,
'%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.', {
senderName: senderName,
addedAddresses: addedAliases.join(', '),
removedAddresses: removedAliases.join(', '),
},
);
}
}
@ -250,8 +248,7 @@ function textForCanonicalAliasEvent(ev) {
senderName: senderName,
address: ev.getContent().alias,
});
}
else if (oldAlias) {
} else if (oldAlias) {
return _t('%(senderName)s removed the main address for this room.', {
senderName: senderName,
});
@ -275,6 +272,12 @@ function textForCallHangupEvent(event) {
reason = _t('(could not connect media)');
} else if (eventContent.reason === "invite_timeout") {
reason = _t('(no answer)');
} else if (eventContent.reason === "user hangup") {
// workaround for https://github.com/vector-im/riot-web/issues/5178
// it seems Android randomly sets a reason of "user hangup" which is
// interpreted as an error code :(
// https://github.com/vector-im/riot-android/issues/2623
reason = '';
} else {
reason = _t('(unknown failure: %(reason)s)', {reason: eventContent.reason});
}

View file

@ -17,9 +17,9 @@ limitations under the License.
"use strict";
import Promise from 'bluebird';
var Matrix = require("matrix-js-sdk");
var Room = Matrix.Room;
var CallHandler = require('./CallHandler');
const Matrix = require("matrix-js-sdk");
const Room = Matrix.Room;
const CallHandler = require('./CallHandler');
// FIXME: this is Riot (Vector) specific code, but will be removed shortly when
// we switch over to jitsi entirely for video conferencing.
@ -28,8 +28,8 @@ var CallHandler = require('./CallHandler');
// This is bad because it prevents people running their own ASes from being used.
// This isn't permanent and will be customisable in the future: see the proposal
// at docs/conferencing.md for more info.
var USER_PREFIX = "fs_";
var DOMAIN = "matrix.org";
const USER_PREFIX = "fs_";
const DOMAIN = "matrix.org";
function ConferenceCall(matrixClient, groupChatRoomId) {
this.client = matrixClient;
@ -38,14 +38,14 @@ function ConferenceCall(matrixClient, groupChatRoomId) {
}
ConferenceCall.prototype.setup = function() {
var self = this;
const self = this;
return this._joinConferenceUser().then(function() {
return self._getConferenceUserRoom();
}).then(function(room) {
// return a call for *this* room to be placed. We also tack on
// confUserId to speed up lookups (else we'd need to loop every room
// looking for a 1:1 room with this conf user ID!)
var call = Matrix.createNewMatrixCall(self.client, room.roomId);
const call = Matrix.createNewMatrixCall(self.client, room.roomId);
call.confUserId = self.confUserId;
call.groupRoomId = self.groupRoomId;
return call;
@ -54,11 +54,11 @@ ConferenceCall.prototype.setup = function() {
ConferenceCall.prototype._joinConferenceUser = function() {
// Make sure the conference user is in the group chat room
var groupRoom = this.client.getRoom(this.groupRoomId);
const groupRoom = this.client.getRoom(this.groupRoomId);
if (!groupRoom) {
return Promise.reject("Bad group room ID");
}
var member = groupRoom.getMember(this.confUserId);
const member = groupRoom.getMember(this.confUserId);
if (member && member.membership === "join") {
return Promise.resolve();
}
@ -67,10 +67,10 @@ ConferenceCall.prototype._joinConferenceUser = function() {
ConferenceCall.prototype._getConferenceUserRoom = function() {
// Use an existing 1:1 with the conference user; else make one
var rooms = this.client.getRooms();
var confRoom = null;
for (var i = 0; i < rooms.length; i++) {
var confUser = rooms[i].getMember(this.confUserId);
const rooms = this.client.getRooms();
let confRoom = null;
for (let i = 0; i < rooms.length; i++) {
const confUser = rooms[i].getMember(this.confUserId);
if (confUser && confUser.membership === "join" &&
rooms[i].getJoinedMemberCount() === 2) {
confRoom = rooms[i];
@ -82,7 +82,7 @@ ConferenceCall.prototype._getConferenceUserRoom = function() {
}
return this.client.createRoom({
preset: "private_chat",
invite: [this.confUserId]
invite: [this.confUserId],
}).then(function(res) {
return new Room(res.room_id, null, client.getUserId());
});
@ -97,9 +97,9 @@ module.exports.isConferenceUser = function(userId) {
if (userId.indexOf("@" + USER_PREFIX) !== 0) {
return false;
}
var base64part = userId.split(":")[0].substring(1 + USER_PREFIX.length);
const base64part = userId.split(":")[0].substring(1 + USER_PREFIX.length);
if (base64part) {
var decoded = new Buffer(base64part, "base64").toString();
const decoded = new Buffer(base64part, "base64").toString();
// ! $STUFF : $STUFF
return /^!.+:.+/.test(decoded);
}
@ -108,23 +108,23 @@ module.exports.isConferenceUser = function(userId) {
module.exports.getConferenceUserIdForRoom = function(roomId) {
// abuse browserify's core node Buffer support (strip padding ='s)
var base64RoomId = new Buffer(roomId).toString("base64").replace(/=/g, "");
const base64RoomId = new Buffer(roomId).toString("base64").replace(/=/g, "");
return "@" + USER_PREFIX + base64RoomId + ":" + DOMAIN;
};
module.exports.createNewMatrixCall = function(client, roomId) {
var confCall = new ConferenceCall(
client, roomId
const confCall = new ConferenceCall(
client, roomId,
);
return confCall.setup();
};
module.exports.getConferenceCallForRoom = function(roomId) {
// search for a conference 1:1 call for this group chat room ID
var activeCall = CallHandler.getAnyActiveCall();
const activeCall = CallHandler.getAnyActiveCall();
if (activeCall && activeCall.confUserId) {
var thisRoomConfUserId = module.exports.getConferenceUserIdForRoom(
roomId
const thisRoomConfUserId = module.exports.getConferenceUserIdForRoom(
roomId,
);
if (thisRoomConfUserId === activeCall.confUserId) {
return activeCall;

View file

@ -68,7 +68,9 @@ module.exports = React.createClass({
if (oldNode && oldNode.style.visibility == 'hidden' && c.props.style.visibility == 'visible') {
oldNode.style.visibility = c.props.style.visibility;
}
self.children[c.key] = old;
// clone the old element with the props (and children) of the new element
// so prop updates are still received by the children.
self.children[c.key] = React.cloneElement(old, c.props, c.props.children);
} else {
// new element. If we have a startStyle, use that as the style and go through
// the enter animations

View file

@ -20,7 +20,7 @@ limitations under the License.
import React from 'react';
import {_t} from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import {TextualCompletion} from './Components';
import type {Completion, SelectionRange} from "./Autocompleter";
import {CommandMap} from '../SlashCommands';
@ -32,7 +32,7 @@ const COMMAND_RE = /(^\/\w*)(?: .*)?/g;
export default class CommandProvider extends AutocompleteProvider {
constructor() {
super(COMMAND_RE);
this.matcher = new FuzzyMatcher(COMMANDS, {
this.matcher = new QueryMatcher(COMMANDS, {
keys: ['command', 'args', 'description'],
});
}

View file

@ -19,7 +19,7 @@ import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import MatrixClientPeg from '../MatrixClientPeg';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components';
import sdk from '../index';
import _sortBy from 'lodash/sortBy';
@ -41,7 +41,7 @@ function score(query, space) {
export default class CommunityProvider extends AutocompleteProvider {
constructor() {
super(COMMUNITY_REGEX);
this.matcher = new FuzzyMatcher([], {
this.matcher = new QueryMatcher([], {
keys: ['groupId', 'name', 'shortDescription'],
});
}

View file

@ -20,7 +20,7 @@ import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import sdk from '../index';
import {PillCompletion} from './Components';
import type {Completion, SelectionRange} from './Autocompleter';
@ -84,12 +84,12 @@ function score(query, space) {
export default class EmojiProvider extends AutocompleteProvider {
constructor() {
super(EMOJI_REGEX);
this.matcher = new FuzzyMatcher(EMOJI_SHORTNAMES, {
this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, {
keys: ['aliases_ascii', 'shortname', 'aliases'],
// For matching against ascii equivalents
shouldMatchWordsOnly: false,
});
this.nameMatcher = new FuzzyMatcher(EMOJI_SHORTNAMES, {
this.nameMatcher = new QueryMatcher(EMOJI_SHORTNAMES, {
keys: ['name'],
// For removing punctuation
shouldMatchWordsOnly: true,

View file

@ -1,107 +0,0 @@
/*
Copyright 2017 Aviral Dasgupta
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//import Levenshtein from 'liblevenshtein';
//import _at from 'lodash/at';
//import _flatMap from 'lodash/flatMap';
//import _sortBy from 'lodash/sortBy';
//import _sortedUniq from 'lodash/sortedUniq';
//import _keys from 'lodash/keys';
//
//class KeyMap {
// keys: Array<String>;
// objectMap: {[String]: Array<Object>};
// priorityMap: {[String]: number}
//}
//
//const DEFAULT_RESULT_COUNT = 10;
//const DEFAULT_DISTANCE = 5;
// FIXME Until Fuzzy matching works better, we use prefix matching.
import PrefixMatcher from './QueryMatcher';
export default PrefixMatcher;
//class FuzzyMatcher { // eslint-disable-line no-unused-vars
// /**
// * @param {object[]} objects the objects to perform a match on
// * @param {string[]} keys an array of keys within each object to match on
// * Keys can refer to object properties by name and as in JavaScript (for nested properties)
// *
// * To use, simply presort objects by required criteria, run through this function and create a FuzzyMatcher with the
// * resulting KeyMap.
// *
// * TODO: Handle arrays and objects (Fuse did this, RoomProvider uses it)
// * @return {KeyMap}
// */
// static valuesToKeyMap(objects: Array<Object>, keys: Array<String>): KeyMap {
// const keyMap = new KeyMap();
// const map = {};
// const priorities = {};
//
// objects.forEach((object, i) => {
// const keyValues = _at(object, keys);
// console.log(object, keyValues, keys);
// for (const keyValue of keyValues) {
// if (!map.hasOwnProperty(keyValue)) {
// map[keyValue] = [];
// }
// map[keyValue].push(object);
// }
// priorities[object] = i;
// });
//
// keyMap.objectMap = map;
// keyMap.priorityMap = priorities;
// keyMap.keys = _sortBy(_keys(map), [(value) => priorities[value]]);
// return keyMap;
// }
//
// constructor(objects: Array<Object>, options: {[Object]: Object} = {}) {
// this.options = options;
// this.keys = options.keys;
// this.setObjects(objects);
// }
//
// setObjects(objects: Array<Object>) {
// this.keyMap = FuzzyMatcher.valuesToKeyMap(objects, this.keys);
// console.log(this.keyMap.keys);
// this.matcher = new Levenshtein.Builder()
// .dictionary(this.keyMap.keys, true)
// .algorithm('transposition')
// .sort_candidates(false)
// .case_insensitive_sort(true)
// .include_distance(true)
// .maximum_candidates(this.options.resultCount || DEFAULT_RESULT_COUNT) // result count 0 doesn't make much sense
// .build();
// }
//
// match(query: String): Array<Object> {
// const candidates = this.matcher.transduce(query, this.options.distance || DEFAULT_DISTANCE);
// // TODO FIXME This is hideous. Clean up when possible.
// const val = _sortedUniq(_sortBy(_flatMap(candidates, (candidate) => {
// return this.keyMap.objectMap[candidate[0]].map((value) => {
// return {
// distance: candidate[1],
// ...value,
// };
// });
// }),
// [(candidate) => candidate.distance, (candidate) => this.keyMap.priorityMap[candidate]]));
// console.log(val);
// return val;
// }
//}

View file

@ -2,6 +2,7 @@
/*
Copyright 2017 Aviral Dasgupta
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -20,99 +21,99 @@ import _at from 'lodash/at';
import _flatMap from 'lodash/flatMap';
import _sortBy from 'lodash/sortBy';
import _uniq from 'lodash/uniq';
import _keys from 'lodash/keys';
class KeyMap {
keys: Array<String>;
objectMap: {[String]: Array<Object>};
priorityMap = new Map();
}
function stripDiacritics(str: string): string {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
/**
* Simple search matcher that matches any results with the query string anywhere
* in the search string. Returns matches in the order the query string appears
* in the search key, earliest first, then in the order the items appeared in
* the source array.
*
* @param {Object[]} objects Initial list of objects. Equivalent to calling
* setObjects() after construction
* @param {Object} options Options object
* @param {string[]} options.keys List of keys to use as indexes on the objects
* @param {function[]} options.funcs List of functions that when called with the
* object as an arg will return a string to use as an index
*/
export default class QueryMatcher {
/**
* @param {object[]} objects the objects to perform a match on
* @param {string[]} keys an array of keys within each object to match on
* Keys can refer to object properties by name and as in JavaScript (for nested properties)
*
* To use, simply presort objects by required criteria, run through this function and create a QueryMatcher with the
* resulting KeyMap.
*
* TODO: Handle arrays and objects (Fuse did this, RoomProvider uses it)
* @return {KeyMap}
*/
static valuesToKeyMap(objects: Array<Object>, keys: Array<String>): KeyMap {
const keyMap = new KeyMap();
const map = {};
objects.forEach((object, i) => {
const keyValues = _at(object, keys);
for (const keyValue of keyValues) {
const key = stripDiacritics(keyValue).toLowerCase();
if (!map.hasOwnProperty(key)) {
map[key] = [];
}
map[key].push(object);
}
keyMap.priorityMap.set(object, i);
});
keyMap.objectMap = map;
keyMap.keys = _keys(map);
return keyMap;
}
constructor(objects: Array<Object>, options: {[Object]: Object} = {}) {
this.options = options;
this.keys = options.keys;
this._options = options;
this._keys = options.keys;
this._funcs = options.funcs || [];
this.setObjects(objects);
// By default, we remove any non-alphanumeric characters ([^A-Za-z0-9_]) from the
// query and the value being queried before matching
if (this.options.shouldMatchWordsOnly === undefined) {
this.options.shouldMatchWordsOnly = true;
if (this._options.shouldMatchWordsOnly === undefined) {
this._options.shouldMatchWordsOnly = true;
}
// By default, match anywhere in the string being searched. If enabled, only return
// matches that are prefixed with the query.
if (this.options.shouldMatchPrefix === undefined) {
this.options.shouldMatchPrefix = false;
if (this._options.shouldMatchPrefix === undefined) {
this._options.shouldMatchPrefix = false;
}
}
setObjects(objects: Array<Object>) {
this.keyMap = QueryMatcher.valuesToKeyMap(objects, this.keys);
this._items = new Map();
for (const object of objects) {
const keyValues = _at(object, this._keys);
for (const f of this._funcs) {
keyValues.push(f(object));
}
for (const keyValue of keyValues) {
const key = stripDiacritics(keyValue).toLowerCase();
if (!this._items.has(key)) {
this._items.set(key, []);
}
this._items.get(key).push(object);
}
}
}
match(query: String): Array<Object> {
query = stripDiacritics(query).toLowerCase();
if (this.options.shouldMatchWordsOnly) {
if (this._options.shouldMatchWordsOnly) {
query = query.replace(/[^\w]/g, '');
}
if (query.length === 0) {
return [];
}
const results = [];
this.keyMap.keys.forEach((key) => {
// Iterate through the map & check each key.
// ES6 Map iteration order is defined to be insertion order, so results
// here will come out in the order they were put in.
for (const key of this._items.keys()) {
let resultKey = key;
if (this.options.shouldMatchWordsOnly) {
if (this._options.shouldMatchWordsOnly) {
resultKey = resultKey.replace(/[^\w]/g, '');
}
const index = resultKey.indexOf(query);
if (index !== -1 && (!this.options.shouldMatchPrefix || index === 0)) {
if (index !== -1 && (!this._options.shouldMatchPrefix || index === 0)) {
results.push({key, index});
}
}
// Sort them by where the query appeared in the search key
// lodash sortBy is a stable sort, so results where the query
// appeared in the same place will retain their order with
// respect to each other.
const sortedResults = _sortBy(results, (candidate) => {
return candidate.index;
});
return _uniq(_flatMap(_sortBy(results, (candidate) => {
return candidate.index;
}).map((candidate) => {
// return an array of objects (those given to setObjects) that have the given
// key as a property.
return this.keyMap.objectMap[candidate.key];
})));
// Now map the keys to the result objects. Each result object is a list, so
// flatMap will flatten those lists out into a single list. Also remove any
// duplicates.
return _uniq(_flatMap(sortedResults, (candidate) => this._items.get(candidate.key)));
}
}

View file

@ -21,7 +21,7 @@ import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import MatrixClientPeg from '../MatrixClientPeg';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components';
import {getDisplayAliasForRoom} from '../Rooms';
import sdk from '../index';
@ -43,7 +43,7 @@ function score(query, space) {
export default class RoomProvider extends AutocompleteProvider {
constructor() {
super(ROOM_REGEX);
this.matcher = new FuzzyMatcher([], {
this.matcher = new QueryMatcher([], {
keys: ['displayedAlias', 'name'],
});
}

View file

@ -23,7 +23,7 @@ import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {PillCompletion} from './Components';
import sdk from '../index';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import _sortBy from 'lodash/sortBy';
import MatrixClientPeg from '../MatrixClientPeg';
@ -44,8 +44,9 @@ export default class UserProvider extends AutocompleteProvider {
constructor(room) {
super(USER_REGEX, FORCED_USER_REGEX);
this.room = room;
this.matcher = new FuzzyMatcher([], {
keys: ['name', 'userId'],
this.matcher = new QueryMatcher([], {
keys: ['name'],
funcs: [obj => obj.userId.slice(1)], // index by user id minus the leading '@'
shouldMatchPrefix: true,
shouldMatchWordsOnly: false,
});
@ -104,12 +105,14 @@ export default class UserProvider extends AutocompleteProvider {
const fullMatch = command[0];
// Don't search if the query is a single "@"
if (fullMatch && fullMatch !== '@') {
completions = this.matcher.match(fullMatch).map((user) => {
const displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done
// Don't include the '@' in our search query - it's only used as a way to trigger completion
const query = fullMatch.startsWith('@') ? fullMatch.substring(1) : fullMatch;
completions = this.matcher.match(query).map((user) => {
const displayName = (user.name || user.userId || '');
return {
// Length of completion should equal length of text in decorator. draft-js
// relies on the length of the entity === length of the text in the decoration.
completion: user.rawDisplayName.replace(' (IRC)', ''),
completion: user.rawDisplayName,
completionId: user.userId,
suffix: (selection.beginning && range.start === 0) ? ': ' : ' ',
href: makeUserPermalink(user.userId),

View file

@ -319,7 +319,7 @@ const LoggedInView = React.createClass({
), true);
},
_onClick: function(ev) {
_onMouseDown: function(ev) {
// When the panels are disabled, clicking on them results in a mouse event
// which bubbles to certain elements in the tree. When this happens, close
// any settings page that is currently open (user/room/group).
@ -330,11 +330,37 @@ const LoggedInView = React.createClass({
targetClasses.has('mx_MatrixChat_middlePanel') ||
targetClasses.has('mx_RoomView')
) {
dis.dispatch({ action: 'close_settings' });
this.setState({
mouseDown: {
x: ev.pageX,
y: ev.pageY,
},
});
}
}
},
_onMouseUp: function(ev) {
if (!this.state.mouseDown) return;
const deltaX = ev.pageX - this.state.mouseDown.x;
const deltaY = ev.pageY - this.state.mouseDown.y;
const distance = Math.sqrt((deltaX * deltaX) + (deltaY + deltaY));
const maxRadius = 5; // People shouldn't be straying too far, hopefully
// Note: we track how far the user moved their mouse to help
// combat against https://github.com/vector-im/riot-web/issues/7158
if (distance < maxRadius) {
// This is probably a real click, and not a drag
dis.dispatch({ action: 'close_settings' });
}
// Always clear the mouseDown state to ensure we don't accidentally
// use stale values due to the mouseDown checks.
this.setState({mouseDown: null});
},
render: function() {
const LeftPanel = sdk.getComponent('structures.LeftPanel');
const RightPanel = sdk.getComponent('structures.RightPanel');
@ -478,7 +504,7 @@ const LoggedInView = React.createClass({
}
return (
<div className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onClick={this._onClick}>
<div className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
{ topBar }
<DragDropContext onDragEnd={this._onDragEnd}>
<div className={bodyClasses}>

View file

@ -48,6 +48,10 @@ import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
import { startAnyRegistrationFlow } from "../../Registration.js";
import { messageForSyncError } from '../../utils/ErrorUtils';
// Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings.
Promise.config({warnings: false});
/** constants for MatrixChat.state.view */
const VIEWS = {
// a special initial state which is only used at startup, while we are
@ -286,6 +290,14 @@ export default React.createClass({
register_hs_url: paramHs,
});
}
// Set a default IS with query param `is_url`
const paramIs = this.props.startingFragmentQueryParams.is_url;
if (paramIs) {
console.log('Setting register_is_url ', paramIs);
this.setState({
register_is_url: paramIs,
});
}
// a thing to call showScreen with once login completes. this is kept
// outside this.state because updating it should never trigger a
@ -1253,8 +1265,11 @@ export default React.createClass({
// its own dispatch).
dis.dispatch({action: 'sync_state', prevState, state});
if (state === "ERROR") {
self.setState({syncError: data.error});
if (state === "ERROR" || state === "RECONNECTING") {
if (data.error instanceof Matrix.InvalidStoreError) {
Lifecycle.handleInvalidStoreError(data.error);
}
self.setState({syncError: data.error || true});
} else if (self.state.syncError) {
self.setState({syncError: null});
}
@ -1730,10 +1745,14 @@ export default React.createClass({
}
if (this.state.view === VIEWS.LOGGED_IN) {
// store errors stop the client syncing and require user intervention, so we'll
// be showing a dialog. Don't show anything else.
const isStoreError = this.state.syncError && this.state.syncError instanceof Matrix.InvalidStoreError;
// `ready` and `view==LOGGED_IN` may be set before `page_type` (because the
// latter is set via the dispatcher). If we don't yet have a `page_type`,
// keep showing the spinner for now.
if (this.state.ready && this.state.page_type) {
if (this.state.ready && this.state.page_type && !isStoreError) {
/* for now, we stuff the entirety of our props and state into the LoggedInView.
* we should go through and figure out what we actually need to pass down, as well
* as using something like redux to avoid having a billion bits of state kicking around.
@ -1755,7 +1774,7 @@ export default React.createClass({
// we think we are logged in, but are still waiting for the /sync to complete
const Spinner = sdk.getComponent('elements.Spinner');
let errorBox;
if (this.state.syncError) {
if (this.state.syncError && !isStoreError) {
errorBox = <div className="mx_MatrixChat_syncError">
{messageForSyncError(this.state.syncError)}
</div>;

View file

@ -542,7 +542,7 @@ module.exports = React.createClass({
},
// get a list of read receipts that should be shown next to this event
// Receipts are objects which have a 'roomMember' and 'ts'.
// Receipts are objects which have a 'userId', 'roomMember' and 'ts'.
_getReadReceiptsForEvent: function(event) {
const myUserId = MatrixClientPeg.get().credentials.userId;
@ -560,10 +560,8 @@ module.exports = React.createClass({
return; // ignore ignored users
}
const member = room.getMember(r.userId);
if (!member) {
return; // ignore unknown user IDs
}
receipts.push({
userId: r.userId,
roomMember: member,
ts: r.data ? r.data.ts : 0,
});

View file

@ -51,6 +51,7 @@ class HeaderButton extends React.Component {
return <AccessibleButton
aria-label={this.props.title}
aria-expanded={this.props.isHighlighted}
title={this.props.title}
className="mx_RightPanel_headerButton"
onClick={this.onClick} >
@ -345,11 +346,11 @@ module.exports = React.createClass({
// being put in the RoomHeader or GroupView header, so only show the minimise
// button on these 2 screens or you won't be able to re-expand the panel.
headerButtons.push(
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" key="_minimizeButton"
<AccessibleButton className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" key="_minimizeButton"
title={_t("Hide panel")} aria-label={_t("Hide panel")} onClick={this.onCollapseClick}
>
<TintableSvg src="img/minimise.svg" width="10" height="16" />
</div>,
<TintableSvg src="img/minimise.svg" width="10" height="16" alt="" />
</AccessibleButton>,
);
}

View file

@ -223,14 +223,15 @@ module.exports = React.createClass({
);
}
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
if (!this.props.atEndOfLiveTimeline) {
return (
<div className="mx_RoomStatusBar_scrollDownIndicator"
<AccessibleButton className="mx_RoomStatusBar_scrollDownIndicator"
onClick={this.props.onScrollToBottomClick}>
<img src="img/scrolldown.svg" width="24" height="24"
alt={_t("Scroll to bottom of page")}
title={_t("Scroll to bottom of page")} />
</div>
</AccessibleButton>
);
}
@ -385,7 +386,7 @@ module.exports = React.createClass({
}
return <div className="mx_RoomStatusBar_connectionLostBar">
<img src="img/warning.svg" width="24" height="23" title={_t("Warning")} alt={_t("Warning")} />
<img src="img/warning.svg" width="24" height="23" title={_t("Warning")} alt="" />
<div>
<div className="mx_RoomStatusBar_connectionLostBar_title">
{ title }
@ -485,7 +486,9 @@ module.exports = React.createClass({
<div className="mx_RoomStatusBar_indicator">
{ indicator }
</div>
{ content }
<div role="alert">
{ content }
</div>
</div>
);
},

View file

@ -195,6 +195,8 @@ module.exports = React.createClass({
editingRoomSettings: RoomViewStore.isEditingSettings(),
};
if (this.state.editingRoomSettings && !newState.editingRoomSettings) dis.dispatch({action: 'focus_composer'});
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
console.log(
'RVS update:',

View file

@ -804,7 +804,7 @@ module.exports = React.createClass({
}
return (
<div>
<h3>{ _t("Debug Logs Submission") }</h3>
<h3>{ _t("Submit Debug Logs") }</h3>
<div className="mx_UserSettings_section">
<p>{
_t( "If you've submitted a bug via GitHub, debug logs can help " +
@ -832,9 +832,9 @@ module.exports = React.createClass({
<br />
{ _t('Privacy is important to us, so we don\'t collect any personal'
+ ' or identifiable data for our analytics.') }
<div className="mx_UserSettings_advanced_spoiler" onClick={Analytics.showDetailsModal}>
<AccessibleButton className="mx_UserSettings_advanced_spoiler" onClick={Analytics.showDetailsModal}>
{ _t('Learn more about how we use analytics.') }
</div>
</AccessibleButton>
{ ANALYTICS_SETTINGS.map( this._renderDeviceSetting ) }
</div>
</div>;
@ -1066,9 +1066,9 @@ module.exports = React.createClass({
_renderWebRtcDeviceSettings: function() {
if (this.state.mediaDevices === false) {
return (
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
<AccessibleButton element="p" className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
{ _t('Missing Media Permissions, click here to request.') }
</p>
</AccessibleButton>
);
} else if (!this.state.mediaDevices) return;
@ -1235,7 +1235,7 @@ module.exports = React.createClass({
/>
</div>
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
<img src="img/cancel-small.svg" width="14" height="14" alt={_t("Remove")}
<AccessibleButton element="img" src="img/cancel-small.svg" width="14" height="14" alt={_t("Remove")}
onClick={onRemoveClick} />
</div>
</div>
@ -1260,7 +1260,7 @@ module.exports = React.createClass({
onValueChanged={this._onAddEmailEditFinished} />
</div>
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
<img src="img/plus.svg" width="14" height="14" alt={_t("Add")} onClick={this._addEmail} />
<AccessibleButton element="img" src="img/plus.svg" width="14" height="14" alt={_t("Add")} onClick={this._addEmail} />
</div>
</div>
);
@ -1329,13 +1329,13 @@ module.exports = React.createClass({
</div>
<div className="mx_UserSettings_avatarPicker">
<div className="mx_UserSettings_avatarPicker_remove" onClick={this.onAvatarRemoveClick}>
<AccessibleButton className="mx_UserSettings_avatarPicker_remove" onClick={this.onAvatarRemoveClick}>
<img src="img/cancel.svg"
width="15" height="15"
className="mx_filterFlipColor"
alt={_t("Remove avatar")}
title={_t("Remove avatar")} />
</div>
</AccessibleButton>
<div onClick={this.onAvatarPickerClick} className="mx_UserSettings_avatarPicker_imgContainer">
<ChangeAvatar ref="changeAvatar" initialAvatarUrl={avatarUrl}
showUploadSection={false} className="mx_UserSettings_avatarPicker_img" />
@ -1396,11 +1396,11 @@ module.exports = React.createClass({
</div>
<div className="mx_UserSettings_advanced">
{ _t('Access Token:') + ' ' }
<span className="mx_UserSettings_advanced_spoiler"
<AccessibleButton element="span" className="mx_UserSettings_advanced_spoiler"
onClick={this._showSpoiler}
data-spoiler={MatrixClientPeg.get().getAccessToken()}>
&lt;{ _t("click to reveal") }&gt;
</span>
</AccessibleButton>
</div>
<div className="mx_UserSettings_advanced">
{ _t("Homeserver is") } { MatrixClientPeg.get().getHomeserverUrl() }

View file

@ -321,6 +321,12 @@ module.exports = React.createClass({
case "RegistrationForm.ERR_PHONE_NUMBER_INVALID":
errMsg = _t('This doesn\'t look like a valid phone number.');
break;
case "RegistrationForm.ERR_MISSING_EMAIL":
errMsg = _t('An email address is required to register on this homeserver.');
break;
case "RegistrationForm.ERR_MISSING_PHONE_NUMBER":
errMsg = _t('A phone number is required to register on this homeserver.');
break;
case "RegistrationForm.ERR_USERNAME_INVALID":
errMsg = _t('User names may only contain letters, numbers, dots, hyphens and underscores.');
break;

View file

@ -26,7 +26,8 @@ module.exports = React.createClass({
displayName: 'MemberAvatar',
propTypes: {
member: PropTypes.object.isRequired,
member: PropTypes.object,
fallbackUserId: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
resizeMethod: PropTypes.string,
@ -55,23 +56,30 @@ module.exports = React.createClass({
},
_getState: function(props) {
if (!props.member) {
console.error("MemberAvatar called somehow with null member");
if (props.member) {
return {
name: props.member.name,
title: props.title || props.member.userId,
imageUrl: Avatar.avatarUrlForMember(props.member,
props.width,
props.height,
props.resizeMethod),
};
} else if (props.fallbackUserId) {
return {
name: props.fallbackUserId,
title: props.fallbackUserId,
};
} else {
console.error("MemberAvatar called somehow with null member or fallbackUserId");
}
return {
name: props.member.name,
title: props.title || props.member.userId,
imageUrl: Avatar.avatarUrlForMember(props.member,
props.width,
props.height,
props.resizeMethod),
};
},
render: function() {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
let {member, onClick, viewUserOnClick, ...otherProps} = this.props;
let {member, fallbackUserId, onClick, viewUserOnClick, ...otherProps} = this.props;
let userId = member ? member.userId : fallbackUserId;
if (viewUserOnClick) {
onClick = () => {
@ -84,7 +92,7 @@ module.exports = React.createClass({
return (
<BaseAvatar {...otherProps} name={this.state.name} title={this.state.title}
idName={member.userId} url={this.state.imageUrl} onClick={onClick} />
idName={userId} url={this.state.imageUrl} onClick={onClick} />
);
},
});

View file

@ -140,9 +140,9 @@ export default class BugReportDialog extends React.Component {
"not contain messages.",
) }
</p>
<p>
<p><b>
{ _t(
"Riot bugs are tracked on GitHub: <a>create a GitHub issue</a>.",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.",
{},
{
a: (sub) => <a
@ -153,13 +153,13 @@ export default class BugReportDialog extends React.Component {
</a>,
},
) }
</p>
</b></p>
<div className="mx_BugReportDialog_field_container">
<label
htmlFor="mx_BugReportDialog_issueUrl"
className="mx_BugReportDialog_field_label"
>
{ _t("GitHub issue link:") }
{ _t("What GitHub issue are these logs for?") }
</label>
<input
id="mx_BugReportDialog_issueUrl"
@ -167,7 +167,7 @@ export default class BugReportDialog extends React.Component {
className="mx_BugReportDialog_field_input"
onChange={this._onIssueUrlChange}
value={this.state.issueUrl}
placeholder="https://github.com/vector-im/riot-web/issues/1337"
placeholder="https://github.com/vector-im/riot-web/issues/..."
/>
</div>
<div className="mx_BugReportDialog_field_container">

View file

@ -0,0 +1,39 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import QuestionDialog from './QuestionDialog';
import { _t } from '../../../languageHandler';
export default (props) => {
const description1 =
_t("You've previously used Riot on %(host)s with lazy loading of members enabled. " +
"In this version lazy loading is disabled. " +
"As the local cache is not compatible between these two settings, " +
"Riot needs to resync your account.",
{host: props.host});
const description2 = _t("If the other version of Riot is still open in another tab, " +
"please close it as using Riot on the same host with both " +
"lazy loading enabled and disabled simultaneously will cause issues.");
return (<QuestionDialog
hasCancelButton={false}
title={_t("Incompatible local cache")}
description={<div><p>{description1}</p><p>{description2}</p></div>}
button={_t("Clear cache and resync")}
onFinished={props.onFinished}
/>);
};

View file

@ -0,0 +1,33 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import QuestionDialog from './QuestionDialog';
import { _t } from '../../../languageHandler';
export default (props) => {
const description =
_t("Riot now uses 3-5x less memory, by only loading information about other users"
+ " when needed. Please wait whilst we resynchronise with the server!");
return (<QuestionDialog
hasCancelButton={false}
title={_t("Updating Riot")}
description={<div>{description}</div>}
button={_t("OK")}
onFinished={props.onFinished}
/>);
};

View file

@ -62,6 +62,7 @@ export default React.createClass({
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished}
title={this.props.title}
contentId='mx_Dialog_content'
hasCancel={this.props.hasCancelButton}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ this.props.description }

View file

@ -1,12 +1,11 @@
import React from 'react'; // eslint-disable-line no-unused-vars
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
const AppWarning = (props) => {
return (
<div className='mx_AppPermissionWarning'>
<div className='mx_AppPermissionWarningImage'>
<img src='img/warning.svg' alt={_t('Warning!')} />
<img src='img/warning.svg' alt='' />
</div>
<div className='mx_AppPermissionWarningText'>
<span className='mx_AppPermissionWarningTextLabel'>{ props.errorMsg }</span>

View file

@ -234,7 +234,7 @@ const Pill = React.createClass({
if (member) {
userId = member.userId;
member.rawDisplayName = member.rawDisplayName || '';
linkText = member.rawDisplayName.replace(' (IRC)', ''); // FIXME when groups are done
linkText = member.rawDisplayName;
if (this.props.shouldShowPillAvatar) {
avatar = <MemberAvatar member={member} width={16} height={16} />;
}

View file

@ -51,7 +51,7 @@ export default class CookieBar extends React.Component {
const toolbarClasses = "mx_MatrixToolbar";
return (
<div className={toolbarClasses}>
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="Warning" />
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="" />
<div className="mx_MatrixToolbar_content">
{ this.props.policyUrl ? _t(
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. " +
@ -95,7 +95,7 @@ export default class CookieBar extends React.Component {
{ _t("Yes, I want to help!") }
</AccessibleButton>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.onReject}>
<img src="img/cancel.svg" width="18" height="18" />
<img src="img/cancel.svg" width="18" height="18" alt={_t('Close')} />
</AccessibleButton>
</div>
);

View file

@ -35,11 +35,11 @@ module.exports = React.createClass({
render: function() {
return (
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="Warning"/>
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" />
<div className="mx_MatrixToolbar_content">
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
</div>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" /></AccessibleButton>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/></AccessibleButton>
</div>
);
},

View file

@ -96,7 +96,7 @@ export default React.createClass({
}
return (
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="Warning"/>
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" />
<div className="mx_MatrixToolbar_content">
{_t("A new version of Riot is available.")}
</div>

View file

@ -34,7 +34,7 @@ export default React.createClass({
src="img/warning.svg"
width="24"
height="23"
alt="Warning"
alt=""
/>
<div className="mx_MatrixToolbar_content">
{ _t(

View file

@ -71,9 +71,9 @@ export default React.createClass({
let image;
if (doneStatuses.includes(this.props.status)) {
image = <img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt={warning}/>;
image = <img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="" />;
} else {
image = <img className="mx_MatrixToolbar_warning" src="img/spinner.gif" width="24" height="23" alt={message}/>;
image = <img className="mx_MatrixToolbar_warning" src="img/spinner.gif" width="24" height="23" alt="" />;
}
return (
@ -83,7 +83,7 @@ export default React.createClass({
{message}
</div>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.hideToolbar}>
<img src="img/cancel.svg" width="18" height="18" />
<img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/>
</AccessibleButton>
</div>
);

View file

@ -182,12 +182,16 @@ module.exports = React.createClass({
});
}
const emailValid = email === '' || Email.looksValid(email);
this.markFieldValid(field_id, emailValid, "RegistrationForm.ERR_EMAIL_INVALID");
if (this._authStepIsRequired('m.login.email.identity') && (!emailValid || email === '')) {
this.markFieldValid(field_id, false, "RegistrationForm.ERR_MISSING_EMAIL");
} else this.markFieldValid(field_id, emailValid, "RegistrationForm.ERR_EMAIL_INVALID");
break;
case FIELD_PHONE_NUMBER:
const phoneNumber = this.refs.phoneNumber ? this.refs.phoneNumber.value : '';
const phoneNumberValid = phoneNumber === '' || phoneNumberLooksValid(phoneNumber);
this.markFieldValid(field_id, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID");
if (this._authStepIsRequired('m.login.msisdn') && (!phoneNumberValid || phoneNumber === '')) {
this.markFieldValid(field_id, false, "RegistrationForm.ERR_MISSING_PHONE_NUMBER");
} else this.markFieldValid(field_id, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID");
break;
case FIELD_USERNAME:
// XXX: SPEC-1

View file

@ -96,7 +96,7 @@ export default React.createClass({
render() {
const EmojiText = sdk.getComponent('elements.EmojiText');
const {mxEvent} = this.props;
let name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
const {msgtype} = mxEvent.getContent();
if (msgtype === 'm.emote') {
@ -109,9 +109,6 @@ export default React.createClass({
this.state.userGroups, this.state.relatedGroups,
);
// Backwards-compatible replacing of "(IRC)" with AS user flair
name = displayedGroups.length > 0 ? name.replace(' (IRC)', '') : name;
flair = <Flair key='flair'
userId={mxEvent.getSender()}
groups={displayedGroups}

View file

@ -30,6 +30,7 @@ import ScalarMessaging from '../../../ScalarMessaging';
import { _t } from '../../../languageHandler';
import WidgetUtils from '../../../utils/WidgetUtils';
import WidgetEchoStore from "../../../stores/WidgetEchoStore";
import AccessibleButton from '../elements/AccessibleButton';
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
@ -197,17 +198,15 @@ module.exports = React.createClass({
if (this.props.showApps &&
this._canUserModify()
) {
addWidget = <div
addWidget = <AccessibleButton
onClick={this.onClickAddWidget}
role='button'
tabIndex='0'
className={this.state.apps.length<2 ?
'mx_AddWidget_button mx_AddWidget_button_full_width' :
'mx_AddWidget_button'
}
title={_t('Add a widget')}>
[+] { _t('Add a widget') }
</div>;
</AccessibleButton>;
}
let spinner;

View file

@ -114,7 +114,7 @@ export default class Autocomplete extends React.Component {
processQuery(query, selection) {
return this.autocompleter.getCompletions(
query, selection, this.state.forceComplete
query, selection, this.state.forceComplete,
).then((completions) => {
// Only ever process the completions for the most recent query being processed
if (query !== this.queryRequested) {

View file

@ -277,7 +277,11 @@ module.exports = withMatrixClient(React.createClass({
return false;
}
for (let j = 0; j < rA.length; j++) {
if (rA[j].roomMember.userId !== rB[j].roomMember.userId) {
if (rA[j].userId !== rB[j].userId) {
return false;
}
// one has a member set and the other doesn't?
if (rA[j].roomMember !== rB[j].roomMember) {
return false;
}
}
@ -359,7 +363,7 @@ module.exports = withMatrixClient(React.createClass({
// else set it proportional to index
left = (hidden ? MAX_READ_AVATARS - 1 : i) * -receiptOffset;
const userId = receipt.roomMember.userId;
const userId = receipt.userId;
let readReceiptInfo;
if (this.props.readReceiptMap) {
@ -373,6 +377,7 @@ module.exports = withMatrixClient(React.createClass({
// add to the start so the most recent is on the end (ie. ends up rightmost)
avatars.unshift(
<ReadReceiptMarker key={userId} member={receipt.roomMember}
fallbackUserId={userId}
leftOffset={left} hidden={hidden}
readReceiptInfo={readReceiptInfo}
checkUnmounting={this.props.checkUnmounting}

View file

@ -777,7 +777,7 @@ module.exports = withMatrixClient(React.createClass({
const myMembership = room.getMyMembership();
// not a DM room if we have are not joined
if (myMembership !== 'join') continue;
const them = this.props.member;
// not a DM room if they are not joined
if (!them.membership || them.membership !== 'join') continue;
@ -935,7 +935,7 @@ module.exports = withMatrixClient(React.createClass({
<div className="mx_MemberInfo">
<GeminiScrollbarWrapper autoshow={true}>
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}>
<img src="img/cancel.svg" width="18" height="18" className="mx_filterFlipColor" />
<img src="img/cancel.svg" width="18" height="18" className="mx_filterFlipColor" alt={_t('Close')} />
</AccessibleButton>
<div className="mx_MemberInfo_avatar">
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />

View file

@ -292,21 +292,22 @@ export default class MessageComposer extends React.Component {
let videoCallButton;
let hangupButton;
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
// Call buttons
if (this.props.callState && this.props.callState !== 'ended') {
hangupButton =
<div key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>
<AccessibleButton key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>
<img src="img/hangup.svg" alt={_t('Hangup')} title={_t('Hangup')} width="25" height="26" />
</div>;
</AccessibleButton>;
} else {
callButton =
<div key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title={_t('Voice call')}>
<AccessibleButton key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title={_t('Voice call')}>
<TintableSvg src="img/icon-call.svg" width="35" height="35" />
</div>;
</AccessibleButton>;
videoCallButton =
<div key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title={_t('Video call')}>
<AccessibleButton key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title={_t('Video call')}>
<TintableSvg src="img/icons-video.svg" width="35" height="35" />
</div>;
</AccessibleButton>;
}
const canSendMessages = !this.state.tombstone &&
@ -317,18 +318,19 @@ export default class MessageComposer extends React.Component {
// check separately for whether we can call, but this is slightly
// complex because of conference calls.
const uploadButton = (
<div key="controls_upload" className="mx_MessageComposer_upload"
<AccessibleButton key="controls_upload" className="mx_MessageComposer_upload"
onClick={this.onUploadClick} title={_t('Upload file')}>
<TintableSvg src="img/icons-upload.svg" width="35" height="35" />
<input ref="uploadInput" type="file"
style={uploadInputStyle}
multiple
onChange={this.onUploadFileSelected} />
</div>
</AccessibleButton>
);
const formattingButton = this.state.inputState.isRichTextEnabled ? (
<img className="mx_MessageComposer_formatting"
<AccessibleButton element="img" className="mx_MessageComposer_formatting"
alt={_t("Show Text Formatting Toolbar")}
title={_t("Show Text Formatting Toolbar")}
src="img/button-text-formatting.svg"
onClick={this.onToggleFormattingClicked}
@ -372,7 +374,6 @@ export default class MessageComposer extends React.Component {
} else if (this.state.tombstone) {
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
controls.push(<div className="mx_MessageComposer_replaced_wrapper">
<div className="mx_MessageComposer_replaced_valign">
<img className="mx_MessageComposer_roomReplaced_icon" src="img/room_replaced.svg" />
@ -423,7 +424,7 @@ export default class MessageComposer extends React.Component {
onMouseDown={this.onToggleMarkdownClicked}
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
src={`img/button-md-${!this.state.inputState.isRichTextEnabled}.png`} />
<img title={_t("Hide Text Formatting Toolbar")}
<AccessibleButton element="img" title={_t("Hide Text Formatting Toolbar")}
onClick={this.onToggleFormattingClicked}
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
src="img/icon-text-cancel.svg" />

View file

@ -106,6 +106,17 @@ const MARK_TAGS = {
s: 'deleted', // deprecated
};
const SLATE_SCHEMA = {
inlines: {
pill: {
isVoid: true,
},
emoji: {
isVoid: true,
},
},
};
function onSendMessageFailed(err, room) {
// XXX: temporary logging to try to diagnose
// https://github.com/vector-im/riot-web/issues/3148
@ -116,10 +127,10 @@ function onSendMessageFailed(err, room) {
}
function rangeEquals(a: Range, b: Range): boolean {
return (a.anchorKey === b.anchorKey
&& a.anchorOffset === b.anchorOffset
&& a.focusKey === b.focusKey
&& a.focusOffset === b.focusOffset
return (a.anchor.key === b.anchor.key
&& a.anchor.offset === b.anchorOffset
&& a.focus.key === b.focusKey
&& a.focus.offset === b.focusOffset
&& a.isFocused === b.isFocused
&& a.isBackward === b.isBackward);
}
@ -213,7 +224,7 @@ export default class MessageComposerInput extends React.Component {
object: 'block',
type: type,
nodes: next(el.childNodes),
}
};
}
type = MARK_TAGS[tag];
if (type) {
@ -221,7 +232,7 @@ export default class MessageComposerInput extends React.Component {
object: 'mark',
type: type,
nodes: next(el.childNodes),
}
};
}
// special case links
if (tag === 'a') {
@ -239,16 +250,14 @@ export default class MessageComposerInput extends React.Component {
completion: el.innerText,
completionId: m[1],
},
isVoid: true,
}
}
else {
};
} else {
return {
object: 'inline',
type: 'link',
data: { href },
nodes: next(el.childNodes),
}
};
}
}
},
@ -258,14 +267,12 @@ export default class MessageComposerInput extends React.Component {
node: obj,
children: children,
});
}
else if (obj.object === 'mark') {
} else if (obj.object === 'mark') {
return this.renderMark({
mark: obj,
children: children,
});
}
else if (obj.object === 'inline') {
} else if (obj.object === 'inline') {
// special case links, pills and emoji otherwise we
// end up with React components getting rendered out(!)
switch (obj.type) {
@ -285,9 +292,9 @@ export default class MessageComposerInput extends React.Component {
children: children,
});
}
}
}
]
},
},
],
});
const savedState = MessageComposerStore.getEditorState(this.props.room.roomId);
@ -345,9 +352,13 @@ export default class MessageComposerInput extends React.Component {
dis.unregister(this.dispatcherRef);
}
_collectEditor = (e) => {
this._editor = e;
}
onAction = (payload) => {
const editor = this.refs.editor;
let editorState = this.state.editorState;
const editor = this._editor;
const editorState = this.state.editorState;
switch (payload.action) {
case 'reply_to_event':
@ -361,7 +372,7 @@ export default class MessageComposerInput extends React.Component {
const selection = this.getSelectionRange(this.state.editorState);
const member = this.props.room.getMember(payload.user_id);
const completion = member ?
member.rawDisplayName.replace(' (IRC)', '') : payload.user_id;
member.rawDisplayName : payload.user_id;
this.setDisplayedCompletion({
completion,
completionId: payload.user_id,
@ -402,20 +413,24 @@ export default class MessageComposerInput extends React.Component {
}
// XXX: this is to bring back the focus in a sane place and add a paragraph after it
change = change.select({
anchorKey: quote.key,
focusKey: quote.key,
}).collapseToEndOfBlock().insertBlock(Block.create(DEFAULT_NODE)).focus();
change = change.select(Range.create({
anchor: {
key: quote.key,
},
focus: {
key: quote.key,
},
})).moveToEndOfBlock().insertBlock(Block.create(DEFAULT_NODE)).focus();
this.onChange(change);
} else {
let fragmentChange = fragment.change();
fragmentChange.moveToRangeOf(fragment.document)
const fragmentChange = fragment.change();
fragmentChange.moveToRangeOfNode(fragment.document)
.wrapBlock(quote);
// FIXME: handle pills and use commonmark rather than md-serialize
const md = this.md.serialize(fragmentChange.value);
let change = editorState.change()
const change = editorState.change()
.insertText(md + '\n\n')
.focus();
this.onChange(change);
@ -497,15 +512,15 @@ export default class MessageComposerInput extends React.Component {
if (this.direction !== '') {
const focusedNode = editorState.focusInline || editorState.focusText;
if (focusedNode.isVoid) {
if (editorState.schema.isVoid(focusedNode)) {
// XXX: does this work in RTL?
const edge = this.direction === 'Previous' ? 'End' : 'Start';
if (editorState.isCollapsed) {
change = change[`collapseTo${ edge }Of${ this.direction }Text`]();
if (editorState.selection.isCollapsed) {
change = change[`moveTo${ edge }Of${ this.direction }Text`]();
} else {
const block = this.direction === 'Previous' ? editorState.previousText : editorState.nextText;
if (block) {
change = change[`moveFocusTo${ edge }Of`](block);
change = change[`moveFocusTo${ edge }OfNode`](block);
}
}
editorState = change.value;
@ -517,12 +532,11 @@ export default class MessageComposerInput extends React.Component {
if (this.autocomplete.state.completionList.length > 0 && !this.autocomplete.state.hide &&
!rangeEquals(this.state.editorState.selection, editorState.selection) &&
// XXX: the heuristic failed when inlines like pills weren't taken into account. This is inideal
this.state.editorState.document.toJSON() === editorState.document.toJSON())
{
this.state.editorState.document.toJSON() === editorState.document.toJSON()) {
this.autocomplete.hide();
}
if (!editorState.document.isEmpty) {
if (Plain.serialize(editorState) !== '') {
this.onTypingActivity();
} else {
this.onFinishedTyping();
@ -543,10 +557,14 @@ export default class MessageComposerInput extends React.Component {
const unicodeEmoji = shortnameToUnicode(EMOJI_UNICODE_TO_SHORTNAME[emojiUc]);
const range = Range.create({
anchorKey: editorState.selection.startKey,
anchorOffset: currentStartOffset - emojiMatch[1].length - 1,
focusKey: editorState.selection.startKey,
focusOffset: currentStartOffset - 1,
anchor: {
key: editorState.selection.startKey,
offset: currentStartOffset - emojiMatch[1].length - 1,
},
focus: {
key: editorState.selection.startKey,
offset: currentStartOffset - 1,
},
});
change = change.insertTextAtRange(range, unicodeEmoji);
editorState = change.value;
@ -560,15 +578,18 @@ export default class MessageComposerInput extends React.Component {
let match;
while ((match = EMOJI_REGEX.exec(node.text)) !== null) {
const range = Range.create({
anchorKey: node.key,
anchorOffset: match.index,
focusKey: node.key,
focusOffset: match.index + match[0].length,
anchor: {
key: node.key,
offset: match.index,
},
focus: {
key: node.key,
offset: match.index + match[0].length,
},
});
const inline = Inline.create({
type: 'emoji',
data: { emojiUnicode: match[0] },
isVoid: true,
});
change = change.insertInlineAtRange(range, inline);
editorState = change.value;
@ -580,10 +601,9 @@ export default class MessageComposerInput extends React.Component {
// emoji picker can leave the selection stuck in the emoji's
// child text. This seems to happen due to selection getting
// moved in the normalisation phase after calculating these changes
if (editorState.anchorKey &&
editorState.document.getParent(editorState.anchorKey).type === 'emoji')
{
change = change.collapseToStartOfNextText();
if (editorState.selection.anchor.key &&
editorState.document.getParent(editorState.selection.anchor.key).type === 'emoji') {
change = change.moveToStartOfNextText();
editorState = change.value;
}
@ -595,15 +615,14 @@ export default class MessageComposerInput extends React.Component {
const parent = editorState.document.getParent(editorState.blocks.first().key);
if (parent.type === 'numbered-list') {
blockType = 'numbered-list';
}
else if (parent.type === 'bulleted-list') {
} else if (parent.type === 'bulleted-list') {
blockType = 'bulleted-list';
}
}
const inputState = {
marks: editorState.activeMarks,
isRichTextEnabled: this.state.isRichTextEnabled,
blockType
blockType,
};
this.props.onInputStateChanged(inputState);
}
@ -613,7 +632,7 @@ export default class MessageComposerInput extends React.Component {
this.setState({
editorState,
originalEditorState: originalEditorState || null
originalEditorState: originalEditorState || null,
});
};
@ -653,7 +672,7 @@ export default class MessageComposerInput extends React.Component {
// which doesn't roundtrip symmetrically with commonmark, which we use for
// compiling MD out of the MD editor state above.
this.md.serialize(editorState),
{ defaultBlock: DEFAULT_NODE }
{ defaultBlock: DEFAULT_NODE },
);
}
@ -673,11 +692,11 @@ export default class MessageComposerInput extends React.Component {
editorState: this.createEditorState(enabled, editorState),
isRichTextEnabled: enabled,
}, ()=>{
this.refs.editor.focus();
this._editor.focus();
});
SettingsStore.setValue("MessageComposerInput.isRichTextEnabled", null, SettingLevel.ACCOUNT, enabled);
};
}
/**
* Check if the current selection has a mark with `type` in it.
@ -687,8 +706,8 @@ export default class MessageComposerInput extends React.Component {
*/
hasMark = type => {
const { editorState } = this.state
return editorState.activeMarks.some(mark => mark.type === type)
const { editorState } = this.state;
return editorState.activeMarks.some(mark => mark.type === type);
};
/**
@ -699,20 +718,18 @@ export default class MessageComposerInput extends React.Component {
*/
hasBlock = type => {
const { editorState } = this.state
return editorState.blocks.some(node => node.type === type)
const { editorState } = this.state;
return editorState.blocks.some(node => node.type === type);
};
onKeyDown = (ev: KeyboardEvent, change: Change, editor: Editor) => {
this.suppressAutoComplete = false;
// skip void nodes - see
// https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095
if (ev.keyCode === KeyCode.LEFT) {
this.direction = 'Previous';
}
else if (ev.keyCode === KeyCode.RIGHT) {
} else if (ev.keyCode === KeyCode.RIGHT) {
this.direction = 'Next';
} else {
this.direction = '';
@ -760,7 +777,9 @@ export default class MessageComposerInput extends React.Component {
// drop a point in history so the user can undo a word
// XXX: this seems nasty but adding to history manually seems a no-go
ev.preventDefault();
return change.setOperationFlag("skip", false).setOperationFlag("merge", false).insertText(ev.key);
return change.withoutMerging(() => {
change.insertText(ev.key);
});
};
onBackspace = (ev: KeyboardEvent, change: Change): Change => {
@ -771,23 +790,24 @@ export default class MessageComposerInput extends React.Component {
const { editorState } = this.state;
// Allow Ctrl/Cmd-Backspace when focus starts at the start of the composer (e.g select-all)
// for some reason if slate sees you Ctrl-backspace and your anchorOffset=0 it just resets your focus
if (!editorState.isCollapsed && editorState.anchorOffset === 0) {
// for some reason if slate sees you Ctrl-backspace and your anchor.offset=0 it just resets your focus
// XXX: Doing this now seems to put slate into a broken state, and it didn't appear to be doing
// what it claims to do on the old version of slate anyway...
/*if (!editorState.isCollapsed && editorState.selection.anchor.offset === 0) {
return change.delete();
}
}*/
if (this.state.isRichTextEnabled) {
// let backspace exit lists
const isList = this.hasBlock('list-item');
if (isList && editorState.anchorOffset == 0) {
if (isList && editorState.selection.anchor.offset == 0) {
change
.setBlocks(DEFAULT_NODE)
.unwrapBlock('bulleted-list')
.unwrapBlock('numbered-list');
return change;
}
else if (editorState.anchorOffset == 0 && editorState.isCollapsed) {
} else if (editorState.selection.anchor.offset == 0 && editorState.isCollapsed) {
// turn blocks back into paragraphs
if ((this.hasBlock('block-quote') ||
this.hasBlock('heading1') ||
@ -796,20 +816,18 @@ export default class MessageComposerInput extends React.Component {
this.hasBlock('heading4') ||
this.hasBlock('heading5') ||
this.hasBlock('heading6') ||
this.hasBlock('code')))
{
this.hasBlock('code'))) {
return change.setBlocks(DEFAULT_NODE);
}
// remove paragraphs entirely if they're nested
const parent = editorState.document.getParent(editorState.anchorBlock.key);
if (editorState.anchorOffset == 0 &&
if (editorState.selection.anchor.offset == 0 &&
this.hasBlock('paragraph') &&
parent.nodes.size == 1 &&
parent.object !== 'document')
{
parent.object !== 'document') {
return change.replaceNodeByKey(editorState.anchorBlock.key, editorState.anchorText)
.collapseToEndOf(parent)
.moveToEndOfNode(parent)
.focus();
}
}
@ -823,7 +841,7 @@ export default class MessageComposerInput extends React.Component {
return true;
}
let newState: ?Value = null;
const newState: ?Value = null;
// Draft handles rich text mode commands by default but we need to do it ourselves for Markdown.
if (this.state.isRichTextEnabled) {
@ -849,7 +867,7 @@ export default class MessageComposerInput extends React.Component {
} else if (isList) {
change
.unwrapBlock(
type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list'
type === 'bulleted-list' ? 'numbered-list' : 'bulleted-list',
)
.wrapBlock(type);
} else {
@ -942,8 +960,8 @@ export default class MessageComposerInput extends React.Component {
const collapseAndOffsetSelection = (selection, offset) => {
const key = selection.endKey();
return new Range({
anchorKey: key, anchorOffset: offset,
focusKey: key, focusOffset: offset,
anchorKey: key, anchor.offset: offset,
focus.key: key, focus.offset: offset,
});
};
@ -1000,18 +1018,16 @@ export default class MessageComposerInput extends React.Component {
.insertFragment(fragment.document);
} else {
// in MD mode we don't want the rich content pasted as the magic was annoying people so paste plain
return change
.setOperationFlag("skip", false)
.setOperationFlag("merge", false)
.insertText(transfer.text);
return change.withoutMerging(() => {
change.insertText(transfer.text);
});
}
}
case 'text':
// don't skip/merge so that multiple consecutive pastes can be undone individually
return change
.setOperationFlag("skip", false)
.setOperationFlag("merge", false)
.insertText(transfer.text);
return change.withoutMerging(() => {
change.insertText(transfer.text);
});
}
};
@ -1054,8 +1070,7 @@ export default class MessageComposerInput extends React.Component {
const firstGrandChild = firstChild && firstChild.nodes.get(0);
if (firstChild && firstGrandChild &&
firstChild.object === 'block' && firstGrandChild.object === 'text' &&
firstGrandChild.text[0] === '/')
{
firstGrandChild.text[0] === '/') {
commandText = this.plainWithIdPills.serialize(editorState);
cmd = processCommandInput(this.props.room.roomId, commandText);
}
@ -1066,7 +1081,7 @@ export default class MessageComposerInput extends React.Component {
this.setState({
editorState: this.createEditorState(),
}, ()=>{
this.refs.editor.focus();
this._editor.focus();
});
}
if (cmd.promise) {
@ -1196,7 +1211,7 @@ export default class MessageComposerInput extends React.Component {
this.setState({
editorState: this.createEditorState(),
}, ()=>{ this.refs.editor.focus() });
}, ()=>{ this._editor.focus(); });
return true;
};
@ -1216,9 +1231,9 @@ export default class MessageComposerInput extends React.Component {
// and we must be at the edge of the document (up=start, down=end)
if (up) {
if (!selection.isAtStartOf(document)) return;
if (!selection.anchor.isAtStartOfNode(document)) return;
} else {
if (!selection.isAtEndOf(document)) return;
if (!selection.anchor.isAtEndOfNode(document)) return;
}
const selected = this.selectHistory(up);
@ -1266,7 +1281,7 @@ export default class MessageComposerInput extends React.Component {
}
// Move selection to the end of the selected history
const change = editorState.change().collapseToEndOf(editorState.document);
const change = editorState.change().moveToEndOfNode(editorState.document);
// We don't call this.onChange(change) now, as fixups on stuff like emoji
// should already have been done and persisted in the history.
@ -1275,7 +1290,7 @@ export default class MessageComposerInput extends React.Component {
this.suppressAutoComplete = true;
this.setState({ editorState }, ()=>{
this.refs.editor.focus();
this._editor.focus();
});
return true;
};
@ -1326,7 +1341,7 @@ export default class MessageComposerInput extends React.Component {
if (displayedCompletion == null) {
if (this.state.originalEditorState) {
let editorState = this.state.originalEditorState;
const editorState = this.state.originalEditorState;
this.setState({editorState});
}
return false;
@ -1337,7 +1352,7 @@ export default class MessageComposerInput extends React.Component {
completion = '',
completionId = '',
href = null,
suffix = ''
suffix = '',
} = displayedCompletion;
let inline;
@ -1345,15 +1360,11 @@ export default class MessageComposerInput extends React.Component {
inline = Inline.create({
type: 'pill',
data: { completion, completionId, href },
// we can't put text in here otherwise the editor tries to select it
isVoid: true,
});
} else if (completion === '@room') {
inline = Inline.create({
type: 'pill',
data: { completion, completionId },
// we can't put text in here otherwise the editor tries to select it
isVoid: true,
});
}
@ -1361,8 +1372,9 @@ export default class MessageComposerInput extends React.Component {
if (range) {
const change = editorState.change()
.collapseToAnchor()
.moveOffsetsTo(range.start, range.end)
.moveToAnchor()
.moveAnchorTo(range.start)
.moveFocusTo(range.end)
.focus();
editorState = change.value;
}
@ -1373,8 +1385,7 @@ export default class MessageComposerInput extends React.Component {
.insertInlineAtRange(editorState.selection, inline)
.insertText(suffix)
.focus();
}
else {
} else {
change = editorState.change()
.insertTextAtRange(editorState.selection, completion)
.insertText(suffix)
@ -1433,17 +1444,17 @@ export default class MessageComposerInput extends React.Component {
room={this.props.room}
shouldShowPillAvatar={shouldShowPillAvatar}
isSelected={isSelected}
{...attributes}
/>;
}
else if (Pill.isPillUrl(url)) {
} else if (Pill.isPillUrl(url)) {
return <Pill
url={url}
room={this.props.room}
shouldShowPillAvatar={shouldShowPillAvatar}
isSelected={isSelected}
{...attributes}
/>;
}
else {
} else {
const { text } = node;
return <a href={url} {...props.attributes}>
{ text }
@ -1456,9 +1467,11 @@ export default class MessageComposerInput extends React.Component {
const uri = RichText.unicodeToEmojiUri(emojiUnicode);
const shortname = toShort(emojiUnicode);
const className = classNames('mx_emojione', {
mx_emojione_selected: isSelected
mx_emojione_selected: isSelected,
});
return <img className={ className } src={ uri } title={ shortname } alt={ emojiUnicode }/>;
const style = {};
if (props.selected) style.border = '1px solid blue';
return <img className={ className } src={ uri } title={ shortname } alt={ emojiUnicode } style={style} />;
}
}
};
@ -1486,7 +1499,7 @@ export default class MessageComposerInput extends React.Component {
// of focusing it doesn't then cancel the format button being pressed
// FIXME: can we just tell handleKeyCommand's change to invoke .focus()?
if (document.activeElement && document.activeElement.className !== 'mx_MessageComposer_editor') {
this.refs.editor.focus();
this._editor.focus();
setTimeout(()=>{
this.handleKeyCommand(name);
}, 500); // can't find any callback to hook this to. onFocus and onChange and willComponentUpdate fire too early.
@ -1503,10 +1516,9 @@ export default class MessageComposerInput extends React.Component {
// This avoids us having to serialize the whole thing to plaintext and convert
// selection offsets in & out of the plaintext domain.
if (editorState.selection.anchorKey) {
return editorState.document.getDescendant(editorState.selection.anchorKey).text;
}
else {
if (editorState.selection.anchor.key) {
return editorState.document.getDescendant(editorState.selection.anchor.key).text;
} else {
return '';
}
}
@ -1518,17 +1530,17 @@ export default class MessageComposerInput extends React.Component {
const firstGrandChild = firstChild && firstChild.nodes.get(0);
beginning = (firstChild && firstGrandChild &&
firstChild.object === 'block' && firstGrandChild.object === 'text' &&
editorState.selection.anchorKey === firstGrandChild.key);
editorState.selection.anchor.key === firstGrandChild.key);
// return a character range suitable for handing to an autocomplete provider.
// the range is relative to the anchor of the current editor selection.
// if the selection spans multiple blocks, then we collapse it for the calculation.
const range = {
beginning, // whether the selection is in the first block of the editor or not
start: editorState.selection.anchorOffset,
end: (editorState.selection.anchorKey == editorState.selection.focusKey) ?
editorState.selection.focusOffset : editorState.selection.anchorOffset,
}
start: editorState.selection.anchor.offset,
end: (editorState.selection.anchor.key == editorState.selection.focus.key) ?
editorState.selection.focus.offset : editorState.selection.anchor.offset,
};
if (range.start > range.end) {
const tmp = range.start;
range.start = range.end;
@ -1543,7 +1555,7 @@ export default class MessageComposerInput extends React.Component {
};
focusComposer = () => {
this.refs.editor.focus();
this._editor.focus();
};
render() {
@ -1553,7 +1565,7 @@ export default class MessageComposerInput extends React.Component {
mx_MessageComposer_input_error: this.state.someCompletions === false,
});
const isEmpty = this.state.editorState.document.isEmpty;
const isEmpty = Plain.serialize(this.state.editorState) === '';
let {placeholder} = this.props;
// XXX: workaround for placeholder being shown when there is a formatting block e.g blockquote but no text
@ -1579,7 +1591,7 @@ export default class MessageComposerInput extends React.Component {
onMouseDown={this.onMarkdownToggleClicked}
title={this.state.isRichTextEnabled ? _t("Markdown is disabled") : _t("Markdown is enabled")}
src={`img/button-md-${!this.state.isRichTextEnabled}.png`} />
<Editor ref="editor"
<Editor ref={this._collectEditor}
dir="auto"
className="mx_MessageComposer_editor"
placeholder={placeholder}
@ -1591,6 +1603,7 @@ export default class MessageComposerInput extends React.Component {
renderMark={this.renderMark}
// disable spell check for the placeholder because browsers don't like "unencrypted"
spellCheck={!isEmpty}
schema={SLATE_SCHEMA}
/>
</div>
</div>

View file

@ -41,7 +41,10 @@ module.exports = React.createClass({
propTypes: {
// the RoomMember to show the RR for
member: PropTypes.object.isRequired,
member: PropTypes.object,
// userId to fallback the avatar to
// if the member hasn't been loaded yet
fallbackUserId: PropTypes.string.isRequired,
// number of pixels to offset the avatar from the right of its parent;
// typically a negative value.
@ -130,8 +133,7 @@ module.exports = React.createClass({
// the docs for `offsetParent` say it may be null if `display` is
// `none`, but I can't see why that would happen.
console.warn(
`ReadReceiptMarker for ${this.props.member.userId} in ` +
`${this.props.member.roomId} has no offsetParent`,
`ReadReceiptMarker for ${this.props.fallbackUserId} in has no offsetParent`,
);
startTopOffset = 0;
} else {
@ -186,17 +188,17 @@ module.exports = React.createClass({
let title;
if (this.props.timestamp) {
const dateString = formatDate(new Date(this.props.timestamp), this.props.showTwelveHour);
if (this.props.member.userId === this.props.member.rawDisplayName) {
if (!this.props.member || this.props.fallbackUserId === this.props.member.rawDisplayName) {
title = _t(
"Seen by %(userName)s at %(dateTime)s",
{userName: this.props.member.userId,
{userName: this.props.fallbackUserId,
dateTime: dateString},
);
} else {
title = _t(
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
{displayName: this.props.member.rawDisplayName,
userName: this.props.member.userId,
userName: this.props.fallbackUserId,
dateTime: dateString},
);
}
@ -208,6 +210,7 @@ module.exports = React.createClass({
enterTransitionOpts={this.state.enterTransitionOpts} >
<MemberAvatar
member={this.props.member}
fallbackUserId={this.props.fallbackUserId}
aria-hidden="true"
width={14} height={14} resizeMethod="crop"
style={style}

View file

@ -16,7 +16,7 @@ limitations under the License.
'use strict';
var React = require('react');
const React = require('react');
module.exports = React.createClass({
displayName: 'RoomDropTarget',
@ -31,5 +31,5 @@ module.exports = React.createClass({
</div>
</div>
);
}
},
});

View file

@ -16,11 +16,11 @@ limitations under the License.
'use strict';
var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var classNames = require('classnames');
var AccessibleButton = require('../../../components/views/elements/AccessibleButton');
const React = require('react');
const MatrixClientPeg = require('../../../MatrixClientPeg');
const sdk = require('../../../index');
const classNames = require('classnames');
const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
@ -28,7 +28,7 @@ module.exports = React.createClass({
getInitialState: function() {
return ({
scope: 'Room'
scope: 'Room',
});
},
@ -54,18 +54,18 @@ module.exports = React.createClass({
},
render: function() {
var searchButtonClasses = classNames({ mx_SearchBar_searchButton : true, mx_SearchBar_searching: this.props.searchInProgress });
var thisRoomClasses = classNames({ mx_SearchBar_button : true, mx_SearchBar_unselected : this.state.scope !== 'Room' });
var allRoomsClasses = classNames({ mx_SearchBar_button : true, mx_SearchBar_unselected : this.state.scope !== 'All' });
const searchButtonClasses = classNames({ mx_SearchBar_searchButton: true, mx_SearchBar_searching: this.props.searchInProgress });
const thisRoomClasses = classNames({ mx_SearchBar_button: true, mx_SearchBar_unselected: this.state.scope !== 'Room' });
const allRoomsClasses = classNames({ mx_SearchBar_button: true, mx_SearchBar_unselected: this.state.scope !== 'All' });
return (
<div className="mx_SearchBar">
<input ref="search_term" className="mx_SearchBar_input" type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange}/>
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch}><img src="img/search-button.svg" width="37" height="37" alt={_t("Search")}/></AccessibleButton>
<div className="mx_SearchBar">
<input ref="search_term" className="mx_SearchBar_input" type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch}><img src="img/search-button.svg" width="37" height="37" alt={_t("Search")} /></AccessibleButton>
<AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick}>{_t("This Room")}</AccessibleButton>
<AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick}>{_t("All Rooms")}</AccessibleButton>
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" /></AccessibleButton>
</div>
);
}
},
});

View file

@ -151,8 +151,8 @@ export default class Stickerpicker extends React.Component {
<AccessibleButton onClick={this._launchManageIntegrations}
className='mx_Stickers_contentPlaceholder'>
<p>{ _t("You don't currently have any stickerpacks enabled") }</p>
<p className='mx_Stickers_addLink'>Add some now</p>
<img src='img/stickerpack-placeholder.png' alt={_t('Add a stickerpack')} />
<p className='mx_Stickers_addLink'>{ _t("Add some now") }</p>
<img src='img/stickerpack-placeholder.png' alt="" />
</AccessibleButton>
);
}
@ -344,7 +344,7 @@ export default class Stickerpicker extends React.Component {
if (this.state.showStickers) {
// Show hide-stickers button
stickersButton =
<div
<AccessibleButton
id='stickersButton'
key="controls_hide_stickers"
className="mx_MessageComposer_stickers mx_Stickers_hideStickers"
@ -352,18 +352,18 @@ export default class Stickerpicker extends React.Component {
ref='target'
title={_t("Hide Stickers")}>
<TintableSvg src="img/icons-hide-stickers.svg" width="35" height="35" />
</div>;
</AccessibleButton>;
} else {
// Show show-stickers button
stickersButton =
<div
<AccessibleButton
id='stickersButton'
key="constrols_show_stickers"
key="controls_show_stickers"
className="mx_MessageComposer_stickers"
onClick={this._onShowStickersClick}
title={_t("Show Stickers")}>
<TintableSvg src="img/icons-show-stickers.svg" width="35" height="35" />
</div>;
</AccessibleButton>;
}
return <div>
{stickersButton}

View file

@ -164,6 +164,7 @@ export default class DevicesPanel extends React.Component {
render() {
const Spinner = sdk.getComponent("elements.Spinner");
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
if (this.state.deviceLoadError !== undefined) {
const classes = classNames(this.props.className, "error");
@ -185,9 +186,9 @@ export default class DevicesPanel extends React.Component {
const deleteButton = this.state.deleting ?
<Spinner w={22} h={22} /> :
<div className="mx_textButton" onClick={this._onDeleteClick}>
<AccessibleButton className="mx_textButton" onClick={this._onDeleteClick}>
{ _t("Delete %(count)s devices", {count: this.state.selectedDevices.length}) }
</div>;
</AccessibleButton>;
const classes = classNames(this.props.className, "mx_DevicesPanel");
return (

View file

@ -16,10 +16,10 @@ limitations under the License.
'use strict';
var React = require('react');
var sdk = require('../../../index');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var dis = require('../../../dispatcher');
const React = require('react');
const sdk = require('../../../index');
const MatrixClientPeg = require('../../../MatrixClientPeg');
const dis = require('../../../dispatcher');
module.exports = React.createClass({
displayName: 'IntegrationsManager',
@ -59,5 +59,5 @@ module.exports = React.createClass({
return (
<iframe src={ this.props.src }></iframe>
);
}
},
});

View file

@ -26,7 +26,7 @@ import {
NotificationUtils,
VectorPushRulesDefinitions,
PushRuleVectorState,
ContentRules
ContentRules,
} from '../../../notifications';
// TODO: this "view" component still has far too much application logic in it,
@ -47,7 +47,7 @@ const LEGACY_RULES = {
"im.vector.rule.room_message": ".m.rule.message",
"im.vector.rule.invite_for_me": ".m.rule.invite_for_me",
"im.vector.rule.call": ".m.rule.call",
"im.vector.rule.notices": ".m.rule.suppress_notices"
"im.vector.rule.notices": ".m.rule.suppress_notices",
};
function portLegacyActions(actions) {
@ -67,7 +67,7 @@ module.exports = React.createClass({
phases: {
LOADING: "LOADING", // The component is loading or sending data to the hs
DISPLAY: "DISPLAY", // The component is ready and display data
ERROR: "ERROR" // There was an error
ERROR: "ERROR", // There was an error
},
propTypes: {
@ -79,7 +79,7 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
threepids: []
threepids: [],
};
},
@ -90,10 +90,10 @@ module.exports = React.createClass({
vectorPushRules: [], // HS default push rules displayed in Vector UI
vectorContentRules: { // Keyword push rules displayed in Vector UI
vectorState: PushRuleVectorState.ON,
rules: []
rules: [],
},
externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
externalContentRules: [] // Keyword push rules that have been defined outside Vector UI
externalContentRules: [], // Keyword push rules that have been defined outside Vector UI
};
},
@ -104,7 +104,7 @@ module.exports = React.createClass({
onEnableNotificationsChange: function(event) {
const self = this;
this.setState({
phase: this.phases.LOADING
phase: this.phases.LOADING,
});
MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !event.target.checked).done(function() {
@ -145,7 +145,7 @@ module.exports = React.createClass({
onEnableEmailNotificationsChange: function(address, event) {
let emailPusherPromise;
if (event.target.checked) {
const data = {}
const data = {};
data['brand'] = this.props.brand || 'Riot';
emailPusherPromise = UserSettingsStore.addEmailPusher(address, data);
} else {
@ -170,9 +170,8 @@ module.exports = React.createClass({
const newPushRuleVectorState = event.target.className.split("-")[1];
if ("_keywords" === vectorRuleId) {
this._setKeywordsPushRuleVectorState(newPushRuleVectorState)
}
else {
this._setKeywordsPushRuleVectorState(newPushRuleVectorState);
} else {
const rule = this.getRule(vectorRuleId);
if (rule) {
this._setPushRuleVectorState(rule, newPushRuleVectorState);
@ -185,7 +184,7 @@ module.exports = React.createClass({
// Compute the keywords list to display
let keywords = [];
for (let i in this.state.vectorContentRules.rules) {
for (const i in this.state.vectorContentRules.rules) {
const rule = this.state.vectorContentRules.rules[i];
keywords.push(rule.pattern);
}
@ -195,8 +194,7 @@ module.exports = React.createClass({
keywords.sort();
keywords = keywords.join(", ");
}
else {
} else {
keywords = "";
}
@ -207,29 +205,28 @@ module.exports = React.createClass({
button: _t('OK'),
value: keywords,
onFinished: function onFinished(should_leave, newValue) {
if (should_leave && newValue !== keywords) {
let newKeywords = newValue.split(',');
for (let i in newKeywords) {
for (const i in newKeywords) {
newKeywords[i] = newKeywords[i].trim();
}
// Remove duplicates and empty
newKeywords = newKeywords.reduce(function(array, keyword){
newKeywords = newKeywords.reduce(function(array, keyword) {
if (keyword !== "" && array.indexOf(keyword) < 0) {
array.push(keyword);
}
return array;
},[]);
}, []);
self._setKeywords(newKeywords);
}
}
},
});
},
getRule: function(vectorRuleId) {
for (let i in this.state.vectorPushRules) {
for (const i in this.state.vectorPushRules) {
const rule = this.state.vectorPushRules[i];
if (rule.vectorRuleId === vectorRuleId) {
return rule;
@ -239,9 +236,8 @@ module.exports = React.createClass({
_setPushRuleVectorState: function(rule, newPushRuleVectorState) {
if (rule && rule.vectorState !== newPushRuleVectorState) {
this.setState({
phase: this.phases.LOADING
phase: this.phases.LOADING,
});
const self = this;
@ -255,8 +251,7 @@ module.exports = React.createClass({
if (!actions) {
// The new state corresponds to disabling the rule.
deferreds.push(cli.setPushRuleEnabled('global', rule.rule.kind, rule.rule.rule_id, false));
}
else {
} else {
// The new state corresponds to enabling the rule and setting specific actions
deferreds.push(this._updatePushRuleActions(rule.rule, actions, true));
}
@ -270,7 +265,7 @@ module.exports = React.createClass({
Modal.createTrackedDialog('Failed to change settings', '', ErrorDialog, {
title: _t('Failed to change settings'),
description: ((error && error.message) ? error.message : _t('Operation failed')),
onFinished: self._refreshFromServer
onFinished: self._refreshFromServer,
});
});
}
@ -287,12 +282,12 @@ module.exports = React.createClass({
const cli = MatrixClientPeg.get();
this.setState({
phase: this.phases.LOADING
phase: this.phases.LOADING,
});
// Update all rules in self.state.vectorContentRules
const deferreds = [];
for (let i in this.state.vectorContentRules.rules) {
for (const i in this.state.vectorContentRules.rules) {
const rule = this.state.vectorContentRules.rules[i];
let enabled, actions;
@ -326,8 +321,7 @@ module.exports = React.createClass({
// Note that the workaround in _updatePushRuleActions will automatically
// enable the rule
deferreds.push(this._updatePushRuleActions(rule, actions, enabled));
}
else if (enabled != undefined) {
} else if (enabled != undefined) {
deferreds.push(cli.setPushRuleEnabled('global', rule.kind, rule.rule_id, enabled));
}
}
@ -340,14 +334,14 @@ module.exports = React.createClass({
Modal.createTrackedDialog('Can\'t update user notifcation settings', '', ErrorDialog, {
title: _t('Can\'t update user notification settings'),
description: ((error && error.message) ? error.message : _t('Operation failed')),
onFinished: self._refreshFromServer
onFinished: self._refreshFromServer,
});
});
},
_setKeywords: function(newKeywords) {
this.setState({
phase: this.phases.LOADING
phase: this.phases.LOADING,
});
const self = this;
@ -356,7 +350,7 @@ module.exports = React.createClass({
// Remove per-word push rules of keywords that are no more in the list
const vectorContentRulesPatterns = [];
for (let i in self.state.vectorContentRules.rules) {
for (const i in self.state.vectorContentRules.rules) {
const rule = self.state.vectorContentRules.rules[i];
vectorContentRulesPatterns.push(rule.pattern);
@ -368,7 +362,7 @@ module.exports = React.createClass({
// If the keyword is part of `externalContentRules`, remove the rule
// before recreating it in the right Vector path
for (let i in self.state.externalContentRules) {
for (const i in self.state.externalContentRules) {
const rule = self.state.externalContentRules[i];
if (newKeywords.indexOf(rule.pattern) >= 0) {
@ -382,9 +376,9 @@ module.exports = React.createClass({
Modal.createTrackedDialog('Failed to update keywords', '', ErrorDialog, {
title: _t('Failed to update keywords'),
description: ((error && error.message) ? error.message : _t('Operation failed')),
onFinished: self._refreshFromServer
onFinished: self._refreshFromServer,
});
}
};
// Then, add the new ones
Promise.all(removeDeferreds).done(function(resps) {
@ -398,14 +392,13 @@ module.exports = React.createClass({
// Thus, this new rule will join the 'vectorContentRules' set.
if (self.state.vectorContentRules.rules.length) {
pushRuleVectorStateKind = PushRuleVectorState.contentRuleVectorStateKind(self.state.vectorContentRules.rules[0]);
}
else {
} else {
// ON is default
pushRuleVectorStateKind = PushRuleVectorState.ON;
pushRuleVectorStateKind = PushRuleVectorState.ON;
}
}
for (let i in newKeywords) {
for (const i in newKeywords) {
const keyword = newKeywords[i];
if (vectorContentRulesPatterns.indexOf(keyword) < 0) {
@ -413,13 +406,12 @@ module.exports = React.createClass({
deferreds.push(cli.addPushRule
('global', 'content', keyword, {
actions: PushRuleVectorState.actionsFor(pushRuleVectorStateKind),
pattern: keyword
pattern: keyword,
}));
}
else {
} else {
deferreds.push(self._addDisabledPushRule('global', 'content', keyword, {
actions: PushRuleVectorState.actionsFor(pushRuleVectorStateKind),
pattern: keyword
pattern: keyword,
}));
}
}
@ -435,7 +427,7 @@ module.exports = React.createClass({
_addDisabledPushRule: function(scope, kind, ruleId, body) {
const cli = MatrixClientPeg.get();
return cli.addPushRule(scope, kind, ruleId, body).then(() =>
cli.setPushRuleEnabled(scope, kind, ruleId, false)
cli.setPushRuleEnabled(scope, kind, ruleId, false),
);
},
@ -446,7 +438,7 @@ module.exports = React.createClass({
const needsUpdate = [];
const cli = MatrixClientPeg.get();
for (let kind in rulesets.global) {
for (const kind in rulesets.global) {
const ruleset = rulesets.global[kind];
for (let i = 0; i < ruleset.length; ++i) {
const rule = ruleset[i];
@ -454,9 +446,9 @@ module.exports = React.createClass({
console.log("Porting legacy rule", rule);
needsUpdate.push( function(kind, rule) {
return cli.setPushRuleActions(
'global', kind, LEGACY_RULES[rule.rule_id], portLegacyActions(rule.actions)
'global', kind, LEGACY_RULES[rule.rule_id], portLegacyActions(rule.actions),
).then(() =>
cli.deletePushRule('global', kind, rule.rule_id)
cli.deletePushRule('global', kind, rule.rule_id),
).catch( (e) => {
console.warn(`Error when porting legacy rule: ${e}`);
});
@ -469,7 +461,7 @@ module.exports = React.createClass({
// If some of the rules need to be ported then wait for the porting
// to happen and then fetch the rules again.
return Promise.all(needsUpdate).then(() =>
cli.getPushRules()
cli.getPushRules(),
);
} else {
// Otherwise return the rules that we already have.
@ -480,7 +472,6 @@ module.exports = React.createClass({
_refreshFromServer: function() {
const self = this;
const pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
/// XXX seriously? wtf is this?
MatrixClientPeg.get().pushRules = rulesets;
@ -497,7 +488,7 @@ module.exports = React.createClass({
'.m.rule.invite_for_me': 'vector',
//'.m.rule.member_event': 'vector',
'.m.rule.call': 'vector',
'.m.rule.suppress_notices': 'vector'
'.m.rule.suppress_notices': 'vector',
// Others go to others
};
@ -505,7 +496,7 @@ module.exports = React.createClass({
// HS default rules
const defaultRules = {master: [], vector: {}, others: []};
for (let kind in rulesets.global) {
for (const kind in rulesets.global) {
for (let i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
const r = rulesets.global[kind][i];
const cat = rule_categories[r.rule_id];
@ -514,11 +505,9 @@ module.exports = React.createClass({
if (r.rule_id[0] === '.') {
if (cat === 'vector') {
defaultRules.vector[r.rule_id] = r;
}
else if (cat === 'master') {
} else if (cat === 'master') {
defaultRules.master.push(r);
}
else {
} else {
defaultRules['others'].push(r);
}
}
@ -551,9 +540,9 @@ module.exports = React.createClass({
'.m.rule.invite_for_me',
//'im.vector.rule.member_event',
'.m.rule.call',
'.m.rule.suppress_notices'
'.m.rule.suppress_notices',
];
for (let i in vectorRuleIds) {
for (const i in vectorRuleIds) {
const vectorRuleId = vectorRuleIds[i];
if (vectorRuleId === '_keywords') {
@ -562,20 +551,19 @@ module.exports = React.createClass({
// it corresponds to all content push rules (stored in self.state.vectorContentRule)
self.state.vectorPushRules.push({
"vectorRuleId": "_keywords",
"description" : (
"description": (
<span>
{ _t('Messages containing <span>keywords</span>',
{},
{ 'span': (sub) =>
<span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>
<span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>,
},
)}
</span>
),
"vectorState": self.state.vectorContentRules.vectorState
"vectorState": self.state.vectorContentRules.vectorState,
});
}
else {
} else {
const ruleDefinition = VectorPushRulesDefinitions[vectorRuleId];
const rule = defaultRules.vector[vectorRuleId];
@ -585,7 +573,7 @@ module.exports = React.createClass({
self.state.vectorPushRules.push({
"vectorRuleId": vectorRuleId,
"description" : _t(ruleDefinition.description), // Text from VectorPushRulesDefinitions.js
"description": _t(ruleDefinition.description), // Text from VectorPushRulesDefinitions.js
"rule": rule,
"vectorState": vectorState,
});
@ -604,7 +592,7 @@ module.exports = React.createClass({
'.m.rule.fallback': _t('Notify me for anything else'),
};
for (let i in defaultRules.others) {
for (const i in defaultRules.others) {
const rule = defaultRules.others[i];
const ruleDescription = otherRulesDescriptions[rule.rule_id];
@ -622,12 +610,12 @@ module.exports = React.createClass({
Promise.all([pushRulesPromise, pushersPromise]).then(function() {
self.setState({
phase: self.phases.DISPLAY
phase: self.phases.DISPLAY,
});
}, function(error) {
console.error(error);
self.setState({
phase: self.phases.ERROR
phase: self.phases.ERROR,
});
}).finally(() => {
// actually explicitly update our state having been deep-manipulating it
@ -645,12 +633,12 @@ module.exports = React.createClass({
const cli = MatrixClientPeg.get();
return cli.setPushRuleActions(
'global', rule.kind, rule.rule_id, actions
'global', rule.kind, rule.rule_id, actions,
).then( function() {
// Then, if requested, enabled or disabled the rule
if (undefined != enabled) {
return cli.setPushRuleEnabled(
'global', rule.kind, rule.rule_id, enabled
'global', rule.kind, rule.rule_id, enabled,
);
}
});
@ -689,7 +677,7 @@ module.exports = React.createClass({
renderNotifRulesTableRows: function() {
const rows = [];
for (let i in this.state.vectorPushRules) {
for (const i in this.state.vectorPushRules) {
const rule = this.state.vectorPushRules[i];
//console.log("rendering: " + rule.description + ", " + rule.vectorRuleId + ", " + rule.vectorState);
rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState));
@ -769,20 +757,20 @@ module.exports = React.createClass({
// This only supports the first email address in your profile for now
emailNotificationsRow = this.emailNotificationsRow(
emailThreepids[0].address,
`${_t('Enable email notifications')} (${emailThreepids[0].address})`
`${_t('Enable email notifications')} (${emailThreepids[0].address})`,
);
}
// Build external push rules
const externalRules = [];
for (let i in this.state.externalPushRules) {
for (const i in this.state.externalPushRules) {
const rule = this.state.externalPushRules[i];
externalRules.push(<li>{ _t(rule.description) }</li>);
}
// Show keywords not displayed by the vector UI as a single external push rule
let externalKeywords = [];
for (let i in this.state.externalContentRules) {
for (const i in this.state.externalContentRules) {
const rule = this.state.externalContentRules[i];
externalKeywords.push(rule.pattern);
}
@ -793,7 +781,7 @@ module.exports = React.createClass({
let devicesSection;
if (this.state.pushers === undefined) {
devicesSection = <div className="error">{ _t('Unable to fetch notification target list') }</div>
devicesSection = <div className="error">{ _t('Unable to fetch notification target list') }</div>;
} else if (this.state.pushers.length == 0) {
devicesSection = null;
} else {
@ -824,7 +812,7 @@ module.exports = React.createClass({
advancedSettings = (
<div>
<h3>{ _t('Advanced notification settings') }</h3>
{ _t('There are advanced notifications which are not shown here') }.<br/>
{ _t('There are advanced notifications which are not shown here') }.<br />
{ _t('You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply') }.
<ul>
{ externalRules }
@ -915,5 +903,5 @@ module.exports = React.createClass({
</div>
);
}
},
});

View file

@ -125,14 +125,15 @@ module.exports = React.createClass({
render: function() {
const VideoView = sdk.getComponent('voip.VideoView');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let voice;
if (this.state.call && this.state.call.type === "voice" && this.props.showVoice) {
const callRoom = MatrixClientPeg.get().getRoom(this.state.call.roomId);
voice = (
<div className="mx_CallView_voice" onClick={this.props.onClick}>
<AccessibleButton className="mx_CallView_voice" onClick={this.props.onClick}>
{ _t("Active call (%(roomName)s)", {roomName: callRoom.name}) }
</div>
</AccessibleButton>
);
}

View file

@ -65,5 +65,6 @@
"Cancel Sending": "إلغاء الإرسال",
"Collapse panel": "طي الجدول",
"Set Password": "تعيين كلمة سرية",
"Checking for an update...": "البحث عن تحديث …"
"Checking for an update...": "البحث عن تحديث …",
"powered by Matrix": "مشغل بواسطة Matrix"
}

View file

@ -1256,5 +1256,37 @@
"Lazy loading is not supported by your current homeserver.": "Отложеното зареждане не се поддържа от текущия сървър.",
"Sorry, your homeserver is too old to participate in this room.": "Съжаляваме, вашият сървър е прекалено стар за да участва в тази стая.",
"Please contact your homeserver administrator.": "Моля, свържете се се със сървърния администратор.",
"Legal": "Юридически"
"Legal": "Юридически",
"Registration Required": "Нужна е регистрация",
"You need to register to do this. Would you like to register now?": "За да направите това е нужно да се регистрирате. Искате ли да се регистрирате сега?",
"Unable to connect to Homeserver. Retrying...": "Неуспешно свързване със сървъра. Опитване отново...",
"This room has been replaced and is no longer active.": "Тази стая е била заменена и вече не е активна.",
"The conversation continues here.": "Разговора продължава тук.",
"Upgrade room to version %(ver)s": "Обновете стаята до версия %(ver)s",
"This room is a continuation of another conversation.": "Тази стая е продължение на предишен разговор.",
"Click here to see older messages.": "Кликнете тук за да видите предишните съобщения.",
"Failed to upgrade room": "Неуспешно обновяване на стаята",
"The room upgrade could not be completed": "Обновяването на тази стая не можа да бъде завършено",
"Upgrade this room to version %(version)s": "Обновете тази стая до версия %(version)s",
"Unable to query for supported registration methods": "Неуспешно запитване за поддържани методи за регистрация",
"Forces the current outbound group session in an encrypted room to be discarded": "Принудително прекратява текущата изходяща групова сесия в шифрована стая",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s добави %(addedAddresses)s като адрес за тази стая.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s добави %(addedAddresses)s като адреси за тази стая.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s премахна %(removedAddresses)s като адрес за тази стая.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s премахна %(removedAddresses)s като адреси за тази стая.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s добави %(addedAddresses)s и премахна %(removedAddresses)s като адреси за тази стая.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s настрой основния адрес на тази стая на %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s премахна основния адрес на тази стая.",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Преди да изпратите логове, трябва да <a>отворите доклад за проблем в Github</a>.",
"What GitHub issue are these logs for?": "За кой Github проблем са тези логове?",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot вече използва 3-5 пъти по-малко памет, като зарежда информация за потребители само когато е нужна. Моля, изчакайте докато ресинхронизираме със сървъра!",
"Updating Riot": "Обновяване на Riot",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>HTML за страницата на Вашата общност</h1>\n<p>\n Използвайте дългото описание за да въведете нови членове в общността,\n или да разпространите важно <a href=\"foo\">връзки</a>\n</p>\n<p>\n Можете дори да използвате 'img' тагове\n</p>\n",
"Submit Debug Logs": "Изпратете логове за диагностика",
"An email address is required to register on this homeserver.": "Необходим е имейл адрес за регистрация на този сървър.",
"A phone number is required to register on this homeserver.": "Необходим е телефонен номер за регистрация на този сървър.",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Преди сте използвали Riot на %(host)s с включено постепенно зареждане на членове. В тази версия, тази настройка е изключена. Понеже локалният кеш не е съвместим при тези две настройки, Riot трябва да синхронизира акаунта Ви наново.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Ако другата версия на Riot все още е отворена в друг таб, моля затворете я. Използването на Riot на един адрес във версии с постепенно и без постепенно зареждане ще причини проблеми.",
"Incompatible local cache": "Несъвместим локален кеш",
"Clear cache and resync": "Изчисти кеша и ресинхронизирай"
}

View file

@ -130,7 +130,7 @@
"Unable to create widget.": "No s'ha pogut crear el giny.",
"Failed to send request.": "No s'ha pogut enviar la sol·licitud.",
"This room is not recognised.": "No es reconeix aquesta sala.",
"Power level must be positive integer.": "El nivell de potència ha de ser un enter positiu.",
"Power level must be positive integer.": "El nivell de poders ha de ser un enter positiu.",
"You are not in this room.": "No heu entrat a aquesta sala.",
"You do not have permission to do that in this room.": "No teniu el permís per realitzar aquesta acció en aquesta sala.",
"Missing room_id in request": "Falta l'ID de la sala en la vostra sol·licitud",
@ -167,7 +167,7 @@
"%(targetName)s joined the room.": "%(targetName)s ha entrat a la sala.",
"VoIP conference finished.": "S'ha finalitzat la conferència VoIP.",
"%(targetName)s rejected the invitation.": "%(targetName)s ha rebutjat la invitació.",
"%(targetName)s left the room.": "%(targetName)s ha sortir de la sala.",
"%(targetName)s left the room.": "%(targetName)s ha sortit de la sala.",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s ha readmès a %(targetName)s.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s ha fet fora a %(targetName)s.",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s ha retirat la invitació per a %(targetName)s.",
@ -191,7 +191,7 @@
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s ha fet visible l'històric de la sala per a desconeguts (%(visibility)s).",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s ha activat l'encriptació d'extrem a extrem (algoritme %(algorithm)s).",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s fins %(toPowerLevel)s",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ha canviat el nivell de potència de %(powerLevelDiffText)s.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ha canviat el nivell de poders de %(powerLevelDiffText)s.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s ha canviat els missatges fixats de la sala.",
"%(widgetName)s widget modified by %(senderName)s": "%(senderName)s ha modificat el giny %(widgetName)s",
"%(widgetName)s widget added by %(senderName)s": "%(senderName)s ha afegit el giny %(widgetName)s",
@ -301,7 +301,7 @@
"Failed to ban user": "No s'ha pogut expulsar l'usuari",
"Failed to mute user": "No s'ha pogut silenciar l'usuari",
"Failed to toggle moderator status": "No s'ha pogut canviar l'estat del moderador",
"Failed to change power level": "No s'ha pogut canviar el nivell de potència",
"Failed to change power level": "No s'ha pogut canviar el nivell de poders",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "No podreu desfer aquest canvi ja que estareu baixant de grau de privilegis. Només un altre usuari amb més privilegis podrà fer que els recupereu.",
"Are you sure?": "Esteu segur?",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "No podreu desfer aquesta acció ja que esteu donant al usuari el mateix nivell de privilegi que el vostre.",
@ -401,7 +401,7 @@
"Press <StartChatButton> to start a chat with someone": "Prem <StartChatButton> per a començar un xat amb algú",
"You may wish to login with a different account, or add this email to this account.": "És possible que vulgueu iniciar la sessió amb un altre compte o bé afegir aquest correu electrònic a aquest compte.",
"You have been invited to join this room by %(inviterName)s": "Heu sigut convidat a aquesta sala per %(inviterName)s",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Voleu <acceptText>accept</acceptText> o bé declineText>decline</declineText> aquesta invitació?",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Voleu <acceptText>accept</acceptText> o bé <declineText>decline</declineText> aquesta invitació?",
"Reason: %(reasonText)s": "Raó: %(reasonText)s",
"Rejoin": "Trona a entrar",
"You have been kicked from %(roomName)s by %(userName)s.": "%(userName)s us ha fet fora de la sala %(roomName)s.",

View file

@ -1221,8 +1221,8 @@
"numbered-list": "Liste mit Nummern",
"Failed to remove widget": "Widget konnte nicht entfernt werden",
"An error ocurred whilst trying to remove the widget from the room": "Ein Fehler trat auf, während versucht wurde das Widget aus diesem Raum zu entfernen",
"inline-code": "Quellcode in der Zeile",
"block-quote": "Quellcode im Block",
"inline-code": "Quellcode",
"block-quote": "Zitat",
"This homeserver has hit its Monthly Active User limit": "Dieser Heimserver hat sein Limit für monatlich aktive Nutzer erreicht",
"Please contact your service administrator to continue using this service.": "Bitte kontaktiere deinen Administrator um diesen Dienst weiter zu nutzen.",
"System Alerts": "System-Benachrichtigung",
@ -1266,5 +1266,28 @@
"The room upgrade could not be completed": "Die Raum-Aufrüstung konnte nicht fertiggestellt werden",
"Upgrade this room to version %(version)s": "Diesen Raum zur Version %(version)s aufrüsten",
"Forces the current outbound group session in an encrypted room to be discarded": "Erzwingt, dass die aktuell ausgehende Gruppen-Sitzung in einem verschlüsseltem Raum verworfen wird",
"Error Discarding Session": "Sitzung konnte nicht verworfen werden"
"Error Discarding Session": "Sitzung konnte nicht verworfen werden",
"Registration Required": "Registrierung erforderlich",
"You need to register to do this. Would you like to register now?": "Du musst dich registrieren um dies zu tun. Möchtest du dich jetzt registrieren?",
"Unable to connect to Homeserver. Retrying...": "Verbindung mit Heimserver nicht möglich. Versuche erneut...",
"Unable to query for supported registration methods": "Unterstützte Registrierungsmethoden können nicht abgefragt werden",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s fügte %(addedAddresses)s als Adresse zu diesem Raum hinzu.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s fügte %(addedAddresses)s als Adressen zu diesem Raum hinzu.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s entfernte %(removedAddresses)s als Adresse von diesem Raum.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s entfernte %(removedAddresses)s als Adressen von diesem Raum.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s setzte die Hauptadresse zu diesem Raum auf %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s entfernte die Hauptadresse von diesem Raum.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s fügte %(addedAddresses)s hinzu und entfernte %(removedAddresses)s als Adressen von diesem Raum.",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Bevor du Log-Dateien übermittelst, musst du ein <a>GitHub-Issue erstellen</a> um dein Problem zu beschreiben.",
"What GitHub issue are these logs for?": "Für welches GitHub-Issue sind diese Logs?",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot benutzt nun 3-5x weniger Arbeitsspeicher, indem Informationen über andere Nutzer erst bei Bedarf geladen werden. Bitte warte, während die Daten erneut mit dem Server abgeglichen werden!",
"Updating Riot": "Aktualisiere Riot",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>HTML for deine Community-Seite</h1>\n<p>\n Nutze die lange Beschreibung um die Community neuen Mitgliedern vorzustellen oder um\n einige wichtige <a href=\"https://beispiel.de\">Links</a> zu teilen\n</p>\n<p>\n Du kannst auch 'img'-Tags verwenden\n</p>\n",
"Submit Debug Logs": "Fehlerprotokoll senden",
"An email address is required to register on this homeserver.": "Zur Registrierung auf diesem Heimserver ist eine E-Mail-Adresse erforderlich.",
"A phone number is required to register on this homeserver.": "Zur Registrierung auf diesem Heimserver ist eine Telefon-Nummer erforderlich.",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Du hast zuvor Riot auf %(host)s ohne verzögertem Laden von Mitgliedern genutzt. In dieser Version war das verzögerte Laden deaktiviert. Da die lokal zwischengespeicherten Daten zwischen diesen Einstellungen nicht kompatibel ist, muss Riot dein Konto neu synchronisieren.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Wenn Riot mit der alten Version in einem anderen Tab geöffnet ist, schließe dies bitte, da das parallele Nutzen von Riot auf demselben Host mit aktivierten und deaktivierten verzögertem Laden, Probleme verursachen wird.",
"Incompatible local cache": "Inkompatibler lokaler Zwischenspeicher",
"Clear cache and resync": "Zwischenspeicher löschen und erneut synchronisieren"
}

View file

@ -43,6 +43,10 @@
"The file '%(fileName)s' failed to upload": "The file '%(fileName)s' failed to upload",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "The file '%(fileName)s' exceeds this home server's size limit for uploads",
"Upload Failed": "Upload Failed",
"Failure to create room": "Failure to create room",
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
"Send anyway": "Send anyway",
"Send": "Send",
"Sun": "Sun",
"Mon": "Mon",
"Tue": "Tue",
@ -82,6 +86,7 @@
"Failed to invite users to community": "Failed to invite users to community",
"Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s",
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
"Unnamed Room": "Unnamed Room",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings",
"Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again",
"Unable to enable Notifications": "Unable to enable Notifications",
@ -174,6 +179,13 @@
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.",
"Someone": "Someone",
"(not supported by this browser)": "(not supported by this browser)",
"%(senderName)s answered the call.": "%(senderName)s answered the call.",
@ -199,11 +211,6 @@
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
"Failure to create room": "Failure to create room",
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
"Send anyway": "Send anyway",
"Send": "Send",
"Unnamed Room": "Unnamed Room",
"This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.",
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
@ -324,6 +331,31 @@
"Off": "Off",
"On": "On",
"Noisy": "Noisy",
"Invalid alias format": "Invalid alias format",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
"Invalid address format": "Invalid address format",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
"not specified": "not specified",
"not set": "not set",
"Remote addresses for this room:": "Remote addresses for this room:",
"Addresses": "Addresses",
"The main address for this room is": "The main address for this room is",
"Local addresses for this room:": "Local addresses for this room:",
"This room has no local addresses": "This room has no local addresses",
"New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
"Invalid community ID": "Invalid community ID",
"'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID",
"Flair": "Flair",
"Showing flair for these communities:": "Showing flair for these communities:",
"This room is not showing flair for any communities": "This room is not showing flair for any communities",
"New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
"URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
"URL Previews": "URL Previews",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.",
"Cannot add any more widgets": "Cannot add any more widgets",
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
"Add a widget": "Add a widget",
@ -428,11 +460,11 @@
"At this time it is not possible to reply with an emote.": "At this time it is not possible to reply with an emote.",
"Markdown is disabled": "Markdown is disabled",
"Markdown is enabled": "Markdown is enabled",
"Unpin Message": "Unpin Message",
"Jump to message": "Jump to message",
"No pinned messages.": "No pinned messages.",
"Loading...": "Loading...",
"Pinned Messages": "Pinned Messages",
"Unpin Message": "Unpin Message",
"Jump to message": "Jump to message",
"%(duration)ss": "%(duration)ss",
"%(duration)sm": "%(duration)sm",
"%(duration)sh": "%(duration)sh",
@ -573,31 +605,6 @@
"Scroll to unread messages": "Scroll to unread messages",
"Jump to first unread message.": "Jump to first unread message.",
"Close": "Close",
"Invalid alias format": "Invalid alias format",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
"Invalid address format": "Invalid address format",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
"not specified": "not specified",
"not set": "not set",
"Remote addresses for this room:": "Remote addresses for this room:",
"Addresses": "Addresses",
"The main address for this room is": "The main address for this room is",
"Local addresses for this room:": "Local addresses for this room:",
"This room has no local addresses": "This room has no local addresses",
"New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
"Invalid community ID": "Invalid community ID",
"'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID",
"Flair": "Flair",
"Showing flair for these communities:": "Showing flair for these communities:",
"This room is not showing flair for any communities": "This room is not showing flair for any communities",
"New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
"URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
"URL Previews": "URL Previews",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.",
"Sunday": "Sunday",
"Monday": "Monday",
"Tuesday": "Tuesday",
@ -810,8 +817,8 @@
"Failed to send logs: ": "Failed to send logs: ",
"Submit debug logs": "Submit debug logs",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
"Riot bugs are tracked on GitHub: <a>create a GitHub issue</a>.": "Riot bugs are tracked on GitHub: <a>create a GitHub issue</a>.",
"GitHub issue link:": "GitHub issue link:",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.",
"What GitHub issue are these logs for?": "What GitHub issue are these logs for?",
"Notes:": "Notes:",
"Send logs": "Send logs",
"Unavailable": "Unavailable",
@ -877,6 +884,8 @@
"Ignore request": "Ignore request",
"Loading device info...": "Loading device info...",
"Encryption key request": "Encryption key request",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
"Updating Riot": "Updating Riot",
"Failed to upgrade room": "Failed to upgrade room",
"The room upgrade could not be completed": "The room upgrade could not be completed",
"Upgrade this room to version %(version)s": "Upgrade this room to version %(version)s",
@ -975,7 +984,7 @@
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
"You must join the room to see its files": "You must join the room to see its files",
"There are no visible files in this room": "There are no visible files in this room",
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n",
"Add rooms to the community summary": "Add rooms to the community summary",
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",
"Add to summary": "Add to summary",
@ -1129,7 +1138,7 @@
"Device ID:": "Device ID:",
"Device key:": "Device key:",
"Ignored Users": "Ignored Users",
"Debug Logs Submission": "Debug Logs Submission",
"Submit Debug Logs": "Submit Debug Logs",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.",
"Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.",
@ -1211,6 +1220,8 @@
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Password too short (min %(MIN_PASSWORD_LENGTH)s).",
"This doesn't look like a valid email address.": "This doesn't look like a valid email address.",
"This doesn't look like a valid phone number.": "This doesn't look like a valid phone number.",
"An email address is required to register on this homeserver.": "An email address is required to register on this homeserver.",
"A phone number is required to register on this homeserver.": "A phone number is required to register on this homeserver.",
"You need to enter a user name.": "You need to enter a user name.",
"An unknown error occurred.": "An unknown error occurred.",
"I already have an account": "I already have an account",
@ -1247,16 +1258,13 @@
"Import room keys": "Import room keys",
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s removed %(removedAddresses)s as an address for this room.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s removed %(removedAddresses)s as addresses for this room.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.",
"File to import": "File to import",
"Import": "Import",
"Failed to set direct chat tag": "Failed to set direct chat tag",
"Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room",
"Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room"
"Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.",
"Incompatible local cache": "Incompatible local cache",
"Clear cache and resync": "Clear cache and resync"
}

View file

@ -53,9 +53,9 @@
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "La dosiero «%(fileName)s» estas tro granda por la hejma servilo",
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Averto: ajna persono aldonita al komunumo estos publike videbla al iu ajn, kiu konas la identigilon de tiu komunumo",
"Which rooms would you like to add to this community?": "Kiujn ĉambrojn vi volas aldoni al ĉi tiu komunumo?",
"Show these rooms to non-members on the community page and room list?": "Ĉu la ĉambroj montriĝu al malanoj en la komunuma paĝo kaj listo de ĉambroj?",
"Show these rooms to non-members on the community page and room list?": "Montri tiujn babilejojn al malanoj en la komunuma paĝo kaj listo de babilejoj?",
"Add rooms to the community": "Aldoni ĉambrojn al la komunumo",
"Room name or alias": "Nomo aŭ kromnomo de ĉambro",
"Room name or alias": "Nomo aŭ kromnomo de babilejo",
"Add to community": "Aldoni al komunumo",
"Failed to invite the following users to %(groupId)s:": "Malsukcesis inviti jenajn uzantojn al %(groupId)s:",
"Failed to invite users to community": "Malsukcesis inviti novajn uzantojn al komunumo",
@ -74,28 +74,28 @@
"Who would you like to communicate with?": "Kun kiu vi volas komuniki?",
"Email, name or matrix ID": "Retpoŝtadreso, nomo, aŭ Matrix-identigaĵo",
"Start Chat": "Komenci babilon",
"Invite new room members": "Inviti novajn ĉambranojn",
"Who would you like to add to this room?": "Kiun vi ŝatus aldoni al tiu ĉi ĉambro?",
"Invite new room members": "Inviti novajn babilejanojn",
"Who would you like to add to this room?": "Kiun vi ŝatus aldoni al tiu ĉi babilejo?",
"Send Invites": "Sendi invitojn",
"Failed to invite user": "Malsukcesis inviti uzanton",
"Operation failed": "Ago malsukcesis",
"Failed to invite": "Invito malsukcesis",
"Failed to invite the following users to the %(roomName)s room:": "Malsukcesis inviti la jenajn uzantojn al la ĉambro %(roomName)s:",
"Failed to invite the following users to the %(roomName)s room:": "Malsukcesis inviti la jenajn uzantojn al la babilejo %(roomName)s:",
"You need to be logged in.": "Vi devas saluti.",
"You need to be able to invite users to do that.": "Vi bezonas permeson inviti uzantojn por tio.",
"Unable to create widget.": "Fenestraĵo ne kreeblas.",
"Failed to send request.": "Malsukcesis sendi peton.",
"This room is not recognised.": "Ĉi tiu ĉambro ne estas rekonita.",
"This room is not recognised.": "Ĉi tiu babilejo ne estas rekonita.",
"Power level must be positive integer.": "Nivelo de potenco devas esti entjero pozitiva.",
"You are not in this room.": "Vi ne estas en tiu ĉi ĉambro.",
"You do not have permission to do that in this room.": "Vi ne havas permeson fari tion en tiu ĉi ĉambro.",
"You are not in this room.": "Vi ne estas en tiu ĉi babilejo.",
"You do not have permission to do that in this room.": "Vi ne havas permeson fari tion en tiu babilejo.",
"Missing room_id in request": "En peto mankas «room_id»",
"Room %(roomId)s not visible": "Ĉambro %(roomId)s ne videblas",
"Room %(roomId)s not visible": "babilejo %(roomId)s ne videblas",
"Missing user_id in request": "En peto mankas «user_id»",
"Usage": "Uzo",
"/ddg is not a command": "/ddg ne estas komando",
"To use it, just wait for autocomplete results to load and tab through them.": "Por uzi ĝin, atendu aperon de sugestaj rezultoj, kaj tabu tra ili.",
"Unrecognised room alias:": "Nerekonita ĉambra alinomo:",
"Unrecognised room alias:": "Nerekonita babileja kromnomo:",
"Ignored user": "Malatentata uzanto",
"You are now ignoring %(userId)s": "Vi nun malatentas uzanton %(userId)s",
"Unignored user": "Reatentata uzanto",
@ -119,16 +119,16 @@
"%(senderName)s changed their profile picture.": "%(senderName)s ŝanĝis sian profilbildon.",
"%(senderName)s set a profile picture.": "%(senderName)s agordis profilbildon.",
"VoIP conference started.": "Rettelefona voko komenciĝis.",
"%(targetName)s joined the room.": "%(targetName)s venis en la ĉambron.",
"%(targetName)s joined the room.": "%(targetName)s venis en la babilejo.",
"VoIP conference finished.": "Rettelefona voko finiĝis.",
"%(targetName)s rejected the invitation.": "%(targetName)s rifuzis la inviton.",
"%(targetName)s left the room.": "%(targetName)s forlasis la ĉambron.",
"%(targetName)s left the room.": "%(targetName)s forlasis la babilejo.",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s malbaris uzanton %(targetName)s.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s forpelis uzanton %(targetName)s.",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s nuligis inviton por %(targetName)s.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s ŝanĝis la temon al «%(topic)s».",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s forigis nomon de la ĉambro.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s ŝanĝis nomon de la ĉambro al %(roomName)s.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s forigis nomon de la babilejo.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s ŝanĝis nomon de la babilejo al %(roomName)s.",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sendis bildon.",
"Someone": "Iu",
"(not supported by this browser)": "(nesubtenata de tiu ĉi foliumilo)",
@ -138,16 +138,16 @@
"(unknown failure: %(reason)s)": "(nekonata eraro: %(reason)s)",
"%(senderName)s ended the call.": "%(senderName)s finis la vokon.",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s faris vokon de speco: %(callType)s.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sendis ĉambran inviton al %(targetDisplayName)s.",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s videbligis estontan historion de la ĉambro al ĉiuj ĉambranoj, de la tempo de invito.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s videbligis estontan historion de la ĉambro al ĉiuj ĉambranoj, de la tempo de aliĝo.",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s videbligis estontan historion de la ĉambro al ĉiuj ĉambranoj.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s videbligis estontan historion de la ĉambro al ĉiuj.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s videbligis estontan historion de la ĉambro al nekonatoj (%(visibility)s).",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sendis babilejan inviton al %(targetDisplayName)s.",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s videbligis estontan historion de la babilejo al ĉiuj babilejanoj, ekde la tempo de invito.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s videbligis estontan historion de la babilejo al ĉiuj babilejanoj, ekde la tempo de aliĝo.",
"%(senderName)s made future room history visible to all room members.": "%(senderName)s videbligis estontan historion de la babilejo al ĉiuj babilejanoj.",
"%(senderName)s made future room history visible to anyone.": "%(senderName)s videbligis estontan historion de la babilejo al ĉiuj.",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s videbligis estontan historion de la babilejo al nekonata (%(visibility)s).",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s ŝaltis ĝiscelan ĉifradon (algoritmo: %(algorithm)s).",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s de %(fromPowerLevel)s al %(toPowerLevel)s",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ŝanĝis la potencan nivelon de %(powerLevelDiffText)s.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s ŝanĝis la fiksitajn mesaĝojn de la ĉambro.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s ŝanĝis la fiksitajn mesaĝojn de la babilejo.",
"%(widgetName)s widget modified by %(senderName)s": "Fenestraĵon %(widgetName)s ŝanĝis %(senderName)s",
"%(widgetName)s widget added by %(senderName)s": "Fenestraĵon %(widgetName)s aldonis %(senderName)s",
"%(widgetName)s widget removed by %(senderName)s": "Fenestraĵon %(widgetName)s forigis %(senderName)s",
@ -155,13 +155,13 @@
"%(names)s and %(count)s others are typing|other": "%(names)s kaj %(count)s aliaj tajpas",
"%(names)s and %(count)s others are typing|one": "%(names)s kaj unu alia tajpas",
"%(names)s and %(lastPerson)s are typing": "%(names)s kaj %(lastPerson)s tajpas",
"Failure to create room": "Malsukcesis krei ĉambron",
"Failure to create room": "Malsukcesis krei babilejon",
"Server may be unavailable, overloaded, or you hit a bug.": "Servilo povas esti neatingebla, troŝarĝita, aŭ vi renkontis cimon.",
"Unnamed Room": "Sennoma ĉambro",
"Unnamed Room": "Sennoma Babilejo",
"Your browser does not support the required cryptography extensions": "Via foliumilo ne subtenas la bezonatajn ĉifrajn kromprogramojn",
"Not a valid Riot keyfile": "Nevalida ŝlosila dosiero de Riot",
"Authentication check failed: incorrect password?": "Aŭtentiga kontrolo malsukcesis: ĉu pro malĝusta pasvorto?",
"Failed to join room": "Malsukcesis aliĝi al ĉambro",
"Failed to join room": "Malsukcesis aliĝi al babilejo",
"Message Pinning": "Fikso de mesaĝoj",
"Disable Emoji suggestions while typing": "Malŝalti mienetajn sugestojn dum tajpado",
"Use compact timeline layout": "Uzi densan okazordan aranĝon",
@ -174,7 +174,7 @@
"Always show message timestamps": "Ĉiam montri mesaĝajn tempindikojn",
"Autoplay GIFs and videos": "Aŭtomate ludi GIF-bildojn kaj videojn",
"Call Failed": "Voko malsukcesis",
"There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "En la ĉambro estas nekonataj aparatoj. Se vi daŭrigos ne kontrolinte ilin, iu povos subaŭskulti vian vokon.",
"There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "En la babilejo estas nekonataj aparatoj: se vi daŭrigos ne kontrolante ilin, iu povos subaŭskulti vian vokon.",
"Review Devices": "Kontroli aparatojn",
"Call Anyway": "Tamen voki",
"Answer Anyway": "Tamen respondi",
@ -183,18 +183,18 @@
"Send anyway": "Tamen sendi",
"Send": "Sendi",
"Enable automatic language detection for syntax highlighting": "Ŝalti aŭtomatan rekonon de lingvo por sintaksa markado",
"Hide avatars in user and room mentions": "Kaŝi profilbildojn en mencioj de uzantoj kaj ĉambroj",
"Hide avatars in user and room mentions": "Kaŝi profilbildojn en mencioj de uzantoj kaj babilejoj",
"Disable big emoji in chat": "Malŝalti grandajn mienetojn en babilo",
"Don't send typing notifications": "Ne elsendi sciigojn pri tajpado",
"Automatically replace plain text Emoji": "Aŭtomate anstataŭigi tekstajn mienetojn",
"Mirror local video feed": "Speguli lokan videon",
"Disable Peer-to-Peer for 1:1 calls": "Malŝalti samtavolajn duopajn vokojn",
"Never send encrypted messages to unverified devices from this device": "Neniam sendi neĉifritajn mesaĝojn al nekontrolitaj aparatoj de tiu ĉi aparato",
"Never send encrypted messages to unverified devices in this room from this device": "Neniam sendi ĉifritajn mesaĝojn al nekontrolitaj aparatoj en tiu ĉi ĉambro de tiu ĉi aparto",
"Never send encrypted messages to unverified devices in this room from this device": "Neniam sendi ĉifritajn mesaĝojn al nekontrolitaj aparatoj en tiu ĉi babilejo el tiu ĉi aparato",
"Enable inline URL previews by default": "Ŝalti entekstan antaŭrigardon al retadresoj",
"Enable URL previews for this room (only affects you)": "Ŝalti antaŭrigardon al retadresoj por ĉi tiu ĉambro (nur pro vi)",
"Enable URL previews by default for participants in this room": "Ŝalti antaŭrigardon al retadresoj por anoj de ĉi tiu ĉambro",
"Room Colour": "Koloro de ĉambro",
"Enable URL previews for this room (only affects you)": "Ŝalti URL-antaŭrigardon en ĉi tiu babilejo (nur por vi)",
"Enable URL previews by default for participants in this room": "Ŝalti URL-antaŭrigardon por anoj de ĉi tiu babilejo",
"Room Colour": "Babilejo-koloro",
"Active call (%(roomName)s)": "Aktiva voko (%(roomName)s)",
"unknown caller": "nekonata vokanto",
"Incoming voice call from %(name)s": "Envena voĉvoko de %(name)s",
@ -216,7 +216,7 @@
"New passwords don't match": "Novaj pasvortoj ne kongruas",
"Passwords can't be empty": "Pasvortoj ne povas esti malplenaj",
"Continue": "Daŭrigi",
"Export E2E room keys": "Elporti ĝiscele ĉifrajn ŝlosilojn de la ĉambro",
"Export E2E room keys": "Elporti ĝiscele ĉifrajn ŝlosilojn de la babilejo",
"Do you want to set an email address?": "Ĉu vi volas agordi retpoŝtadreson?",
"Current password": "Nuna pasvorto",
"Password": "Pasvorto",
@ -236,7 +236,7 @@
"Disable Notifications": "Malŝalti sciigojn",
"Enable Notifications": "Ŝalti sciigojn",
"Cannot add any more widgets": "Pluaj fenestraĵoj ne aldoneblas",
"The maximum permitted number of widgets have already been added to this room.": "La plejgranda nombro da fenestraĵoj jam aldoniĝis al ĉi tiu ĉambro.",
"The maximum permitted number of widgets have already been added to this room.": "La maksimuma permesata nombro de fenestraĵoj jam aldoniĝis al tiu babilejo.",
"Add a widget": "Aldoni fenestraĵon",
"Drop File Here": "Demetu dosieron tien ĉi",
"Drop file here to upload": "Demetu dosieron tien ĉi por ĝin alŝuti",
@ -275,7 +275,7 @@
"Devices": "Aparatoj",
"Unignore": "Reatenti",
"Ignore": "Malatenti",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Ŝanĝo de pasvorto nuntempe nuligos ĉiujn ĝiscele ĉifrajn ŝlosilojn sur ĉiuj viaj aparatoj. Tio faros ĉifritajn babilajn historiojn nelegeblaj, krom se vi unue elportos viajn ĉambrajn ŝlosilojn kaj reenportos ilin poste. Estontece ĉi tio pliboniĝos.",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Ŝanĝo de pasvorto nuntempe nuligos ĉiujn ĝiscele ĉifrajn ŝlosilojn sur ĉiuj viaj aparatoj. Tio igos ĉifritajn babilajn historiojn nelegeblaj, krom se vi unue elportos viajn babilejajn ŝlosilojn kaj reenportos ilin poste. Estonte tio pliboniĝos.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s ŝanĝis la profilbildon de %(roomName)s",
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Vi estas direktota al ekstera retejo por aŭtentigi vian konton por uzo kun %(integrationsUrl)s. Ĉu vi volas daŭrigi tion?",
"Jump to read receipt": "Salti al legokonfirmo",
@ -413,7 +413,7 @@
"No users have specific privileges in this room": "Neniuj uzantoj havas specialajn privilegiojn en tiu ĉi ĉambro",
"Banned users": "Forbaritaj uzantoj",
"This room is not accessible by remote Matrix servers": "Ĉi tiu ĉambro ne atingeblas por foraj serviloj de Matrix",
"Leave room": "Eliri el ĉambro",
"Leave room": "Eliri babilejon",
"Favourite": "Ŝatata",
"Tagged as: ": "Etikedita kiel: ",
"To link to a room it must have <a>an address</a>.": "Por esti ligebla, ĉambro devas havi <a>adreson</a>.",
@ -874,13 +874,13 @@
"Unbans user with given id": "Malforbaras uzanton kun la donita identigaĵo",
"Define the power level of a user": "Difini la potencan nivelon de uzanto",
"Deops user with given id": "Senestrigas uzanton kun donita identigaĵo",
"Invites user with given id to current room": "Invitas uzanton kun donita identigaĵo al la nuna ĉambro",
"Joins room with given alias": "Aliĝigas al ĉambro kun la donita kromnomo",
"Sets the room topic": "Agordas la ĉambran temon",
"Invites user with given id to current room": "Invitas uzanton per identigilo al la nuna babilejo",
"Joins room with given alias": "Aliĝas al babilejo per kromnomo",
"Sets the room topic": "Agordas la babilejan temon",
"Kicks user with given id": "Forpelas uzanton kun la donita identigaĵo",
"Changes your display nickname": "Ŝanĝas vian vidigan nomon",
"Searches DuckDuckGo for results": "Serĉas rezultojn per DuckDuckGo",
"Changes colour scheme of current room": "Ŝanĝas kolorsĥemon de la nuna ĉambro",
"Changes colour scheme of current room": "Ŝanĝas kolorskemon de la nuna babilejo",
"Verifies a user, device, and pubkey tuple": "Kontrolas opon de uzanto, aparato, kaj publika ŝlosilo",
"Ignores a user, hiding their messages from you": "Malatentas uzanton, kaŝante ĝiajn mesaĝojn de vi",
"Stops ignoring a user, showing their messages going forward": "Ĉesas malatenti uzanton, montronte ĝiajn pluajn mesaĝojn",
@ -938,7 +938,7 @@
"The platform you're on": "Via sistemtipo",
"Which officially provided instance you are using, if any": "Kiun oficiale disponeblan aperon vi uzas, se iun ajn",
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Ĉu vi uzas la riĉtekstan reĝimon de la riĉteksta redaktilo aŭ ne",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Kiam ĉi tiu paĝo enhavas identigeblajn informojn, ekzemple ĉambron, uzantan aŭ grupan identigilon, ĝi sendiĝas al la servilo sen tiuj.",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Kiam ĉi tiu paĝo enhavas identigeblajn informojn, ekzemple babilejon, uzantan aŭ grupan identigilon, ili estas formetataj antaŭ sendado al la servilo.",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s",
"Disable Community Filter Panel": "Malŝalti komunuman filtran breton",
"Failed to add tag %(tagName)s to room": "Malsukcesis aldoni etikedon %(tagName)s al ĉambro",
@ -1044,10 +1044,10 @@
"Failed to send custom event.": "Malsukcesis sendi propran okazon.",
"What's new?": "Kio novas?",
"Notify me for anything else": "Sciigu min pri ĉio alia",
"When I'm invited to a room": "Kiam mi estas invitita al ĉambro",
"When I'm invited to a room": "Kiam mi estas invitita al babilejo",
"Keywords": "Ŝlosilvortoj",
"Can't update user notification settings": "Agordoj de sciigoj al uzanto ne ĝisdatigeblas",
"Notify for all other messages/rooms": "Sciigu min por ĉiu alia babilejo",
"Notify for all other messages/rooms": "Sciigu min por ĉiuj aliaj mesaĝoj/babilejoj",
"Unable to look up room ID from server": "Ĉambra identigaĵo ne akireblas de la servilo",
"Couldn't find a matching Matrix room": "Malsukcesis trovi kongruan ĉambron en Matrix",
"All Rooms": "Ĉiuj babilejoj",

File diff suppressed because it is too large Load diff

View file

@ -46,7 +46,7 @@
"New password": "Pasahitz berria",
"User name": "Erabiltzaile-izena",
"Email address": "E-mail helbidea",
"Email address (optional)": "E-mail helbidea (aukerazkoa)",
"Email address (optional)": "E-mail helbidea (aukerakoa)",
"Confirm your new password": "Berretsi zure pasahitza",
"This Home Server would like to make sure you are not a robot": "Hasiera zerbitzari honek robot bat ez zarela egiaztatu nahi du",
"I have verified my email address": "Nire e-mail helbidea baieztatu dut",
@ -138,7 +138,7 @@
"Hangup": "Eseki",
"Homeserver is": "Hasiera zerbitzaria:",
"Identity Server is": "Identitate zerbitzaria:",
"Mobile phone number (optional)": "Mugikor zenbakia (aukerazkoa)",
"Mobile phone number (optional)": "Mugikor zenbakia (aukerakoa)",
"Moderator": "Moderatzailea",
"Account": "Kontua",
"Access Token:": "Sarbide tokena:",
@ -157,7 +157,7 @@
"Microphone": "Mikrofonoa",
"Camera": "Kamera",
"Hide removed messages": "Ezkutatu kendutako mezuak",
"Alias (optional)": "Ezizena (aukerazkoa)",
"Alias (optional)": "Ezizena (aukerakoa)",
"%(names)s and %(lastPerson)s are typing": "%(names)s eta %(lastPerson)s idazten ari dira",
"An error has occurred.": "Errore bat gertatu da.",
"Are you sure?": "Ziur zaude?",
@ -359,7 +359,7 @@
"riot-web version:": "riot-web bertsioa:",
"Room %(roomId)s not visible": "%(roomId)s gela ez dago ikusgai",
"Room Colour": "Gelaren kolorea",
"Room name (optional)": "Gelaren izena (aukerazkoa)",
"Room name (optional)": "Gelaren izena (aukerakoa)",
"%(roomName)s does not exist.": "Ez dago %(roomName)s izeneko gela.",
"%(roomName)s is not accessible at this time.": "%(roomName)s ez dago eskuragarri orain.",
"Scroll to bottom of page": "Korritu orria behera arte",
@ -566,7 +566,7 @@
"To continue, please enter your password.": "Jarraitzeko sartu zure pasahitza.",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "Gailu hau fidagarria dela egiaztatzeko, kontaktatu bere jabea beste medio bat erabiliz (adib. aurrez aurre edo telefonoz deituz) eta galdetu beraien erabiltzaile-ezarpenetan bere gailurako ikusten duen gakoa hemen beheko bera den:",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "Bat badator sakatu egiaztatu botoia. Bat ez badator, beste inor gailu hau atzematen dago eta blokeatu beharko zenuke.",
"In future this verification process will be more sophisticated.": "etorkizunean egiaztaketa metodoa hobetuko da.",
"In future this verification process will be more sophisticated.": "Etorkizunean egiaztaketa metodo hau hobetuko da.",
"Unable to restore session": "Ezin izan da saioa berreskuratu",
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Aurretik Riot bertsio berriago bat erabili baduzu, zure saioa bertsio honekin bateraezina izan daiteke. Itxi leiho hau eta itzuli bertsio berriagora.",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Gailu bakoitzaren egiaztaketa prozesua jarraitzea aholkatzen dizugu, benetako jabeari dagozkiela baieztatzeko, baina mezua egiaztatu gabe birbidali dezakezu ere.",
@ -835,7 +835,7 @@
"was invited %(count)s times|one": "gonbidatua izan da",
"were banned %(count)s times|other": "%(count)s aldiz debekatuak izan dira",
"were banned %(count)s times|one": "debekatuak izan dira",
"was banned %(count)s times|other": "%(count)s aldi debekatuak izan dira",
"was banned %(count)s times|other": "%(count)s aldiz debekatuak izan dira",
"were unbanned %(count)s times|other": "%(count)s aldiz kendu zaie debekua",
"were unbanned %(count)s times|one": "debekua kendu zaie",
"was unbanned %(count)s times|other": "%(count)s aldiz kendu zaio debekua",
@ -1266,5 +1266,24 @@
"Legal": "Legezkoa",
"Please <a>contact your service administrator</a> to continue using this service.": "<a>Jarri kontaktuan zerbitzuaren administratzailearekin</a> zerbitzu hau erabiltzen jarraitzeko.",
"Forces the current outbound group session in an encrypted room to be discarded": "Uneko irteerako talde saioa zifratutako gela batean baztertzera behartzen du",
"Error Discarding Session": "Errorea saioa baztertzean"
"Error Discarding Session": "Errorea saioa baztertzean",
"Registration Required": "Erregistratzea ezinbestekoa da",
"You need to register to do this. Would you like to register now?": "Hau egiteko erregistratu egin behar zara. Orain erregistratu nahi duzu?",
"Unable to connect to Homeserver. Retrying...": "Ezin izan da hasiera zerbitzarira konektatu. Berriro saiatzen...",
"Unable to query for supported registration methods": "Ezin izan da onartutako erregistratze metodoei buruz itaundu",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s erabiltzaileak %(addedAddresses)s gehitu du gelako helbide gisa.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s erabiltzaileak %(addedAddresses)s helbideak gehitu dizkio gela honi.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s erabiltzileak %(removedAddresses)s helbideak kendu ditu gela honetatik.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s erabiltzaileak %(removedAddresses)s helbideak kendu ditu gela honetatik.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s erabiltzaileak %(addedAddresses)s helbideak gehitu eta %(removedAddresses)s helbideak kendu ditu gela honetatik.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s erabiltzileak %(address)s ezarri du gela honetako helbide nagusi gisa.",
"%(senderName)s removed the main address for this room.": "%(senderName)s erabiltzaileak gela honen helbide nagusia kendu du.",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Egunkariak bidali aurretik, <a>GitHub arazo bat sortu</a> behar duzu gertatzen zaizuna azaltzeko.",
"What GitHub issue are these logs for?": "Zein GitHub arazorako egunkariak dira hauek?",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot-ek orain 3-5 aldiz memoria gutxiago darabil, beste erabiltzaileen informazioa behar denean besterik ez kargatzen. Itxaron zerbitzariarekin sinkronizatzen garen bitartean!",
"Updating Riot": "Riot eguneratzen",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>Zure komunitatearen orriaren HTMLa</h1>\n<p>\n Erabili deskripzio luzea kide berriek komunitatea ezagutu dezaten, edo eman ezagutzera <a href=\"foo\">esteka</a> garrantzitsuak\n</p>\n<p>\n 'img' etiketak erabili ditzakezu ere\n</p>\n",
"Submit Debug Logs": "Bidali arazketa egunkariak",
"An email address is required to register on this homeserver.": "e-mail helbide bat behar da hasiera-zerbitzari honetan izena emateko.",
"A phone number is required to register on this homeserver.": "telefono zenbaki bat behar da hasiera-zerbitzari honetan izena emateko."
}

View file

@ -1133,12 +1133,12 @@
"When I'm invited to a room": "Quand je suis invité dans un salon",
"Checking for an update...": "Recherche de mise à jour...",
"There are advanced notifications which are not shown here": "Il existe une configuration avancée des notifications qui ne peut être affichée ici",
"Logs sent": "Rapports envoyés",
"Logs sent": "Journaux envoyés",
"GitHub issue link:": "Lien du signalement GitHub :",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Les rapports de débogage contiennent des données d'usage de l'application qui incluent votre nom d'utilisateur, les identifiants ou alias des salons ou groupes auxquels vous avez rendu visite ainsi que les noms des autres utilisateurs. Ils ne contiennent aucun message.",
"Failed to send logs: ": "Échec lors de l'envoi des rapports : ",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Les journaux de débogage contiennent des données d'usage de l'application qui incluent votre nom d'utilisateur, les identifiants ou alias des salons ou groupes auxquels vous avez rendu visite ainsi que les noms des autres utilisateurs. Ils ne contiennent aucun message.",
"Failed to send logs: ": "Échec lors de l'envoi des journaux : ",
"Notes:": "Notes :",
"Preparing to send logs": "Préparation d'envoi des rapports",
"Preparing to send logs": "Préparation d'envoi des journaux",
"Missing roomId.": "Identifiant de salon manquant.",
"Picture": "Image",
"Popout widget": "Détacher le widget",
@ -1149,7 +1149,7 @@
"Always show encryption icons": "Toujours afficher les icônes de chiffrement",
"Riot bugs are tracked on GitHub: <a>create a GitHub issue</a>.": "Les bugs de Riot sont suivis sur GitHub : <a>créer un signalement GitHub</a>.",
"Log out and remove encryption keys?": "Se déconnecter et effacer les clés de chiffrement ?",
"Send Logs": "Envoyer les rapports",
"Send Logs": "Envoyer les journaux",
"Clear Storage and Sign Out": "Effacer le stockage et se déconnecter",
"Refresh": "Rafraîchir",
"We encountered an error trying to restore your previous session.": "Une erreur est survenue lors de la récupération de la dernière session.",
@ -1269,5 +1269,27 @@
"Error Discarding Session": "Erreur lors du rejet de la session",
"Registration Required": "Enregistrement nécessaire",
"You need to register to do this. Would you like to register now?": "Vous devez vous enregistrer pour faire cela. Voulez-vous créer un compte maintenant ?",
"Unable to query for supported registration methods": "Impossible de demander les méthodes d'enregistrement prises en charge"
"Unable to query for supported registration methods": "Impossible de demander les méthodes d'enregistrement prises en charge",
"Unable to connect to Homeserver. Retrying...": "Impossible de se connecter au serveur d'accueil. Reconnexion...",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s a ajouté %(addedAddresses)s comme adresse pour ce salon.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s a ajouté %(addedAddresses)s comme adresses pour ce salon.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s a supprimé %(removedAddresses)s comme adresse pour ce salon.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s a supprimé %(removedAddresses)s comme adresses pour ce salon.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s a ajouté %(addedAddresses)s et supprimé %(removedAddresses)s comme adresses pour ce salon.",
"%(senderName)s set the canonical address for this room to %(address)s.": "%(senderName)s a défini l'adresse canonique de ce salon comme %(address)s.",
"%(senderName)s removed the canonical address for this room.": "%(senderName)s a supprimé l'adresse canonique de ce salon.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s à défini l'adresse principale pour ce salon comme %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s a supprimé l'adresse principale de ce salon.",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot utilise maintenant 3 à 5 fois moins de mémoire, en ne chargeant les informations des autres utilisateurs que quand elles sont nécessaires. Veuillez patienter pendant que l'on se resynchronise avec le serveur !",
"Updating Riot": "Mise à jour de Riot",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Avant de soumettre vos journaux, vous devez <a>créer une « issue » sur GitHub</a> pour décrire votre problème.",
"What GitHub issue are these logs for?": "Pour quelle « issue » Github sont ces journaux ?",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>HTML pour votre page de communauté</h1>\n<p>\n Utilisez la description longue pour présenter la communauté aux nouveaux membres,\n ou fournir des <a href=\"foo\">liens</a> importants\n</p>\n<p>\n Vous pouvez même utiliser des balises « img »\n</p>\n",
"Submit Debug Logs": "Envoyer les journaux de débogage",
"An email address is required to register on this homeserver.": "Une adresse e-mail est nécessaire pour s'enregistrer sur ce serveur d'accueil.",
"A phone number is required to register on this homeserver.": "Un numéro de téléphone est nécessaire pour s'enregistrer sur ce serveur d'accueil.",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Vous avez utilisé auparavant Riot sur %(host)s avec le chargement différé activé. Dans cette version le chargement différé est désactivé. Comme le cache local n'est pas compatible entre ces deux réglages, Riot doit resynchroniser votre compte.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Si l'autre version de Riot est encore ouverte dans un autre onglet, merci de le fermer car l'utilisation de Riot sur le même hôte avec le chargement différé activé et désactivé à la fois causera des problèmes.",
"Incompatible local cache": "Cache local incompatible",
"Clear cache and resync": "Vider le cache et resynchroniser"
}

View file

@ -1269,5 +1269,27 @@
"Forces the current outbound group session in an encrypted room to be discarded": "A jelenlegi csoport munkamenet törlését kikényszeríti a titkosított szobában",
"Registration Required": "Regisztrációt igényel",
"You need to register to do this. Would you like to register now?": "Hogy ezt megtedd regisztrálnod kell. Szeretnél regisztrálni?",
"Unable to query for supported registration methods": "A támogatott regisztrációs folyamatok listáját nem sikerült lekérdezni"
"Unable to query for supported registration methods": "A támogatott regisztrációs folyamatok listáját nem sikerült lekérdezni",
"Unable to connect to Homeserver. Retrying...": "A matrix szerverrel nem lehet felvenni a kapcsolatot. Újrapróbálkozás...",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s szoba címnek beállította: %(addedAddresses)s.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s szoba címnek hozzáadta: %(addedAddresses)s.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s törölte a szoba címek közül: %(removedAddresses)s.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s törölte a szoba címek közül: %(removedAddresses)s.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s hozzáadta a szoba címekhez: %(addedAddresses)s és törölte a címek közül: %(removedAddresses)s.",
"%(senderName)s set the canonical address for this room to %(address)s.": "%(senderName)s olvasható címet allított be a szobához: %(address)s.",
"%(senderName)s removed the canonical address for this room.": "%(senderName)s törölte a szoba olvasható címét.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s elsődleges szoba címnek beállította: %(address)s.",
"%(senderName)s removed the main address for this room.": "A szoba elsődleges címét %(senderName)s törölte.",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Mielőtt a naplót elküldöd, egy <a>Github jegyet kell nyitni</a> amiben leírod a problémádat.",
"What GitHub issue are these logs for?": "Melyik Github jegyhez tartozik a napló?",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "3-, 5-ször kevesebb memóriát használ a Riot azzal, hogy csak akkor tölti be az információkat a felhasználókról amikor arra szükség van. Kérlek várd meg amíg újraszinkronizáljuk a szerverrel!",
"Updating Riot": "Riot frissítése",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>HTML a közösségi oldaladhoz</h1>\n<p>\n Mutasd be a közösségedet az újoncoknak vagy ossz meg\n pár fontos <a href=\"foo\">linket</a>\n</p>\n<p>\n Még „img” tag-et is használhatsz.\n</p>\n",
"An email address is required to register on this homeserver.": "Erre a Matrix szerverre való regisztrációhoz az e-mail címet meg kell adnod.",
"A phone number is required to register on this homeserver.": "Erre a Matrix szerverre való regisztrációhoz a telefonszámot meg kell adnod.",
"Submit Debug Logs": "Hibakeresési napló elküldése",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Előzőleg a szoba tagság késleltetett betöltésének engedélyével itt használtad a Riotot: %(host)s. Ebben a verzióban viszont a késleltetett betöltés nem engedélyezett. Mivel a két gyorsítótár nem kompatibilis egymással így Riotnak újra kell szinkronizálnia a fiókot.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Ha a másik Riot verzió fut még egy másik fülön, kérlek zárd be, mivel ha ugyanott használod a Riotot bekapcsolt késleltetett betöltéssel és kikapcsolva is akkor problémák adódhatnak.",
"Incompatible local cache": "A helyi gyorsítótár nem kompatibilis ezzel a verzióval",
"Clear cache and resync": "Gyorsítótár törlése és újraszinkronizálás"
}

View file

@ -341,5 +341,39 @@
"Collapse panel": "Lipat panel",
"With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "Dengan browser ini, tampilan dari aplikasi mungkin tidak sesuai, dan beberapa atau bahkan semua fitur mungkin tidak berjalan. Jika Anda ingin tetap mencobanya, Anda bisa melanjutkan, tapi Anda tanggung sendiri jika muncul masalah yang terjadi!",
"Checking for an update...": "Cek pembaruan...",
"There are advanced notifications which are not shown here": "Ada notifikasi lanjutan yang tidak ditampilkan di sini"
"There are advanced notifications which are not shown here": "Ada notifikasi lanjutan yang tidak ditampilkan di sini",
"This email address is already in use": "Alamat email ini telah terpakai",
"This phone number is already in use": "Nomor telepon ini telah terpakai",
"Failed to verify email address: make sure you clicked the link in the email": "Gagal memverifikasi alamat email: pastikan Anda telah menekan link di dalam email",
"The version of Riot.im": "Versi Riot.im",
"Your language of choice": "Pilihan bahasamu",
"Your homeserver's URL": "URL Homeserver Anda",
"Your identity server's URL": "URL Server Identitas Anda",
"e.g. %(exampleValue)s": "",
"Every page you use in the app": "Setiap halaman yang digunakan di app",
"e.g. <CurrentPageURL>": "e.g. <URLHalamanSaatIni>",
"Your User Agent": "User Agent Anda",
"Your device resolution": "Resolusi perangkat Anda",
"Analytics": "Analitik",
"The information being sent to us to help make Riot.im better includes:": "Informasi yang dikirim membantu kami memperbaiki Riot.im, termasuk:",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Apabila terdapat informasi yang dapat digunakan untuk pengenalan pada halaman ini, seperti ruang, pengguna, atau ID grup, kami akan menghapusnya sebelum dikirim ke server.",
"Call Failed": "Panggilan Gagal",
"There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "Ada perangkat yang belum dikenal di ruang ini: apabila Anda melanjutkan tanpa memverifikasi terlebih dahulu, pembicaraan Anda dapat disadap orang yang tidak diinginkan.",
"Review Devices": "Telaah Perangkat",
"Call Anyway": "Tetap Panggil",
"Answer Anyway": "Tetap Jawab",
"Call": "Panggilan",
"Answer": "Jawab",
"Call Timeout": "Masa Berakhir Panggilan",
"The remote side failed to pick up": "Gagal jawab oleh pihak lain",
"Unable to capture screen": "Tidak dapat menangkap tampilan",
"Existing Call": "Panggilan Berlangsung",
"VoIP is unsupported": "VoIP tidak didukung",
"You cannot place VoIP calls in this browser.": "Anda tidak dapat melakukan panggilan VoIP di browser ini.",
"A conference call could not be started because the intgrations server is not available": "Panggilan massal tidak dapat dimulai karena server integrasi tidak tersedia",
"Call in Progress": "Panggilan Berlangsung",
"A call is currently being placed!": "Sedang melakukan panggilan sekarang!",
"A call is already in progress!": "Masih ada panggilan berlangsung!",
"Permission Required": "Permisi Dibutuhkan",
"You do not have permission to start a conference call in this room": "Anda tidak memiliki permisi untuk memulai panggilan massal di ruang ini"
}

View file

@ -13,7 +13,7 @@
"Cancel": "Annulla",
"Close": "Chiudi",
"Create new room": "Crea una nuova stanza",
"Custom Server Options": "Opzioni Server Personalizzate",
"Custom Server Options": "Opzioni server personalizzate",
"Dismiss": "Chiudi",
"Error": "Errore",
"Favourite": "Preferito",
@ -48,7 +48,7 @@
"Edit": "Modifica",
"This email address is already in use": "Questo indirizzo e-mail è già in uso",
"This phone number is already in use": "Questo numero di telefono è già in uso",
"Failed to verify email address: make sure you clicked the link in the email": "Impossibile verificare l'indirizzo e-mail: accertati di aver cliccato il link nella e-mail",
"Failed to verify email address: make sure you clicked the link in the email": "Impossibile verificare l'indirizzo e-mail: assicurati di aver cliccato il link nell'e-mail",
"VoIP is unsupported": "VoIP non supportato",
"You cannot place VoIP calls in this browser.": "Non puoi effettuare chiamate VoIP con questo browser.",
"You cannot place a call with yourself.": "Non puoi chiamare te stesso.",
@ -95,10 +95,10 @@
"The version of Riot.im": "La versione di Riot.im",
"Whether or not you're logged in (we don't record your user name)": "Se hai eseguito l'accesso o meno (non registriamo il tuo nome utente)",
"Your language of choice": "La lingua scelta",
"Which officially provided instance you are using, if any": "Quale istanza fornita ufficialmente stai usando, se presente",
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Se stai usando o meno la modalità Richtext dell'editor Rich Text",
"Your homeserver's URL": "L'URL del tuo homeserver",
"Your identity server's URL": "L'URL del tuo server di identità",
"Which officially provided instance you are using, if any": "Quale istanza ufficialmente fornita stai usando, se ne usi una",
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Se stai usando o meno la modalità richtext dell'editor con testo arricchito",
"Your homeserver's URL": "L'URL del tuo server home",
"Your identity server's URL": "L'URL del tuo server identità",
"Analytics": "Statistiche",
"The information being sent to us to help make Riot.im better includes:": "Le informazioni inviate per aiutarci a migliorare Riot.im includono:",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Se questa pagina include informazioni identificabili, come una stanza, utente o ID di gruppo, questi dati sono rimossi prima che vengano inviati al server.",
@ -1265,5 +1265,28 @@
"Click here to see older messages.": "Clicca qui per vedere i messaggi precedenti.",
"Failed to upgrade room": "Aggiornamento stanza fallito",
"The room upgrade could not be completed": "Non è stato possibile completare l'aggiornamento della stanza",
"Upgrade this room to version %(version)s": "Aggiorna questa stanza alla versione %(version)s"
"Upgrade this room to version %(version)s": "Aggiorna questa stanza alla versione %(version)s",
"Registration Required": "Registrazione necessaria",
"You need to register to do this. Would you like to register now?": "Devi registrarti per eseguire questa azione. Vuoi registrarti ora?",
"Unable to connect to Homeserver. Retrying...": "Impossibile connettersi all'homeserver. Riprovo...",
"Unable to query for supported registration methods": "Impossibile richiedere i metodi di registrazione supportati",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s ha aggiunto %(addedAddresses)s come indirizzo per questa stanza.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s ha aggiunto %(addedAddresses)s come indirizzi per questa stanza.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s ha rimosso %(removedAddresses)s tra gli indirizzi di questa stanza.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s ha rimosso %(removedAddresses)s tra gli indirizzi di questa stanza.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s ha aggiunto %(addedAddresses)s e rimosso %(removedAddresses)s tra gli indirizzi di questa stanza.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s ha messo %(address)s come indirizzo principale per questa stanza.",
"%(senderName)s removed the main address for this room.": "%(senderName)s ha rimosso l'indirizzo principale di questa stanza.",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Prima di inviare i log, devi <a>creare una segnalazione su GitHub</a> per descrivere il tuo problema.",
"What GitHub issue are these logs for?": "Per quale segnalazione su GitHub sono questi log?",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot ora usa da 3 a 5 volte meno memoria, caricando le informazioni degli altri utenti solo quando serve. Si prega di attendere mentre ci risincronizziamo con il server!",
"Updating Riot": "Aggiornamento di Riot",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>HTML per la pagina della tua comunità</h1>\n<p>\n Usa la descrizione estesa per introdurre i nuovi membri alla comunità, o distribuire alcuni <a href=\"foo\">link</a> importanti\n</p>\n<p>\n Puoi anche usare i tag 'img'\n</p>\n",
"Submit Debug Logs": "Invia log di debug",
"An email address is required to register on this homeserver.": "È necessario un indirizzo email per registrarsi in questo homeserver.",
"A phone number is required to register on this homeserver.": "È necessario un numero di telefono per registrarsi in questo homeserver.",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Hai usato Riot precedentemente su %(host)s con il caricamento lento dei membri attivato. In questa versione il caricamento lento è disattivato. Dato che la cache locale non è compatibile tra queste due impostazioni, Riot deve risincronizzare il tuo account.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Se l'altra versione di Riot è ancora aperta in un'altra scheda, chiudila perchè usare Riot nello stesso host con il caricamento lento sia attivato che disattivato può causare errori.",
"Incompatible local cache": "Cache locale non compatibile",
"Clear cache and resync": "Svuota cache e risincronizza"
}

315
src/i18n/strings/jbo.json Normal file
View file

@ -0,0 +1,315 @@
{
"This email address is already in use": ".i ca'o pilno le ve samymri",
"This phone number is already in use": ".i ca'o pilno le fonjudri",
"Failed to verify email address: make sure you clicked the link in the email": ".i na pu facki lo du'u xu kau do ponse le skami te mrilu .i ko birti lo du'u do pu skami cuxna le urli pe le se samymri",
"The platform you're on": "le ciste poi do pilno",
"The version of Riot.im": "le farvi tcini be la nu zunti",
"Whether or not you're logged in (we don't record your user name)": "lo du'u xu kau do cmisau to na vreji le do plicme toi",
"Your language of choice": "le se cuxna be fi lo'i bangu",
"Which officially provided instance you are using, if any": "le klesi poi ca'irselzau se sabji poi do pilno",
"Whether or not you're using the Richtext mode of the Rich Text Editor": "lo du'u xu kau do pilno la .markdaun. lo nu ciski",
"Your homeserver's URL": "le urli be le do samtcise'u",
"Your identity server's URL": "le urli be le do prenu datni samtcise'u",
"e.g. %(exampleValue)s": "mu'a zoi gy. %(exampleValue)s .gy.",
"Every page you use in the app": "ro lo pagbu poi do pilno pe le samtci",
"e.g. <CurrentPageURL>": "mu'a zoi urli. <CurrentPageURL> .urli",
"Your User Agent": "le datni be lo do kibyca'o",
"Your device resolution": "le ni vidnysle",
"Analytics": "lo se lanli datni",
"The information being sent to us to help make Riot.im better includes:": ".i ti liste lo datni poi se dunda fi lo favgau te zu'e lo nu xagzengau la nu zunti",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": ".i pu lo nu benji fi lo samtcise'u cu vimcu lo datni poi termi'u no'u mu'a lo termi'u be lo kumfa pe'a .o nai lo pilno .o nai lo girzu",
"Call Failed": ".i pu fliba lo nu fonjo'e",
"There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": ".i da poi no'e slabu samtciselse'u cu zvati le kumfa pe'a .i je lo nu lo drata cu tirna lo nu fonjo'e cu cumki lo nu do na'e lacri da",
"Review Devices": "za'u re'u viska lo liste be lo samtciselse'u",
"Call Anyway": "je'e fonjo'e",
"Answer Anyway": "je'e spuda",
"Call": "fonjo'e",
"Answer": "spuda",
"You are already in a call.": ".i do ca'o pu zvati lo nu fonjo'e",
"VoIP is unsupported": ".i na kakne tu'a la .voip.",
"You cannot place VoIP calls in this browser.": ".i le kibyca'o na kakne tu'a la .voip.",
"You cannot place a call with yourself.": ".i lo nu do fonjo'e do na cumki",
"Call in Progress": ".i ca'o nu fonjo'e",
"A call is currently being placed!": ".i pu'o nu fonjo'e",
"A call is already in progress!": ".i ca'o drata nu fonjo'e",
"Permission Required": ".i do notci lo nu curmi",
"You do not have permission to start a conference call in this room": ".i na curmi lo nu do co'a nunjmaji fonjo'e ne'i le kumfa pe'a",
"The file '%(fileName)s' failed to upload": ".i pu fliba lo nu kibdu'a la'o ly. %(fileName)s .ly.",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": ".i le datnyvei no'u la'o ly. %(fileName)s .ly. zmadu lo jimte be lo se kibdu'a bei lo ka barda be'o pe le samtcise'u",
"Upload Failed": ".i pu fliba lo nu kibdu'a",
"Failure to create room": ".i fliba lo nu zbasu lo kumfa pe'a",
"Call Timeout": ".i mutce temci lo nu co'a fonjo'e",
"The remote side failed to pick up": ".i lo se fonjo'e na pu spuda",
"Unable to capture screen": ".i na kakne lo nu benji lo vidvi be lo vidni",
"Existing Call": ".i ca'o pu fonjo'e",
"Could not connect to the integration server": ".i na kakne lo nu co'a samjo'e le jmina samtcise'u",
"A conference call could not be started because the intgrations server is not available": ".i na kakne lo nu co'a jmaji fonjo'e kei ri'a lo nu na kakne lo nu co'a samjo'e le jmina samtcise'u",
"Server may be unavailable, overloaded, or you hit a bug.": ".i la'a cu'i lo samtcise'u cu spofu gi'a mutce gunka .i ja samcfi",
"Send anyway": "je'e benji",
"Send": "benji",
"Sun": "nondei",
"Mon": "pavdei",
"Tue": "reldei",
"Wed": "cibdei",
"Thu": "vondei",
"Fri": "mumdei",
"Sat": "xavdei",
"Jan": "pa",
"Feb": "re",
"Mar": "ci",
"Apr": "vo",
"May": "mu",
"Jun": "xa",
"Jul": "ze",
"Aug": "bi",
"Sep": "so",
"Oct": "pa no",
"Nov": "pa pa",
"Dec": "pa re",
"PM": "su'i pa re",
"AM": "su'i no",
"%(weekDayName)s %(time)s": "de'i lo %(weekDayName)s ti'u li %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "de'i li %(day)s pi'e %(monthName)s noi %(weekDayName)s ge'u ti'u li %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "de'i li %(day)s pi'e %(monthName)s pi'e %(fullYear)s noi %(weekDayName)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "de'i li %(day)s pi'e %(monthName)s pi'e %(fullYear)s noi %(weekDayName)s ge'u ti'u li %(time)s",
"Who would you like to add to this community?": ".i do djica lo nu jmina ma le girzu",
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": ".i ju'i lo djuno be lo judri be lo girzu cu kakne lo nu viska lo liste be ro lo prenu poi se jmina do gy.",
"Invite new community members": "vi'ecpe lo prenu poi cnino le girzu",
"Name or matrix ID": "lo cmene .o nai lo judri be fi la nacmeimei",
"Invite to Community": "vi'ecpe fi le girzu",
"Which rooms would you like to add to this community?": ".i do djica lo nu jmina ma poi kumfa pe'a po'u le girzu",
"Show these rooms to non-members on the community page and room list?": ".i .au pei le kumfa cu gubni zvati le girzu pagbu .e le liste be lo'i kumfa pe'a",
"Add rooms to the community": "jmina lo kumfa pe'a le girzu",
"Room name or alias": "lo cmene ja datcme be lo kumfa",
"Add to community": "jmina fi le girzu",
"Failed to invite the following users to %(groupId)s:": "lo pilno poi fliba lo nu vi'ecpe ke'a la'o ny. %(groupId)s .ny.",
"Failed to invite users to community": ".i pu fliba lo nu vi'ecpe lo pilno le girzu",
"Failed to invite users to %(groupId)s": ".i pu fliba lo nu vi'ecpe lo pilno la'o ny. %(groupId)s .ny.",
"Failed to add the following rooms to %(groupId)s:": "lo kumfa pe'a poi fliba lo nu jmina ke'a la'o ny. %(groupId)s .ny.",
"Unnamed Room": "lo kumfa pe'a noi no da cmene",
"Riot does not have permission to send you notifications - please check your browser settings": ".i na curmi lo nu la nu zunti cu benji lo sajgau do .i .e'o do cipcta lo te cuxna pe le do kibyca'o",
"Riot was not given permission to send notifications - please try again": ".i na pu curmi lo nu la nu zunti cu benji lo sajgau .i .e'o do za'u re'u troci",
"Unable to enable Notifications": ".i na kakne lo nu co'a kakne lo nu benji lo sajgau",
"This email address was not found": ".i na pu facki fi le ve samymri",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": ".i za'a le ve samymri be fo do cu ckini no lo judri be fi la nacmeimei be'o pe le samtcise'u",
"Registration Required": ".i .ei do se cmeveigau",
"You need to register to do this. Would you like to register now?": ".i lo nu cmeveigau do sarcu ti .i do ca .au pei cmeveigau do",
"Register": "cmeveigau",
"Default": "lo zmiselcu'a",
"Restricted": "li so'u",
"Moderator": "li so'i",
"Admin": "li ro",
"Start a chat": "lo nu co'a tavla",
"Who would you like to communicate with?": ".i .au dai do tavla ma",
"Email, name or matrix ID": "lo ve samymri .o nai lo cmene .o nai lo judri be fi la nacmeimei",
"Start Chat": "co'a tavla",
"Invite new room members": "vi'ecpe lo cnino prenu",
"Who would you like to add to this room?": ".i .au dai do jmina ma le kumfa pe'a",
"Send Invites": "mrilu lo ve vi'ecpe",
"Power level must be positive integer.": ".i .ei lo ni vlipa cu kacna'u",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": ".i la'o ly. %(senderName)s .ly. gafygau %(powerLevelDiffText)s",
"Failed to change power level": ".i pu fliba lo nu gafygau lo ni vlipa",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "lo ni la'o ny. %(userId)s .ny. vlipa noi pu du %(fromPowerLevel)s ku %(toPowerLevel)s",
"Failed to invite user": ".i pu fliba lo nu vi'ecpe le pilno",
"Operation failed": ".i pu fliba",
"Failed to invite": ".i pu fliba lo nu vi'ecpe",
"Failed to invite the following users to the %(roomName)s room:": "lo pilno poi fliba lo nu vi'ecpe ke'a la'o ly. %(roomName)s .ly. noi kumfa pe'a",
"You need to be logged in.": ".i .ei do cmisau",
"You need to be able to invite users to do that.": ".i lo nu do kakne lo nu vi'ecpe lo pilno cu sarcu ta",
"Unable to create widget.": ".i na kakne lo nu zbasu lo uidje",
"Missing roomId.": ".i claxu lo judri be lo kumfa pe'a",
"Failed to send request.": ".i pu fliba lo nu benji lo ve cpedu",
"This room is not recognised.": ".i na sanji le kumfa pe'a",
"You are not in this room.": ".i do na zvati le kumfa pe'a",
"You do not have permission to do that in this room.": ".i ne'i le kumfa pe'a na curmi ta poi do troci",
"Missing room_id in request": ".i lo ve cpedu cu claxu lo judri be lo kumfa pe'a",
"Room %(roomId)s not visible": ".i na kakne lo nu viska la'o ly. %(roomId)s .ly. noi kumfa pe'a",
"Missing user_id in request": ".i lo ve cpedu cu claxu lo judri be lo pilno",
"Usage": "lo tadji be lo nu pilno",
"Searches DuckDuckGo for results": ".i sisku se pi'o la datkysisku",
"/ddg is not a command": "zoi ny. /ddg .ny. na nu minde",
"Changes your display nickname": ".i galfi le do cmene",
"Changes colour scheme of current room": ".i gafygau lo se skari be le kumfa pe'a",
"Sets the room topic": ".i ninga'igau lo se casnu pe le kumfa pe'a",
"Invites user with given id to current room": ".i vi'ecpe lo pilno poi se judri ti ku le kumfa pe'a",
"Joins room with given alias": ".i drata judri le kumfa pe'a",
"Leave room": "cliva le kumfa pe'a",
"Unrecognised room alias:": "lo drata judri poi na se sanji",
"Kicks user with given id": ".i rinka lo nu lo pilno poi se judri ti cu cliva",
"Bans user with given id": ".i rinka lo nu lo pilno poi se judri ti cu vitno cliva",
"Unbans user with given id": ".i xruti fo lo nu lo pilno poi se judri ti cu vitno cliva",
"Ignores a user, hiding their messages from you": ".i rinka lo nu no'e jundi lo pilno gi'e mipri lo notci be fi py. do",
"Ignored user": ".i do no'e jundi le pilno",
"You are now ignoring %(userId)s": ".i do ca no'e jundi la'o ny. %(userId)s .ny.",
"Stops ignoring a user, showing their messages going forward": ".i sisti lo nu no'e jundi lo pilno gi'e mipri lo notci be fi py. do",
"Unignored user": ".i do sisti lo nu no'e jundi le pilno",
"You are no longer ignoring %(userId)s": ".i do ca sisti lo nu no'e jundi la'o ny. %(userId)s .ny.",
"Define the power level of a user": ".i ninga'igau lo ni lo pilno cu vlipa",
"Deops user with given id": ".i xruti lo ni lo pilno poi se judri ti cu vlipa",
"Opens the Developer Tools dialog": ".i samymo'i lo favgau se pilno uidje",
"Verifies a user, device, and pubkey tuple": ".i xusra lo du'u do lacri lo pilno joi lo samtciselse'u joi lo gubni termifckiku",
"Unknown (user, device) pair:": "lo pilno ce'o lo samtciselse'u vu'o poi na te djuno",
"Device already verified!": ".i do ca'o pu lacri le samtciselse'u",
"WARNING: Device already verified, but keys do NOT MATCH!": ".i ju'i cai do ca'o pu lacri le samtciselse'u .i je ku'i lo termifckiku ba'e na mapti",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": ".i ju'i cai pu fliba lo nu lacri lo termifckiku .i zoi ny. %(fprint)s .ny. noi se ponse la'o ny. %(userId)s .ny. .e la'o ny. %(deviceId)s .ny. noi samtciselse'u cu termi'u termifckiku gi'e na mapti le termifckiku poi do dunda no'u zoi ny. %(fingerprint)s .ny. .i la'a cu'i lo drata ju'i prenu cu tcidu lo se mrilu be do",
"Verified key": "lo termifckiku poi se lacri",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": ".i lo termi'u termifckiku poi do dunda cu mapti lo termi'u termifckiku poi do te benji la'o ny. %(deviceId)s .ny. noi samtciselse'u po'e la'o ny. %(userId)s .ny. .i do co'a lacri le samtciselse'u",
"Displays action": ".i mrilu lo nu do gasnu",
"Forces the current outbound group session in an encrypted room to be discarded": ".i macnu vimcu lo ca barkla termifckiku gunma lo kumfa pe'a poi mifra",
"Unrecognised command:": "lo se minde poi na te djuno",
"Reason": "lo krinu",
"%(targetName)s accepted the invitation for %(displayName)s.": ".i la'o ly. %(targetName)s .ly. fitytu'i lo ve vi'ecpe be fi la'o ly. %(displayName)s .ly.",
"%(targetName)s accepted an invitation.": ".i la'o ly. %(targetName)s .ly. fitytu'i lo ve vi'ecpe",
"%(senderName)s requested a VoIP conference.": ".i la'o ly. %(senderName)s .ly. cpedu lo .voip. zei nunjmaji",
"%(senderName)s invited %(targetName)s.": ".i la'o ly. %(senderName)s .ly. vi'ecpe la'o ly. %(targetName)s .ly.",
"%(senderName)s banned %(targetName)s.": ".i la'o ly. %(senderName)s .ly. gasnu lo nu la'o ly. %(targetName)s .ly. vitno cliva",
"%(oldDisplayName)s changed their display name to %(displayName)s.": ".i la'o ly. %(oldDisplayName)s .ly. gafygau lo cmene be ri zoi ly. %(displayName)s .ly.",
"%(senderName)s set their display name to %(displayName)s.": ".i la'o ny. %(senderName)s .ny. jmina lo cmene be ri be'o no'u zoi ly. %(displayName)s .ly.",
"%(senderName)s removed their display name (%(oldDisplayName)s).": ".i la'o ny. %(senderName)s .ny. vimcu lo cmene be ri be'o no'u zoi ly. %(oldDisplayName)s .ly.",
"%(senderName)s removed their profile picture.": ".i la'o ly. %(senderName)s .ly. vimcu lo predatni pixra pe ri",
"%(senderName)s changed their profile picture.": ".i la'o ly. %(senderName)s .ly. gafygau lo predatni pixra pe ri",
"%(senderName)s set a profile picture.": ".i la'o ly. %(senderName)s .ly. jmina lo predatni pixra pe ri",
"VoIP conference started.": ".i co'a .voip. zei nunjmaji",
"%(targetName)s joined the room.": ".i la'o ly. %(targetName)s .ly. binxo lo cmima be le kumfa pe'a",
"VoIP conference finished.": ".i mo'u .voip. zei nunjmaji",
"%(targetName)s rejected the invitation.": ".i la'o ly. %(targetName)s .ly. fitytoltu'i lo ve vi'ecpe",
"%(targetName)s left the room.": ".i la'o ly. %(targetName)s .ly. cliva le kumfa pe'a",
"%(senderName)s unbanned %(targetName)s.": ".i la'o ly. %(senderName)s .ly. xruti fo lo nu la'o ly. %(targetName)s .ly. vitno cliva",
"%(senderName)s kicked %(targetName)s.": ".i la'o ly. %(senderName)s .ly. gasnu lo nu la'o ly. %(targetName)s .ly. cliva",
"%(senderName)s withdrew %(targetName)s's invitation.": ".i la'o ly. %(senderName)s .ly. lebna lo ve vi'ecpe be la'o ly. %(targetName)s .ly.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": ".i la'o ly. %(senderDisplayName)s .ly. gafygau lo se casnu pe le kumfa pe'a zoi ly. %(topic)s .ly.",
"%(senderDisplayName)s removed the room name.": ".i la'o ly. %(senderDisplayName)s .ly. vimcu lo cmene be le kumfa pe'a",
"%(senderDisplayName)s changed the room name to %(roomName)s.": ".i la'o ly. %(senderDisplayName)s .ly. gafygau lo cmene be le kumfa zoi ly. %(roomName)s .ly.",
"%(senderDisplayName)s sent an image.": ".i la'o ly. %(senderDisplayName)s .ly. mrilu lo pixra",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": ".i la'o ly. %(senderName)s .ly. jmina zoi ny. %(addedAddresses)s .ny. lo'i judri be le kumfa pe'a",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": ".i la'o ly. %(senderName)s .ly. jmina zoi ny. %(addedAddresses)s .ny. lo'i judri be le kumfa pe'a",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": ".i la'o ly. %(senderName)s .ly. vimcu zoi ny. %(removedAddresses)s .ny. lo'i judri be le kumfa pe'a",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": ".i la'o ly. %(senderName)s .ly. vimcu zoi ny. %(removedAddresses)s .ny. lo'i judri be le kumfa pe'a",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": ".i la'o ly. %(senderName)s .ly. jmina zoi ny. %(addedAddresses)s .ny. lo'i judri be le kumfa pe'a gi'e vimcu zoi ny. %(removedAddresses)s .ny. jy.",
"%(senderName)s set the main address for this room to %(address)s.": ".i la'o ly. %(senderName)s .ly. gafygau lo ralju cmene be le kumfa pe'a zoi ny. %(address)s .ny.",
"%(senderName)s removed the main address for this room.": ".i la'o ly. %(senderName)s .ly. vimcu lo ralju cmene be le kumfa pe'a",
"Someone": "da poi prenu",
"(not supported by this browser)": "to le do kibyca'o na kakne toi",
"%(senderName)s answered the call.": ".i la'o ly. %(senderName)s .ly. spuda lo nu fonjo'e",
"(could not connect media)": "to na kakne lo nu ganvi samjongau toi",
"(no answer)": "to na spuda toi",
"(unknown failure: %(reason)s)": "to na'e te djuno nu fliba fi'o ve skicu zoi gy. %(reason)s .gy. toi",
"%(senderName)s ended the call.": ".i la'o ly. %(senderName)s .ly. sisti lo nu fonjo'e",
"%(senderName)s placed a %(callType)s call.": ".i la'o ly. %(senderName)s .ly. co'a %(callType)s fonjo'e",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": ".i la'o ly. %(senderName)s .ly. vi'ecpe la'o ly. %(targetDisplayName)s .ly. le kumfa pe'a",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": ".i la'o ly. %(senderName)s .ly. gasnu lo nu ro lo cmima ka'e viska ro lo notci be ba lo mu'e cy. se vi'ecpe",
"%(senderName)s made future room history visible to all room members, from the point they joined.": ".i la'o ly. %(senderName)s .ly. gasnu lo nu ro lo cmima ka'e viska ro lo notci be ba lo mu'e cy. cmibi'o",
"%(senderName)s made future room history visible to all room members.": ".i la'o ly. %(senderName)s .ly. gasnu lo nu ro lo cmima ka'e viska ro lo ba notci",
"%(senderName)s made future room history visible to anyone.": ".i la'o ly. %(senderName)s .ly. gasnu lo nu ro lo prenu ka'e viska ro lo ba notci",
"%(senderName)s made future room history visible to unknown (%(visibility)s).": ".i la'o ly. %(senderName)s .ly. gasnu lo nu zo'e ka'e viska lo notci to cuxna zoi ny. %(visibility)s .ny. toi",
"%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": ".i gau la'o ly. %(senderName)s .ly. co'a mulno mifra fi la'o ny. %(algorithm)s .ny.",
"%(senderName)s changed the pinned messages for the room.": ".i la'o ly. %(senderName)s .ly. gafygau lo vitno notci pe le kumfa pe'a",
"%(widgetName)s widget modified by %(senderName)s": ".i la'o ly. %(senderName)s .ly. gafygau la'o ny. %(widgetName)s .ny. noi uidje",
"%(widgetName)s widget added by %(senderName)s": ".i la'o ly. %(senderName)s .ly. jmina la'o ny. %(widgetName)s .ny. noi uidje",
"%(widgetName)s widget removed by %(senderName)s": ".i la'o ly. %(senderName)s .ly. vimcu la'o ny. %(widgetName)s .ny. noi uidje",
"%(displayName)s is typing": ".i la'o ly. %(displayName)s .ly. ca'o ciska",
"%(names)s and %(count)s others are typing|other": ".i la'o ly. %(names)s .ly. .e %(count)s lo drata ca'o ciska",
"%(names)s and %(count)s others are typing|one": ".i la'o ly. %(names)s .ly. .e pa lo drata ca'o ciska",
"%(names)s and %(lastPerson)s are typing": ".i la'o ly. %(names)s .ly. .e la'o ly. %(lastPerson)s .ly. ca'o ciska",
"This homeserver has hit its Monthly Active User limit.": ".i le samtcise'u cu bancu lo masti jimte be ri bei lo ni ca'o pilno",
"This homeserver has exceeded one of its resource limits.": ".i le samtcise'u cu bancu pa lo jimte be ri",
"Please <a>contact your service administrator</a> to continue using the service.": ".i .e'o ko <a>tavla lo do te selfu admine</a> .i ja nai do djica lo nu ca'o pilno le te selfu",
"Unable to connect to Homeserver. Retrying...": ".i pu fliba lo nu samjo'e le samtcise'u .i za'u re'u ca'o troci",
"Your browser does not support the required cryptography extensions": ".i le do kibyca'o na kakne tu'a le te mifra ciste noi se nitcu",
"Not a valid Riot keyfile": ".i na'e drani ckiku datnyvei",
"Authentication check failed: incorrect password?": ".i pu fliba lo nu birti lo du'u curmi lo nu do jonse .i na'e drani xu japyvla",
"Sorry, your homeserver is too old to participate in this room.": ".i .uu le do samtcise'u cu dukse lo ka laldo ku ja'e lo du'u sy. na kakne lo nu pagbu le kumfa pe'a",
"Please contact your homeserver administrator.": ".i .e'o ko tavla lo admine be le samtcise'u",
"Failed to join room": ".i pu fliba lo nu cmibi'o le kumfa pe'a",
"Message Pinning": "lo du'u xu kau kakne lo nu mrilu lo vitno notci",
"Increase performance by only loading room members on first view": "lo du'u xu kau zenba lo ka sutra ku ta'i lo nu samymo'i lo cmima be lo kumfa pe'a ba po'o lo nu viska cy.",
"Disable Emoji suggestions while typing": "lo du'u xu kau na stidi lo pixra lerfu ca lo nu ciska",
"Use compact timeline layout": "lo du'u xu kau lo liste be lo notci cu tagji",
"Hide removed messages": "lo du'u xu kau mipri lo notci poi se vimcu",
"Hide join/leave messages (invites/kicks/bans unaffected)": "lo du'u xu kau mipri lo cmibi'o ja cliva notci to na mipri lo vi'ecpe ja gasnu bo cliva notci toi",
"Hide avatar changes": "lo du'u xu kau mipri lo nu galfi lo predatni pixra",
"Hide display name changes": "lo du'u xu kau mipri lo nu galfi lo cmene",
"Hide read receipts": "lo du'u xu kau mipri lo te benji datni",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "lo du'u xu kau lo tcika cu se tarmi mu'a lu ti'u li re pi'e ci no su'i pa re li'u",
"Always show message timestamps": "lo du'u xu kau do ro roi viska ka'e lo tcika be tu'a lo notci",
"Autoplay GIFs and videos": "lo du'u xu kau lo vidvi cu zmiku cfari",
"Always show encryption icons": "lo du'u xu kau jarco ro lo ka mifra",
"Enable automatic language detection for syntax highlighting": "lo du'u xu kau zmiku facki lo du'u ma kau bangu ku te zu'e lo nu skari ba'argau lo gensu'a",
"Hide avatars in user and room mentions": "lo du'u xu kau mipri lo pixra pe lo nu casnu lo pilno .a lo kumfa pe'a",
"Disable big emoji in chat": "lo du'u xu kau lo pixra lerfu poi cmalu cu basti lo pixra lerfu poi barda",
"Don't send typing notifications": "lo du'u xu kau na benji lo datni be lo nu ciska",
"Automatically replace plain text Emoji": "lo du'u xu kau zmiku basti lo cinmo lerpoi",
"Mirror local video feed": "lo du'u xu kau minra lo diklo vidvi",
"Disable Community Filter Panel": "lo du'u xu kau na viska le girzu cuxselgre uidje",
"Disable Peer-to-Peer for 1:1 calls": "lo du'u xu kau na sirji samjo'e ca lo nu pa da fonjo'e pa de",
"Send analytics data": "lo du'u xu kau benji lo se lanli datni",
"Never send encrypted messages to unverified devices from this device": "lo du'u xu kau no roi benji lo notci poi mifra ku lo samtciselse'u poi na'e lacri ku ti poi samtciselse'u",
"Never send encrypted messages to unverified devices in this room from this device": "lo du'u xu kau no roi benji lo notci poi mifra ku lo samtciselse'u poi na'e lacri poi zvati le kumfa pe'a ku'o ti poi samtciselse'u",
"Enable inline URL previews by default": "lo zmiselcu'a pe lo du'u xu kau zmiku purzga lo se urli",
"Enable URL previews for this room (only affects you)": "lo du'u xu kau do zmiku purzga lo se urli ne'i le kumfa pe'a",
"Enable URL previews by default for participants in this room": "lo zmiselcu'a pe lo du'u xu kau lo cmima be le kumfa pe'a cu zmiku purzga lo se urli",
"Room Colour": "lo se skari be le kumfa pe'a",
"Enable widget screenshots on supported widgets": "lo du'u xu kau kakne lo nu co'a pixra lo uidje kei lo nu kakne tu'a .ubu",
"Show empty room list headings": "lo du'u xu kau viska lo tcita be lo liste be lo kumfa pe'a be'o poi kunti ca lo nu cuxselgre",
"Collecting app version information": ".i ca'o crepu lo datni be lo favytcinymupli",
"Collecting logs": ".i ca'o crepu lo vreji",
"Uploading report": ".i ca'o kibdu'a lo datnynoi",
"Waiting for response from server": ".i ca'o denpa lo nu le samtcise'u cu spuda",
"Messages containing my display name": "lo notci poi vasru lo cmene be mi",
"Messages containing my user name": "lo notci poi vasru lo plicme be mi",
"Messages in one-to-one chats": "lo notci be fi pa lo prenu bei pa lo prenu",
"Messages in group chats": "lo notci pe lo girzu tavla",
"When I'm invited to a room": "lo nu vi'ecpe mi lo kumfa pe'a",
"Call invitation": "lo nu vi'ecpe mi lo nu fonjo'e",
"Messages sent by bot": "lo notci be fi lo sampre",
"Active call (%(roomName)s)": "le ca fonjo'e ne la'o ly. %(roomName)s .ly.",
"unknown caller": "lo fonjo'e noi na'e te djuno",
"Incoming voice call from %(name)s": ".i la'o ly. %(name)s .ly. ca'o snavi fonjo'e",
"Incoming video call from %(name)s": ".i la'o ly. %(name)s .ly. ca'o vidvi fonjo'e",
"Incoming call from %(name)s": ".i la'o ly. %(name)s .ly. ca'o fonjo'e",
"Decline": "fitytoltu'i",
"Accept": "fitytu'i",
"Error": "lo se srera",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": ".i pu mrilu fi lo se fonjudri be zoi fy. +%(msisdn)s .fy. fu la .symysys. .i .e'o ko ciska lo lacri lerpoi po le se mrilu",
"Incorrect verification code": ".i na'e drani ke lacri lerpoi",
"Enter Code": ".i ko ciska le lerpoi",
"Submit": "benji",
"Phone": "lo fonxa",
"Add phone number": "lo fonjudri",
"Add": "jmina",
"Failed to upload profile picture!": ".i pu fliba lo nu kibdu'a lo predatni pixra",
"No display name": ".i no da cmene",
"New passwords don't match": ".i le'i japyvla poi cnino na simxu lo nu mintu",
"Passwords can't be empty": ".i lu li'u .e'a nai japyvla",
"Warning!": ".i ju'i",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": ".i lo nu galfi lo japyvla cu rinka lo nu galfi ro lo termifckiku pe lo samtciselse'u kei .e lo nu na kakne lo nu tolmifygau .i ja do barbei lo do kumfa pe'a termifckiku gi'e ba nerbei ri .i ta'o le ti pruce ba zenba lo ka frili",
"Export E2E room keys": "barbei lo kumfa pe'a termifckiku",
"Continue": "",
"Do you want to set an email address?": ".i .au pei do jmina lo te samymri",
"Current password": "lo ca japyvla",
"Password": "lo japyvla",
"New Password": "lo japyvla poi cnino",
"Confirm password": "lo za'u re'u japyvla poi cnino",
"Change Password": "galfi lo japyvla",
"Your home server does not support device management.": ".i le do samtcise'u na kakne lo nu jitro lo samtciselse'u",
"Unable to load device list": ".i na kakne lo nu samymo'i lo liste be lo'i samtciselse'u",
"Authentication": "lo nu facki lo du'u do du ma kau",
"Delete %(count)s devices|other": "vimcu %(count)s lo samtciselse'u",
"Delete %(count)s devices|one": "vimcu le samtciselse'u",
"Device ID": "lo judri be lo samtciselse'u",
"Device Name": "lo cmene be lo samtciselse'u",
"Last seen": "lo ro re'u nu viska",
"Select devices": "lo du'u xu kau cuxna lo samtciselse'u",
"Failed to set display name": ".i pu fliba lo nu galfi lo cmene",
"Disable Notifications": "na sajgau",
"Enable Notifications": "sajgau",
"Error saving email notification preferences": ".i pu fliba lo nu co'a vreji lo se cuxna pe lo nu samymri",
"An error occurred whilst saving your email notification preferences.": ".i pu fliba lo nu co'a vreji lo se cuxna pe lo nu samymri sajgau",
"Keywords": "lo midvla",
"Enter keywords separated by a comma:": ".i ko ciska lo midvla ta'i lo nu sepli fi lo lerkoma",
"OK": "je'e",
"Failed to change settings": ".i pu fliba lo nu galfi lo se cuxna",
"Can't update user notification settings": ".i pu fliba lo nu galfi lo se cuxna pe lo nu sajgau",
"Failed to update keywords": ".i pu fliba lo nu galfi lo midvla",
"Messages containing <span>keywords</span>": "lo notci poi vasru <span>lo midvla</span>"
}

View file

@ -541,7 +541,7 @@
"File to import": "가져올 파일",
"You must join the room to see its files": "파일을 보려면 방에 들어가야만 해요",
"Reject all %(invitedRooms)s invites": "모든 %(invitedRooms)s의 초대를 거절하기",
"Start new chat": "새 대화하기",
"Start new chat": "새 대화 시작하기",
"Failed to invite": "초대하지 못했습니다.",
"Failed to invite user": "사용자를 초대하지 못했습니다.",
"Failed to invite the following users to the %(roomName)s room:": "다음 사용자들을 %(roomName)s 방으로 초대하지 못했습니다:",
@ -902,7 +902,7 @@
"%(senderName)s sent a video": "%(senderName)s가 비디오를 보냈습니다",
"%(senderName)s uploaded a file": "%(senderName)s가 파일을 보냈습니다",
"Key request sent.": "키 요청을 보냈습니다.",
"If your other devices do not have the key for this message you will not be able to decrypt them.": "다른 기기에",
"If your other devices do not have the key for this message you will not be able to decrypt them.": "당신의 다른 기기에 이 메시지를 읽기 위한 키가 없다면 메시지를 해독할 수 없을 겁니다.",
"Encrypting": "암호화 중",
"Encrypted, not sent": "암호화 됨, 보내지지 않음",
"Disinvite this user?": "이 사용자에 대한 초대를 취소할까요?",
@ -975,15 +975,15 @@
"You are trying to access a room.": "방에 접근하고 있습니다.",
"To change the room's avatar, you must be a": "방의 아바타를 바꾸려면, -여야 합니다",
"To change the room's name, you must be a": "방 이름을 바꾸려면, -여야 합니다.",
"To change the room's main address, you must be a": "방의 인 주소를 바꾸려면, -여야 합니다.",
"Members only (since they joined)": "구성원만(??한 시점부터)",
"To change the room's main address, you must be a": "방의 인 주소를 바꾸려면, -여야 합니다.",
"Members only (since they joined)": "구성원만(구성원들이 참여한 시점부터)",
"%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)s님이 들어왔습니다",
"%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)s님이 %(count)s번 들어왔습니다",
"%(oneUser)sjoined %(count)s times|other": "%(oneUser)s님이 %(count)s번 들어왔습니다",
"%(oneUser)sjoined %(count)s times|one": "%(oneUser)s님이 들어왔습니다",
"%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)s님이 %(count)s번 들어왔다가 나갔습니다",
"%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)s님이 들어왔다가 나갔습니다",
"%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)님이 %(count)s번 들어왔다가 나갔습니다",
"%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)s님이 %(count)s번 들어왔다가 나갔습니다",
"%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)s님이 들어왔다가 나갔습니다",
"%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)s님이 나갔다가 다시 들어왔습니다",
"%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)s님이 %(count)s번 나갔다가 다시 들어왔습니다",
@ -1077,7 +1077,7 @@
"Status.im theme": "Status.im식 테마",
"A text message has been sent to %(msisdn)s": "%(msisdn)s님에게 문자 메시지를 보냈습니다.",
"Something went wrong when trying to get your communities.": "커뮤니티를 받는 중에 뭔가 잘못됐습니다.",
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. This will use a cookie.": "<UsageDataLink>익명으로 사용 데이터</UsageDataLink>를 보내 Riot의 발전을 도와주세요. 이 과정에서 쿠키를 사용합니다.",
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. This will use a cookie.": "<UsageDataLink>익명의 이용자 데이터</UsageDataLink>를 보내 Riot.im의 발전을 도와주세요. 이 과정에서 쿠키를 사용합니다.",
"Allow": "허가하기",
"Visible to everyone": "모두에게 보여짐",
"Only visible to community members": "커뮤니티 구성원에게만 보여짐",
@ -1185,5 +1185,55 @@
"Add a Room": "방 추가하기",
"Add users to the community summary": "커뮤니티 요약에 사용자 추가하기",
"Who would you like to add to this summary?": "이 요약에 누구를 추가하고 싶으세요?",
"Link to most recent message": "가장 최근 메시지로 링크 걸기"
"Link to most recent message": "가장 최근 메시지로 링크 걸기",
"Registration Required": "계정 등록이 필요합니다.",
"You need to register to do this. Would you like to register now?": "계정을 등록해야합니다. 지금 계정을 만드시겠습니까?",
"This homeserver has hit its Monthly Active User limit.": "이 홈서버는 월간 활성 이용자수 한계에 도달했습니다.",
"Please <a>contact your service administrator</a> to continue using the service.": "서비스를 계속 사용하려면 <a>서비스 관리자에게 연락<a>하세요.",
"Unable to connect to Homeserver. Retrying...": "홈서버에 연결할 수 없습니다. 다시 시도하는 중...",
"Please contact your homeserver administrator.": "홈서버 관리자에게 연락하세요.",
"Increase performance by only loading room members on first view": "최초 접속 시의 방 인원만 불러와 성능 향상",
"This room has been replaced and is no longer active.": "이 방은 대체되었으며 더 사용할 수 없습니다.",
"The conversation continues here.": "이 대화는 여기서 이어가세요.",
"System Alerts": "시스템 알림",
"Upgrade room to version %(ver)s": "%(ver)s 버전으로 방을 업그레이드",
"Members only (since the point in time of selecting this option)": "구성원만(이 설정을 선택한 시점부터)",
"Members only (since they were invited)": "구성원만(구성원이 초대받은 시점부터)",
"Room version number: ": "방 버전 넘버: ",
"There is a known vulnerability affecting this room.": "이 방에 영향을 미치는 알려진 취약점이 있습니다.",
"Only room administrators will see this warning": "방 관리자만이 이 경고를 볼 수 있습니다.",
"This room is a continuation of another conversation.": "이 방은 다른 대화방의 연장선입니다.",
"Click here to see older messages.": "여길 눌러 오래된 메시지를 보세요.",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "로봇 확인은 현재 PC에서는 사용할 수 없습니다 - <a>웹 브라우저<a>를 사용해주세요.",
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. This will use a cookie (please see our <PolicyLink>Cookie Policy</PolicyLink>).": "<UsageDataLink>익명의 이용자 데이터</UsageDataLink>를 보내 Riot.im의 발전을 도와주세요. 이 과정에서 쿠키를 사용합니다 (우리의 <PolicyLink>쿠키 정책</PolicyLink>을 살펴보세요).",
"This homeserver has hit its Monthly Active User limit so <b>some users will not be able to log in</b>.": "이 홈서버는 월간 활성 이용자수 한계에 도달했기 때문에 <b>일부 유저는 로그인할 수 없습니다</b>.",
"Do you want to load widget from URL:": "URL에서 위젯을 불러오시겠습니까:",
"Revoke widget access": "위젯 접속 거부",
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
"%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)s님이 떠났으며 %(count)s 번 다시 참여했습니다.",
"<a>In reply to</a> <pill>": "<a>답장하기</a> <pill>",
"Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)": "계정을 비활성화한다면 보냈던 모든 메시지는 잊어버리세요 (<b>경고:</b> 이후 이용자들은 불완전한 대화 목록을 볼 수 있을 겁니다)",
"Explore Account Data": "계정 자료 탐색하기",
"Updating Riot": "Riot 업데이트중",
"Upgrade this room to version %(version)s": "이 방을 %(version)s 버전으로 업그레이드",
"Upgrade Room Version": "방 버전 업그레이드",
"Create a new room with the same name, description and avatar": "이름, 설명, 아바타가 같은 새 방 만들기",
"Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "이전 버전의 방에서 말하는 이용자를 중단시키고, 새 방으로 이동하라는 메시지를 표시합니다.",
"Put a link back to the old room at the start of the new room so people can see old messages": "사람들이 오래된 메시지를 볼 수 있게 새 방의 시작 부분에 오래된 방으로 가는 링크를 놓습니다.",
"Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "브라우저 저장소를 청소한다면 문제가 해결될 수도 있지만, 암호하된 대화 기록을 읽을 수 없게 됩니다.",
"Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "이용자와 방을 같이 묶는 커뮤니티를 만들어보세요! Matrix 세계에서 당신의 공간을 표시하는 사용자정의 홈페이지도 만드세요.",
"%(count)s Members|other": "",
"%(count)s Members|one": "",
"Invite to this community": "이 커뮤니티에 초대하기",
"You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "<consentLink>우리의 약관</consentLink>을 읽고 동의하시기 전까지는 메시지를 보낼 수 없습니다.",
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.": "이 홈서버가 월간 이용자수 한계에 도달했기 때문에 메시지를 보낼 수 없었습니다. 서비스를 계속 이용하려면 <a>서비스 관리자에게 연락하세요</a>.",
"%(count)s of your messages have not been sent.|one": "메시지가 보내지지 않았습니다.",
"%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.|other": "지금 <resendText>전부 다시보내기</resendText> or <cancelText>전부 취소하기</cancelText>. 각 메시지를 골라 다시 보내거나 취소할 수도 있습니다.",
"%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.|one": "지금 <resendText>메시지 다시보내기</resendText> 혹은 <cancelText>메시지 취소하기</cancelText>.",
"Submit Debug Logs": "디버그 로그 제출",
"No Audio Outputs detected": "오디오 출력을 감지하지 못했습니다.",
"Audio Output": "오디오 출력",
"Please <a>contact your service administrator</a> to continue using this service.": "서비스를 계속 이용하려면 <a>서비스 관리자에게 연락하세요</a>.",
"An email address is required to register on this homeserver.": "이 홈서버에 등록하려면 이메일 주소가 필요합니다.",
"A phone number is required to register on this homeserver.": "이 홈서버에 등록하려면 전화번호가 필요합니다."
}

View file

@ -738,9 +738,9 @@
"No results": "Jokių rezultatų",
"Delete": "Ištrinti",
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
"%(oneUser)schanged their name %(count)s times|one": "%(oneUser)s pasikeitė savo vardą",
"%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)s pasikeitė savo avatarą",
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s pasikeitė savo avatarą",
"%(oneUser)schanged their name %(count)s times|one": "%(oneUser)s pasikeitė vardą",
"%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)s pasikeitė avatarą",
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s pasikeitė avatarą",
"collapse": "suskleisti",
"expand": "išskleisti",
"Room directory": "Kambarių katalogas",
@ -782,5 +782,95 @@
"Send Logs": "Siųsti žurnalus",
"Refresh": "Įkelti iš naujo",
"Unable to restore session": "Nepavyko atkurti seanso",
"Invalid Email Address": "Neteisingas el. pašto adresas"
"Invalid Email Address": "Neteisingas el. pašto adresas",
"You cannot place VoIP calls in this browser.": "Negalite inicijuoti VoIP skambučių šioje naršyklėje.",
"You cannot place a call with yourself.": "Negalite skambinti patys sau.",
"Registration Required": "Reikalinga registracija",
"You need to register to do this. Would you like to register now?": "Norėdami tai atlikti, turite užsiregistruoti. Ar norėtumėte užsiregistruoti dabar?",
"Missing roomId.": "Trūksta kambario ID (roomId).",
"Leave room": "Išeiti iš kambario",
"(could not connect media)": "(nepavyko prijungti medijos)",
"This homeserver has hit its Monthly Active User limit.": "Šis namų serveris pasiekė savo mėnesinį aktyvių naudotojų limitą.",
"This homeserver has exceeded one of its resource limits.": "Šis namų serveris viršijo vieno iš savo išteklių limitą.",
"Unable to connect to Homeserver. Retrying...": "Nepavyksta prisijungti prie namų serverio. Bandoma iš naujo...",
"Hide avatar changes": "Slėpti avatarų pasikeitimus",
"Disable Community Filter Panel": "Išjungti bendruomenės filtro skydelį",
"Enable widget screenshots on supported widgets": "Palaikomuose valdikliuose įjungti valdiklių ekrano kopijas",
"Export E2E room keys": "Eksportuoti E2E kambario raktus",
"Select devices": "Pasirinkti įrenginius",
"Last seen": "Paskutinį kartą matytas",
"Unignore": "Nustoti nepaisyti",
"and %(count)s others...|other": "ir %(count)s kitų...",
"and %(count)s others...|one": "ir dar vienas...",
"Mention": "Paminėti",
"At this time it is not possible to reply with a file so this will be sent without being a reply.": "Šiuo metu neįmanoma atsakyti failu, taigi, šis failas bus išsiųstas ne atsakymo pavidalu.",
"This room has been replaced and is no longer active.": "Šis kambarys buvo pakeistas ir daugiau nebėra aktyvus.",
"You do not have permission to post to this room": "Jūs neturite leidimų rašyti šiame kambaryje",
"Turn Markdown on": "Įjungti Markdown",
"Turn Markdown off": "Išjungti Markdown",
"Markdown is disabled": "Markdown yra išjungta",
"Markdown is enabled": "Markdown yra įjungta",
"Drop here to favourite": "Vilkite čia, norėdami pridėti į mėgstamus",
"Drop here to restore": "Vilkite čia, norėdami atkurti",
"System Alerts": "Sistemos įspėjimai",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Norėtumėte <acceptText>priimti</acceptText> ar <declineText>atmesti</declineText> šį pakvietimą?",
"This is a preview of this room. Room interactions have been disabled": "Tai yra kambario peržiūra. Kambario sąveikos yra išjungtos",
"To change the room's avatar, you must be a": "Norėdami pakeisti kambario avatarą, privalote būti",
"Failed to unban": "Nepavyko atblokuoti",
"Privacy warning": "Privatumo įspėjimas",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Šifruotos žinutės nebus matomos kliento programose, kurios kol kas neįgyvendino šifravimo",
"Tagged as: ": "Pažymėtas kaip: ",
"To link to a room it must have <a>an address</a>.": "Norint susieti kambarį, jis privalo turėti <a>adresą</a>.",
"Internal room ID: ": "Vidinis kambario ID: ",
"not specified": "nenurodyta",
"not set": "nenustatyta",
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "Šifruotuose kambariuose, tokiuose kaip šis, URL nuorodų peržiūra pagal numatymą yra išjungta, kad būtų užtikrinta, jog jūsų namų serveris (kuriame yra generuojamos peržiūros) negalės rinkti informacijos apie šiame kambaryje matomas nuorodas.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s pakeitė %(roomName)s avatarą",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s pašalino kambario avatarą.",
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s pakeitė kambario avatarą į <img/>",
"Removed or unknown message type": "Žinutė pašalinta arba yra nežinomo tipo",
"Username on %(hs)s": "Naudotojo vardas ties %(hs)s",
"Filter community members": "Filtruoti bendruomenės dalyvius",
"Removing a room from the community will also remove it from the community page.": "Pašalinus kambarį iš bendruomenės, taip pat pašalins jį iš bendruomenės puslapio.",
"Filter community rooms": "Filtruoti bendruomenės kambarius",
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. This will use a cookie (please see our <PolicyLink>Cookie Policy</PolicyLink>).": "Padėkite patobulinti Riot.im, siųsdami <UsageDataLink>anoniminius naudojimosi duomenis</UsageDataLink>. Tai panaudos slapuką (žiūrėkite mūsų <PolicyLink>Slapukų politiką</PolicyLink>).",
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. This will use a cookie.": "Padėkite patobulinti Riot.im, siųsdami <UsageDataLink>anoniminius naudojimosi duomenis</UsageDataLink>. Tai panaudos slapuką.",
"Please <a>contact your service administrator</a> to get this limit increased.": "Norėdami padidinti šį limitą, <a>susisiekite su savo paslaugų administratoriumi</a>.",
"This homeserver has hit its Monthly Active User limit so <b>some users will not be able to log in</b>.": "Šis namų serveris pasiekė savo mėnesinį aktyvių naudotojų limitą, taigi, <b>kai kurie naudotojai negalės prisijungti</b>.",
"This homeserver has exceeded one of its resource limits so <b>some users will not be able to log in</b>.": "Šis namų serveris viršijo vieno iš savo išteklių limitą, taigi, <b>kai kurie naudotojai negalės prisijungti</b>.",
"An error ocurred whilst trying to remove the widget from the room": "Įvyko klaida, bandant pašalinti valdiklį iš kambario",
"Blacklist": "Įtraukti į juodąjį sąrašą",
"Unblacklist": "Pašalinti iš juodojo sąrašo",
"Verify...": "Patvirtinti...",
"Communities": "Bendruomenės",
"Home": "Pradžia",
"%(severalUsers)sleft %(count)s times|other": "%(severalUsers)s išėjo %(count)s kartų(-us)",
"%(severalUsers)sleft %(count)s times|one": "%(severalUsers)s išėjo",
"%(oneUser)sleft %(count)s times|other": "%(oneUser)s išėjo %(count)s kartų(-us)",
"%(oneUser)sleft %(count)s times|one": "%(oneUser)s išėjo",
"%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)s pasikeitė vardus",
"%(oneUser)schanged their name %(count)s times|other": "%(oneUser)s pasikeitė vardą %(count)s kartų(-us)",
"%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)s pasikeitė avatarą %(count)s kartų(-us)",
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s pasikeitė avatarą %(count)s kartų(-us)",
"And %(count)s more...|other": "Ir dar %(count)s...",
"Existing Call": "Esamas skambutis",
"A call is already in progress!": "Skambutis jau yra inicijuojamas!",
"Default": "Numatytasis",
"Restricted": "Apribotas",
"Moderator": "Moderatorius",
"Ignores a user, hiding their messages from you": "Nepaiso naudotojo, paslepiant nuo jūsų jo žinutes",
"Stops ignoring a user, showing their messages going forward": "Sustabdo naudotojo nepaisymą, rodant jo tolimesnes žinutes",
"Hide avatars in user and room mentions": "Slėpti avatarus naudotojų ir kambarių paminėjimuose",
"Revoke Moderator": "Panaikinti moderatorių",
"deleted": "perbrauktas",
"underlined": "pabrauktas",
"inline-code": "įterptas kodas",
"block-quote": "citatos blokas",
"bulleted-list": "suženklintasis sąrašas",
"numbered-list": "sąrašas su numeriais",
"Invites": "Pakvietimai",
"You have no historical rooms": "Jūs neturite istorinių kambarių",
"Historical": "Istoriniai",
"Every page you use in the app": "Kiekvienas puslapis, kurį naudoji programoje",
"Call Timeout": "Skambučio laikas baigėsi"
}

View file

@ -390,7 +390,7 @@
"Send a reply (unencrypted)…": "Send eit svar (ikkje-kryptert)…",
"Send an encrypted message…": "Send ei kryptert melding…",
"Send a message (unencrypted)…": "Send ei melding (ikkje-kryptert)…",
"You do not have permission to post to this room": "Du har ikkje tillating til å sende meldingar i dette rommet",
"You do not have permission to post to this room": "Du har ikkje tillating til å senda meldingar i dette rommet",
"Turn Markdown on": "Skru Mardown på",
"Turn Markdown off": "Skru Markdown av",
"Hide Text Formatting Toolbar": "Gøym Tekstformverktøylinje",
@ -438,7 +438,7 @@
"Share room": "Del rom",
"Drop here to favourite": "Slepp her for å gjera til yndling",
"Drop here to restore": "Slepp her for å gjenoppretta",
"Drop here to demote": "Slepp her for å senke i høgd",
"Drop here to demote": "Slepp her for å senka i høgd",
"Press <StartChatButton> to start a chat with someone": "Trykk på <StartChatButton> for å starta ei samtale med nokon",
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "Du er enno ikkje i eit rom! Trykk på <CreateRoomButton> for å laga eit rom eller <RoomDirectoryButton> for å sjå gjennom utvalet",
"Community Invites": "Samfunnsinnbydingar",
@ -640,7 +640,7 @@
"Display your community flair in rooms configured to show it.": "Vis samfunnssærpreget ditt i rom som er stilt inn til å visa det.",
"You're not currently a member of any communities.": "Du er for augeblunket ikkje medlem i nokre samfunn.",
"Yes, I want to help!": "Ja, eg vil vera til nytte!",
"You are not receiving desktop notifications": "Du fær ikkje nokre skrivebordsvarsel",
"You are not receiving desktop notifications": "Du fær ikkje skrivebordsvarsel",
"Enable them now": "Skru dei på no",
"What's New": "Kva er nytt",
"Update": "Oppdatering",

View file

@ -1257,5 +1257,26 @@
"Create a new room with the same name, description and avatar": "Vznikne nová miestnosť s rovnakým názvom, témou a obrázkom",
"Update any local room aliases to point to the new room": "Všetky lokálne aliasy pôvodnej miestnosti sa aktualizujú tak, aby ukazovali na novú miestnosť",
"Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "V pôvodnej miestnosti bude zverejnené odporúčanie prejsť do novej miestnosti a posielanie do pôvodnej miestnosti bude zakázané pre všetkých používateľov",
"Put a link back to the old room at the start of the new room so people can see old messages": "História novej miestnosti sa začne odkazom do pôvodnej miestnosti, aby si členovia vedeli zobraziť staršie správy"
"Put a link back to the old room at the start of the new room so people can see old messages": "História novej miestnosti sa začne odkazom do pôvodnej miestnosti, aby si členovia vedeli zobraziť staršie správy",
"Registration Required": "Vyžaduje sa registrácia",
"You need to register to do this. Would you like to register now?": "Aby ste mohli uskutočniť túto akciu, musíte sa zaregistrovať. Chcete teraz spustiť registráciu?",
"Forces the current outbound group session in an encrypted room to be discarded": "Vynúti zabudnutie odchádzajúcej skupinovej relácii v šifrovanej miestnosti",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s pridal adresy %(addedAddresses)s do tejto miestnosti.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s pridal adresu %(addedAddresses)s do tejto miestnosti.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s odstránil adresy %(removedAddresses)s z tejto miestnosti.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s odstránil adresu %(removedAddresses)s z tejto miestnosti.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s pridal %(addedAddresses)s a odstránil %(removedAddresses)s z tejto miestnosti.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s nastavil hlavnú adresu tejto miestnosti %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s odstránil hlavnú adresu tejto miestnosti.",
"Unable to connect to Homeserver. Retrying...": "Nie je možné sa pripojiť k domovskému serveru. Prebieha pokus o opetovné pripojenie...",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Pred tým, než odošlete záznamy, musíte <a>nahlásiť váš problém na GitHub</a>. Uvedte prosím podrobný popis.",
"What GitHub issue are these logs for?": "Pre ktoré hlásenie GitHub sú tieto záznamy?",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot teraz vyžaduje 3-5× menej pamäte, pretože informácie o ostatných používateľoch načítava len podľa potreby. Prosím počkajte na dokončenie synchronizácie so serverom!",
"Updating Riot": "Prebieha aktualizácia Riot",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>Obsah vo formáte HTML pre vašu stránku komunity</h1>\n<p>\n Do poľa dlhý popis zadajte text, ktorým komunitu predstavíte novým členom, alebo ich\n na nejaké dôležité <a href=\"foo\">odkazy</a>\n</p>\n<p>\n Môžete tiež pridať obrázky použitím značiek 'img'\n</p>\n",
"Submit Debug Logs": "Odoslať ladiace záznamy",
"Legal": "Právne",
"Unable to query for supported registration methods": "Nie je možné vyžiadať podporované metódy registrácie",
"An email address is required to register on this homeserver.": "Na registráciu na tomto domovskom servery je vyžadovaná emailová adresa.",
"A phone number is required to register on this homeserver.": "Na registráciu na tomto domovskom servery je vyžadované telefónne číslo."
}

View file

@ -294,5 +294,6 @@
"Collapse panel": "Tkurre panelin",
"With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "Me shfletuesin tuaj të tanishëm, pamja dhe ndjesitë nga aplikacioni mund të jenë plotësisht të pasakta, dhe disa nga ose krejt veçoritë të mos funksionojnë. Nëse doni ta provoni sido qoftë, mund të vazhdoni, por mos u ankoni për çfarëdo problemesh që mund të hasni!",
"Checking for an update...": "Po kontrollohet për një përditësim…",
"There are advanced notifications which are not shown here": "Ka njoftime të thelluara që nuk shfaqen këtu"
"There are advanced notifications which are not shown here": "Ka njoftime të thelluara që nuk shfaqen këtu",
"Show empty room list headings": "Shfaqi emrat e listave të zbrazëta dhomash"
}

View file

@ -52,7 +52,7 @@
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s tog bort rummets namn.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s bytte rummets ämne till \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Ändringar till vem som kan läsa meddelandehistorik tillämpas endast till framtida meddelanden i det här rummet",
"Changes your display nickname": "Byter ditt synliga namn",
"Changes your display nickname": "Ändrar ditt visningsnamn",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Om du byter lösenord kommer för tillfället alla krypteringsnycklar på alla enheter att nollställas, vilket gör all krypterad meddelandehistorik omöjligt att läsa, om du inte först exporterar rumsnycklarna och sedan importerar dem efteråt. I framtiden kommer det här att förbättras.",
"Claimed Ed25519 fingerprint key": "Påstådd Ed25519-fingeravtrycksnyckel",
"Clear Cache and Reload": "Töm cache och ladda om",
@ -96,7 +96,7 @@
"Devices will not yet be able to decrypt history from before they joined the room": "Enheter kan inte ännu dekryptera meddelandehistorik från före de gick med i rummet",
"Direct chats": "Direkt-chattar",
"Disinvite": "Häv inbjudan",
"Display name": "Namn",
"Display name": "Visningsnamn",
"Displays action": "Visar åtgärd",
"Don't send typing notifications": "Skicka inte \"skriver\"-status",
"Download %(text)s": "Ladda ner %(text)s",
@ -135,7 +135,7 @@
"Failed to send email": "Det gick inte att skicka epost",
"Failed to send request.": "Det gick inte att sända begäran.",
"Failed to set avatar.": "Misslyckades med att ange avatar.",
"Failed to set display name": "Det gick inte att sätta namnet",
"Failed to set display name": "Det gick inte att ange visningsnamn",
"Failed to set up conference call": "Det gick inte att starta konferenssamtalet",
"Failed to toggle moderator status": "Det gick inte att växla moderator-status",
"Failed to unban": "Det gick inte att avbanna",
@ -462,7 +462,7 @@
"The Home Server may be too old to support third party networks": "Hemservern kan vara för gammal för stöda tredje parters nätverk",
"Noisy": "Högljudd",
"Room not found": "Rummet hittades inte",
"Messages containing my display name": "Meddelanden som innehåller mitt namn",
"Messages containing my display name": "Meddelanden som innehåller mitt visningsnamn",
"Messages in one-to-one chats": "Meddelanden i privata chattar",
"Unavailable": "Otillgänglig",
"View Decrypted Source": "Visa dekrypterad källa",
@ -1262,5 +1262,10 @@
"Create a new room with the same name, description and avatar": "Skapa ett nytt rum med samma namn, beskrivning och avatar",
"Update any local room aliases to point to the new room": "Uppdatera lokala rumsalias att peka på det nya rummet",
"Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Hindra användare från att prata i den gamla rumsversionen och posta ett meddelande som rekommenderar användare att flytta till det nya rummet",
"Put a link back to the old room at the start of the new room so people can see old messages": "Sätta en länk tillbaka till det gamla rummet i början av det nya rummet så att folk kan se gamla meddelanden"
"Put a link back to the old room at the start of the new room so people can see old messages": "Sätta en länk tillbaka till det gamla rummet i början av det nya rummet så att folk kan se gamla meddelanden",
"Registration Required": "Registrering krävs",
"You need to register to do this. Would you like to register now?": "Du måste registrera dig för att göra detta. Vill du registrera dig nu?",
"Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella utgående gruppsessionen i ett krypterat rum att överges",
"Unable to connect to Homeserver. Retrying...": "Det gick inte att ansluta till hemserver. Försöker igen ...",
"Unable to query for supported registration methods": "Det gick inte att hämta stödda registreringsmetoder"
}

View file

@ -26,7 +26,7 @@
"Enable encryption": "启用加密",
"Encrypted messages will not be visible on clients that do not yet implement encryption": "不支持加密的客户端将看不到加密的消息",
"Encrypted room": "加密聊天室",
"%(senderName)s ended the call.": "%(senderName)s 结束了通话。.",
"%(senderName)s ended the call.": "%(senderName)s 结束了通话。",
"End-to-end encryption information": "端到端加密信息",
"End-to-end encryption is in beta and may not be reliable": "端到端加密现为 beta 版,不一定可靠",
"Enter Code": "输入验证码",
@ -48,7 +48,7 @@
"Failed to save settings": "保存设置失败",
"Failed to send email": "发送邮件失败",
"Failed to send request.": "请求发送失败。",
"Failed to set avatar.": "设置头像失败。.",
"Failed to set avatar.": "设置头像失败。",
"Failed to set display name": "设置昵称失败",
"Failed to set up conference call": "无法启动群组通话",
"Failed to toggle moderator status": "无法切换管理员权限",
@ -62,10 +62,10 @@
"Filter room members": "过滤聊天室成员",
"Forget room": "忘记聊天室",
"Forgot your password?": "忘记密码?",
"For security, this session has been signed out. Please sign in again.": "出于安全考虑,此会话已被注销。请重新登录。.",
"For security, this session has been signed out. Please sign in again.": "出于安全考虑,此会话已被注销。请重新登录。",
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "出于安全考虑,用户注销时会清除浏览器里的端到端加密密钥。如果你想要下次登录 Riot 时能解密过去的聊天记录,请导出你的聊天室密钥。",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s 从 %(fromPowerLevel)s 变为 %(toPowerLevel)s",
"Guests cannot join this room even if explicitly invited.": "游客不能加入此聊天室,即使有人主动邀请。.",
"Guests cannot join this room even if explicitly invited.": "即使有人主动邀请,游客也不能加入此聊天室。",
"Hangup": "挂断",
"Hide read receipts": "隐藏已读回执",
"Hide Text Formatting Toolbar": "隐藏格式工具栏",
@ -76,15 +76,15 @@
"Import E2E room keys": "导入聊天室端到端加密密钥",
"Incorrect verification code": "验证码错误",
"Interface Language": "界面语言",
"Invalid alias format": "别名格式错误",
"Invalid alias format": "别名格式无效",
"Invalid address format": "地址格式错误",
"Invalid Email Address": "邮箱地址格式错误",
"Invalid file%(extra)s": "非法文件%(extra)s",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "重设密码会导致所有设备上的端到端加密密钥被重置,使得加密的聊天记录不可读,除非你事先导出密钥,修改密码后再导入。此问题将来会得到改善。.",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "重设密码会导致所有设备上的端到端加密密钥被重置,使得加密的聊天记录不可读,除非你事先导出密钥,修改密码后再导入。此问题将来会得到改善。",
"Return to login screen": "返回登录页面",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot 没有通知发送权限 - 请检查您的浏览器设置",
"Riot was not given permission to send notifications - please try again": "Riot 没有通知发送权限 - 请重试",
"riot-web version:": "riot-网页版",
"riot-web version:": "riot-web 版本",
"Room %(roomId)s not visible": "聊天室 %(roomId)s 已隐藏",
"Room Colour": "聊天室颜色",
"Room name (optional)": "聊天室名称 (可选)",
@ -97,8 +97,8 @@
"Sender device information": "发送者的设备信息",
"Send Invites": "发送邀请",
"Send Reset Email": "发送密码重设邮件",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s 发了一张图片。.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s 向 %(targetDisplayName)s 发了加入聊天室的邀请。.",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s 发了一张图片。",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s 向 %(targetDisplayName)s 发了加入聊天室的邀请。",
"Server error": "服务器错误",
"Server may be unavailable or overloaded": "服务器可能不可用或者超载",
"Server may be unavailable, overloaded, or search timed out :(": "服务器可能不可用、超载,或者搜索超时 :(",
@ -106,8 +106,8 @@
"Server may be unavailable, overloaded, or you hit a bug.": "当前服务器可能处于不可用或过载状态,或者您遇到了一个 bug。",
"Server unavailable, overloaded, or something else went wrong.": "服务器可能不可用、超载,或者其他东西出错了.",
"Session ID": "会话 ID",
"%(senderName)s set a profile picture.": "%(senderName)s 设置了头像。.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s 将昵称改为了 %(displayName)s。.",
"%(senderName)s set a profile picture.": "%(senderName)s 设置了头像。",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s 将昵称改为了 %(displayName)s。",
"Settings": "设置",
"Show panel": "显示侧边栏",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "用12小时制显示时间戳 (如:下午 2:30",
@ -115,7 +115,7 @@
"Sign in": "登录",
"Sign out": "注销",
"%(count)s of your messages have not been sent.|other": "部分消息未发送。",
"Someone": "某用户",
"Someone": "某用户",
"Start a chat": "创建聊天",
"Start Chat": "开始聊天",
"Submit": "提交",
@ -133,15 +133,15 @@
"Algorithm": "算法",
"Always show message timestamps": "总是显示消息时间戳",
"%(names)s and %(lastPerson)s are typing": "%(names)s 和 %(lastPerson)s 正在输入",
"A new password must be entered.": "一个新的密码必须输入。.",
"%(senderName)s answered the call.": "%(senderName)s 接了通话。.",
"An error has occurred.": "一个错误出现了。",
"A new password must be entered.": "必须输入新密码。",
"%(senderName)s answered the call.": "%(senderName)s 接了通话。",
"An error has occurred.": "发生了一个错误。",
"Attachment": "附件",
"Autoplay GIFs and videos": "自动播放 GIF 与视频",
"%(senderName)s banned %(targetName)s.": "%(senderName)s 封禁了 %(targetName)s.",
"Ban": "封禁",
"Banned users": "被封禁的用户",
"Click here to fix": "点击这里修复",
"Click here to fix": "点击这里修复",
"Confirm password": "确认密码",
"Confirm your new password": "确认你的新密码",
"Continue": "继续",
@ -150,10 +150,10 @@
"Join Room": "加入聊天室",
"%(targetName)s joined the room.": "%(targetName)s 已加入聊天室。",
"Jump to first unread message.": "跳到第一条未读消息。",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s 把 %(targetName)s 踢出了聊天室。.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s 把 %(targetName)s 踢出了聊天室。",
"Leave room": "退出聊天室",
"New password": "新密码",
"Add a topic": "添加一个主题",
"Add a topic": "添加主题",
"Admin": "管理员",
"Admin Tools": "管理工具",
"VoIP": "IP 电话",
@ -167,7 +167,7 @@
"Camera": "摄像头",
"Hide removed messages": "隐藏被删除的消息",
"Authentication": "认证",
"Alias (optional)": "别名 (可选)",
"Alias (optional)": "别名(可选)",
"%(items)s and %(lastItem)s": "%(items)s 和 %(lastItem)s",
"and %(count)s others...|other": "和其它 %(count)s 个...",
"and %(count)s others...|one": "和其它一个...",
@ -180,7 +180,7 @@
"Are you sure you want to upload the following files?": "你确定要上传这些文件吗?",
"Bans user with given id": "按照 ID 封禁指定的用户",
"Blacklisted": "已拉黑",
"Bulk Options": "批量操作",
"Bulk Options": "批量选项",
"Call Timeout": "通话超时",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "无法连接主服务器 - 请检查网络连接,确保你的<a>主服务器 SSL 证书</a>被信任,且没有浏览器插件拦截请求。",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "当浏览器地址栏里有 HTTPS 的 URL 时,不能使用 HTTP 连接主服务器。请使用 HTTPS 或者<a>允许不安全的脚本</a>。",
@ -214,7 +214,7 @@
"Custom": "自定义",
"Custom level": "自定义级别",
"Decline": "拒绝",
"Device already verified!": "设备已验证!",
"Device already verified!": "设备已验证!",
"Device ID:": "设备 ID:",
"device id: ": "设备 ID: ",
"Device key:": "设备密钥 :",
@ -231,7 +231,7 @@
"Export": "导出",
"Failed to fetch avatar URL": "获取 Avatar URL 失败",
"Failed to upload profile picture!": "头像上传失败!",
"Guest access is disabled on this Home Server.": "此服务器禁用了游客访问。",
"Guest access is disabled on this Home Server.": "此服务器已禁止游客访问。",
"Home": "主页面",
"Import": "导入",
"Incoming call from %(name)s": "来自 %(name)s 的通话",
@ -242,8 +242,8 @@
"Invited": "已邀请",
"Invites": "邀请",
"Invites user with given id to current room": "按照 ID 邀请指定用户加入当前聊天室",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' 不是一个合法的邮箱地址格式",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' 不是一个合法的昵称格式",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' 不符合电子邮箱地址的格式",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' 不符合别名的格式",
"%(displayName)s is typing": "%(displayName)s 正在输入",
"Sign in with": "第三方登录",
"Message not sent due to unknown devices being present": "消息未发送,因为有未知的设备存在",
@ -260,7 +260,7 @@
"not set": "未设置",
"not specified": "未指定",
"Notifications": "通知",
"(not supported by this browser)": "(此浏览器不支持)",
"(not supported by this browser)": "(未被此浏览器支持)",
"<not supported>": "<不支持>",
"NOT verified": "未验证",
"No display name": "无昵称",
@ -289,7 +289,7 @@
"Could not connect to the integration server": "无法连接关联的服务器",
"Curve25519 identity key": "Curve25519 认证密钥",
"Edit": "编辑",
"Joins room with given alias": "指定的别名加入聊天室",
"Joins room with given alias": "通过指定的别名加入聊天室",
"Labs": "实验室",
"%(targetName)s left the room.": "%(targetName)s 退出了聊天室。",
"Logged in as:": "登录为:",
@ -315,14 +315,14 @@
"Verified": "已验证",
"Verified key": "已验证的密钥",
"Video call": "视频通话",
"Voice call": "通话",
"Voice call": "音通话",
"VoIP conference finished.": "VoIP 会议结束。",
"VoIP conference started.": "VoIP 会议开始。",
"VoIP is unsupported": "不支持 VoIP",
"Warning!": "警告!",
"You must <a>register</a> to use this functionality": "你必须 <a>注册</a> 以使用此功能",
"You need to be logged in.": "你需要登录。",
"You need to enter a user name.": "你需要输入一个用户名。",
"You need to enter a user name.": "必须输入用户名。",
"Your password has been reset": "你的密码已被重置",
"Topic": "主题",
"Make Moderator": "使成为主持人",
@ -381,22 +381,22 @@
"Example": "例子",
"Create": "创建",
"Failed to upload image": "上传图像失败",
"Add a widget": "添加一个小挂件",
"Add a widget": "添加小挂件",
"Accept": "接受",
"Access Token:": "访问令牌:",
"Cannot add any more widgets": "无法添加更多小挂件",
"Delete widget": "删除小挂件",
"Define the power level of a user": "定义一个用户的特权级",
"Define the power level of a user": "定义一位用户的滥权等级",
"Drop here to tag %(section)s": "拖拽到这里标记 %(section)s",
"Enable automatic language detection for syntax highlighting": "为语法高亮启用自动检测编程语言",
"Failed to change power level": "修改特权级别失败",
"Failed to change power level": "滥权等级修改失败",
"Kick": "移除",
"Kicks user with given id": "按照 ID 移除特定的用户",
"Last seen": "最近一次上线",
"Level:": "级别:",
"Local addresses for this room:": "此聊天室的本地地址:",
"New passwords must match each other.": "新密码必须互相匹配。",
"Power level must be positive integer.": "权限级别必须是正整数。",
"Power level must be positive integer.": "滥权等级必须是正整数。",
"Reason: %(reasonText)s": "理由: %(reasonText)s",
"Revoke Moderator": "撤销主持人",
"Revoke widget access": "撤回小挂件的访问权",
@ -409,9 +409,9 @@
"Save": "保存",
"Send anyway": "仍然发送",
"Sets the room topic": "设置聊天室主题",
"Show Text Formatting Toolbar": "显示文格式工具栏",
"Show Text Formatting Toolbar": "显示文格式工具栏",
"This room has no local addresses": "此聊天室没有本地地址",
"This doesn't appear to be a valid email address": "这看起来不是一个合法的邮箱地址",
"This doesn't appear to be a valid email address": "这似乎不是有效的邮箱地址",
"This is a preview of this room. Room interactions have been disabled": "这是此聊天室的预览。交互操作已被禁用",
"This phone number is already in use": "此手机号码已被使用",
"This room": "此聊天室",
@ -439,12 +439,12 @@
"Usage": "用法",
"Who can read history?": "谁可以阅读历史消息?",
"You are not in this room.": "您不在此聊天室中。",
"You have no visible notifications": "没有可见的通知",
"You have no visible notifications": "没有可见的通知",
"Missing password.": "缺少密码。",
"Passwords don't match.": "密码不匹配。",
"I already have an account": "我已经有一个帐号",
"I already have an account": "我已经有帐号",
"Unblacklist": "移出黑名单",
"Not a valid Riot keyfile": "不是一个有效的 Riot 密钥文件",
"Not a valid Riot keyfile": "不是有效的 Riot 密钥文件",
"%(targetName)s accepted an invitation.": "%(targetName)s 已接受邀请。",
"Do you want to load widget from URL:": "你是否要从此 URL 中加载小挂件:",
"Hide join/leave messages (invites/kicks/bans unaffected)": "隐藏加入/退出消息(邀请/踢出/封禁不受影响)",
@ -452,7 +452,7 @@
"Publish this room to the public in %(domain)s's room directory?": "是否将此聊天室发布至 %(domain)s 的聊天室目录中?",
"Manage Integrations": "管理集成",
"No users have specific privileges in this room": "此聊天室中没有用户有特殊权限",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s 发起了一个 %(callType)s 通话。",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s 发起了%(callType)s通话。",
"Please check your email and click on the link it contains. Once this is done, click continue.": "请检查你的电子邮箱并点击里面包含的链接。完成时请点击继续。",
"Press <StartChatButton> to start a chat with someone": "按下 <StartChatButton> 来开始和某个人聊天",
"%(senderName)s removed their profile picture.": "%(senderName)s 移除了他们的头像。",
@ -462,7 +462,7 @@
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "验证码将发送至 +%(msisdn)s请输入收到的验证码",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s 接受了 %(displayName)s 的邀请。",
"Active call (%(roomName)s)": "当前通话 (来自聊天室 %(roomName)s)",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s 将级别调整%(powerLevelDiffText)s 。",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s 将级别调整%(powerLevelDiffText)s 。",
"Changes colour scheme of current room": "修改了样式",
"Deops user with given id": "按照 ID 取消特定用户的管理员权限",
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "通过 <voiceText>语言</voiceText> 或者 <videoText>视频</videoText>加入.",
@ -481,9 +481,9 @@
"%(roomName)s is not accessible at this time.": "%(roomName)s 此时无法访问。",
"Start authentication": "开始认证",
"The maximum permitted number of widgets have already been added to this room.": "此聊天室可拥有的小挂件数量已达到上限。",
"The phone number entered looks invalid": "输入的手机号码看起来无效",
"The phone number entered looks invalid": "此手机号码似乎无效",
"The remote side failed to pick up": "对方未能接听",
"This Home Server does not support login using email address.": "HS不支持使用邮箱地址登陆。",
"This Home Server does not support login using email address.": "此主服务器不支持使用邮箱地址登陆。",
"This invitation was sent to an email address which is not associated with this account:": "此邀请被发送到与此帐户不相关的邮箱地址:",
"This room is not recognised.": "无法识别此聊天室。",
"To get started, please pick a username!": "请点击用户名!",
@ -498,9 +498,9 @@
"Use with caution": "谨慎使用",
"User Interface": "用户界面",
"User name": "用户名",
"(no answer)": "没有回答",
"(no answer)": "无响应",
"(warning: cannot be disabled again!)": "(警告:无法再被禁用!)",
"WARNING: Device already verified, but keys do NOT MATCH!": "警告:设备已验证,但密钥不匹配!",
"WARNING: Device already verified, but keys do NOT MATCH!": "警告:设备已验证,但密钥不匹配!",
"Who can access this room?": "谁有权访问此聊天室?",
"Who would you like to add to this room?": "你想把谁添加到此聊天室?",
"Who would you like to communicate with?": "你想和谁交流?",
@ -513,12 +513,12 @@
"You seem to be in a call, are you sure you want to quit?": "您似乎正在进行通话,确定要退出吗?",
"You seem to be uploading files, are you sure you want to quit?": "您似乎正在上传文件,确定要退出吗?",
"You should not yet trust it to secure data": "你不应该相信它来保护你的数据",
"Upload an avatar:": "上传一个头像:",
"This doesn't look like a valid email address.": "这看起来不是一个合法的邮箱地址。",
"This doesn't look like a valid phone number.": "这看起来不是一个合法的手机号码。",
"Upload an avatar:": "上传头像:",
"This doesn't look like a valid email address.": "这似乎不是有效的邮箱地址。",
"This doesn't look like a valid phone number.": "这似乎不是有效的手机号码。",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "用户名只可以包含字母、数字、点、连字号和下划线。",
"An unknown error occurred.": "一个未知错误出现了。",
"An error occurred: %(error_string)s": "一个错误出现了 %(error_string)s",
"An unknown error occurred.": "发生了一个未知错误。",
"An error occurred: %(error_string)s": "发生了一个错误: %(error_string)s",
"Encrypt room": "加密聊天室",
"There are no visible files in this room": "此聊天室中没有可见的文件",
"Active call": "当前通话",
@ -531,28 +531,28 @@
"Check for update": "检查更新",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s 移除了聊天室头像。",
"Something went wrong!": "出了点问题!",
"If you already have a Matrix account you can <a>log in</a> instead.": "如果你已经有一个 Matrix 帐号,你可以<a>登录</a>。",
"Do you want to set an email address?": "要设置一个邮箱地址吗?",
"If you already have a Matrix account you can <a>log in</a> instead.": "若您已经拥有 Matrix 帐号,您也可以 <a>登录</a>。",
"Do you want to set an email address?": "您想要设置一个邮箱地址吗?",
"New address (e.g. #foo:%(localDomain)s)": "新的地址(例如 #foo:%(localDomain)s",
"Upload new:": "上传新的:",
"User ID": "用户 ID",
"Username invalid: %(errMessage)s": "用户名无效: %(errMessage)s",
"Verification Pending": "验证等待中",
"(unknown failure: %(reason)s)": "(未知错误:%(reason)s",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "警告:密钥验证失败!%(userId)s 和 device %(deviceId)s 的签名密钥是 \"%(fprint)s\",和提供的咪呀 \"%(fingerprint)s\" 不匹配。这可能意味着你的通信正在被窃听!",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "警告:密钥验证失败!%(userId)s 和 device %(deviceId)s 的签名密钥为 \"%(fprint)s\",与提供的密钥 \"%(fingerprint)s\" 不匹配。这可能意味着你的通信正在被窃听!",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s 收回了 %(targetName)s 的邀请。",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "你想要 <acceptText>接受</acceptText> 还是 <declineText>拒绝</declineText> 这个邀请?",
"You already have existing direct chats with this user:": "你已经有和此用户的直接聊天:",
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "你现在还不再任何聊天室!按下 <CreateRoomButton> 来创建一个聊天室或者 <RoomDirectoryButton> 来浏览目录",
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "您尚未处于任何聊天室中!按下 <CreateRoomButton> 创建一个聊天室或 <RoomDirectoryButton> 来浏览目录",
"You cannot place a call with yourself.": "你怎么寂寞到要和自己打电话,不支持的啦。",
"You have been kicked from %(roomName)s by %(userName)s.": "您已被 %(userName)s 从聊天室 %(roomName)s 中移除。",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "你已经登出了所有的设备并不再接收推送通知。要重新启用通知,请在每个设备上登录",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "你已经登出了所有的设备并不再接收推送通知。要重新启用通知,请在每个设备上重新登录",
"You have <a>disabled</a> URL previews by default.": "你已经默认 <a>禁用</a> 链接预览。",
"You have <a>enabled</a> URL previews by default.": "你已经默认 <a>启用</a> 链接预览。",
"Your home server does not support device management.": "你的 home server 不支持设备管理。",
"Set a display name:": "设置一个昵称:",
"Set a display name:": "设置昵称:",
"This server does not support authentication with a phone number.": "此服务器不支持使用手机号码认证。",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "密码过短(最短为 %(MIN_PASSWORD_LENGTH)s)。",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "密码长度过短(至少应为 %(MIN_PASSWORD_LENGTH)s 位)。",
"Make this room private": "将此聊天室转为私密聊天室",
"Share message history with new users": "和新用户共享消息历史",
"Copied!": "已复制!",
@ -563,7 +563,7 @@
"Please select the destination room for this message": "请选择此消息的目标聊天室",
"Start automatically after system login": "在系统登录后自动启动",
"Analytics": "统计分析服务",
"Reject all %(invitedRooms)s invites": "拒绝所有 %(invitedRooms)s 邀请",
"Reject all %(invitedRooms)s invites": "拒绝所有 %(invitedRooms)s 邀请",
"You may wish to login with a different account, or add this email to this account.": "您可能是想要用另一个账户登录,或是将此电子邮件关联至当前账户。",
"Sun": "周日",
"Mon": "周一",
@ -584,11 +584,11 @@
"Oct": "十月",
"Nov": "十一月",
"Dec": "十二月",
"Desktop specific": "桌面特有的",
"Desktop specific": "桌面版特有功能",
"You must join the room to see its files": "你必须加入聊天室以看到它的文件",
"Failed to invite the following users to the %(roomName)s room:": "邀请以下用户到 %(roomName)s 聊天室失败:",
"Confirm Removal": "确认移除",
"Verifies a user, device, and pubkey tuple": "验证一个用户、设备和密钥元组",
"Verifies a user, device, and pubkey tuple": "验证用户、设备与公钥元组",
"Unknown devices": "未知设备",
"Unknown Address": "未知地址",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s 删除了他们的昵称 (%(oldDisplayName)s).",
@ -599,14 +599,14 @@
"Unable to remove contact information": "无法移除联系人信息",
"Riot collects anonymous analytics to allow us to improve the application.": "Riot 收集匿名的分析数据以允许我们改善它。",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" 包含你以前没见过的设备。",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "你可以使用自定义的服务器选项来通过指定一个不同的主服务器 URL 来登录其他 Matrix 服务器。",
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "你可以使用自定义服务器选项并指定一个不同的主服务器 URL 来登录其他 Matrix 服务器。",
"This allows you to use this app with an existing Matrix account on a different home server.": "这允许你使用其他主服务器上的 Matrix 帐号。",
"Please check your email to continue registration.": "请查看你的电子邮件以继续注册。",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "如果你不指定一个邮箱地址,你将不能重置你的密码。你确定吗?",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "如果不指定一个邮箱地址,您将无法重置你的密码。你确定吗?",
"Home server URL": "主服务器 URL",
"Identity server URL": "身份认证服务器 URL",
"What does this mean?": "这是什么意思?",
"Add an Integration": "添加一个集成",
"Add an Integration": "添加集成",
"Removed or unknown message type": "被移除或未知的消息类型",
"Ongoing conference call%(supportedText)s.": "正在进行的会议通话 %(supportedText)s.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s 修改了 %(roomName)s 的头像",
@ -615,7 +615,7 @@
"Authentication check failed: incorrect password?": "身份验证失败:密码错误?",
"This will allow you to reset your password and receive notifications.": "这将允许你重置你的密码和接收通知。",
"Share without verifying": "不验证就分享",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "添加了一个新的设备 '%(displayName)s',它正在请求加密密钥。",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "添加了一个新的设备 '%(displayName)s',它正在请求加密密钥。",
"Your unverified device '%(displayName)s' is requesting encryption keys.": "你的未经验证的设备 '%(displayName)s' 正在请求加密密钥。",
"Encryption key request": "加密密钥请求",
"Autocomplete Delay (ms):": "自动补全延迟(毫秒):",
@ -634,15 +634,15 @@
"Invite new community members": "邀请新社区成员",
"Invite to Community": "邀请到社区",
"Room name or alias": "聊天室名称或别名",
"Ignored user": "忽视用户",
"Ignored user": "已忽略的用户",
"You are now ignoring %(userId)s": "你正在忽视 %(userId)s",
"Unignored user": "接触忽视用户",
"Unignored user": "未忽略的用户",
"You are no longer ignoring %(userId)s": "你不再忽视 %(userId)s",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s 解除了 %(targetName)s 的封禁。",
"(could not connect media)": "(无法连接媒体)",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s 更改了聊天室的置顶消息。",
"%(names)s and %(count)s others are typing|other": "%(names)s 和另外 %(count)s 个人正在输入",
"%(names)s and %(count)s others are typing|one": "%(names)s 另一个人正在输入",
"%(names)s and %(count)s others are typing|one": "%(names)s 另一个人正在输入",
"Send": "发送",
"Message Pinning": "消息置顶",
"Disable Emoji suggestions while typing": "输入时禁用 Emoji 建议",
@ -659,8 +659,8 @@
"%(senderName)s sent an image": "%(senderName)s 发送了一张图片",
"%(senderName)s sent a video": "%(senderName)s 发送了一个视频",
"%(senderName)s uploaded a file": "%(senderName)s 上传了一个文件",
"Unignore": "取消忽",
"Ignore": "忽",
"Unignore": "取消忽",
"Ignore": "忽",
"Jump to read receipt": "跳到阅读回执",
"Mention": "提及",
"Invite": "邀请",
@ -706,11 +706,11 @@
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s 更换了他们的头像 %(count)s 次",
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s 更换了他们的头像",
"%(items)s and %(count)s others|other": "%(items)s 和其他 %(count)s 人",
"%(items)s and %(count)s others|one": "%(items)s 和另一个人",
"%(items)s and %(count)s others|one": "%(items)s 与另一个",
"collapse": "折叠",
"expand": "展开",
"email address": "邮箱地址",
"You have entered an invalid address.": "你输入了一个无效地址。",
"You have entered an invalid address.": "您输入了无效的地址。",
"Advanced options": "高级选项",
"Leave": "退出",
"Description": "描述",
@ -718,7 +718,7 @@
"Light theme": "浅色主题",
"Dark theme": "深色主题",
"Status.im theme": "Status.im 主题",
"Ignored Users": "忽视用户",
"Ignored Users": "已忽略的用户",
"Room Notification": "聊天室通知",
"The platform you're on": "您使用的平台是",
"The version of Riot.im": "Riot.im 的版本是",
@ -734,10 +734,10 @@
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(monthName)s %(day)s %(time)s, %(weekDayName)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(fullYear)s %(monthName)s %(day)s, %(weekDayName)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(fullYear)s %(monthName)s %(day)s %(time)s, %(weekDayName)s",
"Who would you like to add to this community?": "你想把谁添加到此社区中?",
"Who would you like to add to this community?": "您想把谁添加至此社区中?",
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "警告:您添加的一切用户都将会对一切知道此社区的 ID 的人公开",
"Name or matrix ID": "名称或 Matrix ID",
"Which rooms would you like to add to this community?": "您想把哪个聊天室添加此社区中?",
"Which rooms would you like to add to this community?": "您想把哪个聊天室添加此社区中?",
"Add rooms to the community": "添加聊天室到社区",
"Add to community": "添加到社区",
"Failed to invite users to community": "邀请用户到社区失败",
@ -747,7 +747,7 @@
"Kick this user?": "是否移除此用户?",
"Unban this user?": "是否解封此用户?",
"Ban this user?": "是否封禁此用户?",
"Send an encrypted reply…": "发送加密回复…",
"Send an encrypted reply…": "发送加密回复…",
"Send a reply (unencrypted)…": "发送回复(未加密)…",
"Send an encrypted message…": "发送加密消息…",
"Send a message (unencrypted)…": "发送消息 (未加密)…",
@ -763,9 +763,9 @@
"To kick users, you must be a": "若要移除用户,您至少要是",
"To ban users, you must be a": "若要封禁用户,您至少要是",
"To remove other users' messages, you must be a": "若要删除其他用户的消息,您至少要是",
"%(user)s is a %(userRole)s": "%(user)s 是一 %(userRole)s",
"To link to a room it must have <a>an address</a>.": "要链接一个聊天室,它必须有一个<a>地址</a>。",
"To send events of type <eventType/>, you must be a": "要发送类型为 <eventType/> 的事件,你必须是",
"%(user)s is a %(userRole)s": "%(user)s 是一 %(userRole)s",
"To link to a room it must have <a>an address</a>.": "要链接聊天室,它必须有一个 <a>地址</a>。",
"To send events of type <eventType/>, you must be a": "要发送类型为 <eventType/> 的事件,您的级别至少为",
"Members only (since the point in time of selecting this option)": "仅成员(从选中此选项时开始)",
"Members only (since they were invited)": "只有成员(从他们被邀请开始)",
"Members only (since they joined)": "只有成员(从他们加入开始)",
@ -775,14 +775,14 @@
"Community ID": "社区 ID",
"example": "例子",
"This setting cannot be changed later!": "未来此设置将无法修改!",
"Add a Room": "添加一个聊天室",
"Add a User": "添加一个用户",
"Add a Room": "添加聊天室",
"Add a User": "添加用户",
"Unable to accept invite": "无法接受邀请",
"Unable to reject invite": "无法拒绝邀请",
"Leave Community": "退出社区",
"Community Settings": "社区设置",
"Community %(groupId)s not found": "找不到社区 %(groupId)s",
"Your Communities": "的社区",
"Your Communities": "的社区",
"Failed to set direct chat tag": "无法设定私聊标签",
"Failed to remove tag %(tagName)s from room": "移除聊天室标签 %(tagName)s 失败",
"Failed to add tag %(tagName)s to room": "无法为聊天室新增标签 %(tagName)s",
@ -806,10 +806,10 @@
"Key request sent.": "已发送密钥共享请求。",
"<requestLink>Re-request encryption keys</requestLink> from your other devices.": "从其他设备上 <requestLink>重新请求密钥</requestLink>。",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "如果您是房间中最后一位有权限的用户,在您降低自己的权限等级后将无法撤回此修改,因为你将无法重新获得权限。",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "你将无法撤回此修改,因为您正在将此用户的权限提升至和你相同的级别。",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "您将无法撤回此修改,因为您正在将此用户的滥权等级提升至与你相同。",
"No devices with registered encryption keys": "没有设备有已注册的加密密钥",
"Unmute": "取消静音",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s权限级别 %(powerLevelNumber)s",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s滥权等级 %(powerLevelNumber)s",
"Hide Stickers": "隐藏贴图",
"Show Stickers": "显示贴图",
"%(duration)ss": "%(duration)s 秒",
@ -875,7 +875,7 @@
"%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)s 撤回了他们的邀请",
"%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)s 撤回了他们的邀请共 %(count)s 次",
"%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)s 撤回了他们的邀请",
"Custom of %(powerLevel)s": "",
"Custom of %(powerLevel)s": "%(powerLevel)s 的自定义",
"<a>In reply to</a> <pill>": "<a>回复给</a> <pill>",
"Community IDs cannot be empty.": "社区 ID 不能为空。",
"Community IDs may only contain characters a-z, 0-9, or '=_-./'": "社区 ID 只能包含 a-z、0-9 或 “=_-./” 等字符",
@ -904,7 +904,7 @@
"In future this verification process will be more sophisticated.": "未来,此验证过程将更为精致、巧妙一些。",
"We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "我们建议您对每台设备进行验证以保证它们属于其合法所有者,但是您可以在不验证它们的情况下重新发送消息。",
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>社区页面的 HTML 代码</h1>\n<p>\n 你可以给社区的新成员们写些长长的社区简介来引导他们,或者放置\n 一些重要的<a href=\"foo\">链接</a>\n</p>\n<p>\n 你甚至可以使用 <img> 标签\n</p>\n",
"Add rooms to the community summary": "将聊天室添加到社区简介",
"Add rooms to the community summary": "将聊天室添加到社区简介",
"Which rooms would you like to add to this summary?": "您想要将哪个聊天室添加到社区简介?",
"Add to summary": "添加到简介",
"Failed to add the following rooms to the summary of %(groupId)s:": "添加以下聊天室到 %(groupId)s 的简介时失败:",
@ -917,7 +917,7 @@
"Featured Users:": "核心用户:",
"Join this community": "加入此社区",
"%(inviter)s has invited you to join this community": "%(inviter)s 邀请您加入此社区",
"Failed to add the following users to the summary of %(groupId)s:": "将下列用户添加到 %(groupId)s 的简介中时失败",
"Failed to add the following users to the summary of %(groupId)s:": "将下列用户添加至 %(groupId)s 的简介中时失败:",
"Failed to remove a user from the summary of %(groupId)s": "从 %(groupId)s 的简介中移除用户时失败",
"You are an administrator of this community": "你是此社区的管理员",
"You are a member of this community": "你是此社区的成员",
@ -931,7 +931,7 @@
"Did you know: you can use communities to filter your Riot.im experience!": "你知道吗:你可以将社区用作过滤器以增强你的 Riot.im 使用体验!",
"Create a new community": "创建新社区",
"Error whilst fetching joined communities": "获取已加入社区列表时出现错误",
"Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "创建社区,将用户与聊天室整合在一起!搭建自定义社区主页以在 Matrix 宇宙之中标出您的私人空间。",
"Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "创建社区,将用户与聊天室整合在一起!搭建自定义社区主页以在 Matrix 宇宙之中标出您的私人空间。",
"<showDevicesText>Show devices</showDevicesText>, <sendAnywayText>send anyway</sendAnywayText> or <cancelText>cancel</cancelText>.": "<showDevicesText>显示未信任的设备</showDevicesText> 、 <sendAnywayText>不经信任直接发送</sendAnywayText> 或 <cancelText>取消发送</cancelText>。",
"%(count)s of your messages have not been sent.|one": "您的消息尚未发送。",
"Uploading %(filename)s and %(count)s others|other": "正在上传 %(filename)s 与其他 %(count)s 个文件",
@ -939,7 +939,7 @@
"Uploading %(filename)s and %(count)s others|one": "正在上传 %(filename)s 与其他 %(count)s 个文件",
"Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "隐私对我们而言重要至极,所以我们不会在分析统计服务中收集任何个人信息或者可用于识别身份的数据。",
"Learn more about how we use analytics.": "进一步了解我们如何使用分析统计服务。",
"Please note you are logging into the %(hs)s server, not matrix.org.": "请注意,您正在登录的服务器是 %(hs)s不是 matrix.org。",
"Please note you are logging into the %(hs)s server, not matrix.org.": "请注意,您正在登录 %(hs)s而非 matrix.org。",
"This homeserver doesn't offer any login flows which are supported by this client.": "此主服务器不兼容本客户端支持的任何登录方式。",
"Sign in to get started": "登录以开始使用",
"Unbans user with given id": "按照 ID 解封特定的用户",
@ -949,10 +949,10 @@
"The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "导出的文件将允许任何可以读取它的人解密任何他们可以看到的加密消息,因此您应该小心以确保其安全。为解决此问题,您应该在下面输入密码以加密导出的数据。只有输入相同的密码才能导入数据。",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "导出文件有密码保护。你需要在此输入密码以解密此文件。",
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "此操作允许您导入之前从另一个 Matrix 客户端中导出的加密密钥文件。导入完成后,您将能够解密那个客户端可以解密的加密消息。",
"Ignores a user, hiding their messages from you": "忽略用户,隐藏他们的消息",
"Ignores a user, hiding their messages from you": "忽略用户,隐藏他们发送的消息",
"Stops ignoring a user, showing their messages going forward": "解除忽略用户,显示他们的消息",
"To return to your account in future you need to set a password": "如果你想再次使用账号,您得为它设置一个密码",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "如果你在 GitHub 提交了一个 bug调试日志可以帮助我们追踪这个问题。 调试日志包含应用程序使用数据,也就包括您的用户名、您访问的房间或社区的 ID 或,以及其他用户的用户名,但不包括聊天记录。",
"To return to your account in future you need to set a password": "如果您想再次使用此账号,就必须它设置一个密码",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "如果你在 GitHub 提交了一个 bug调试日志可以帮助我们追踪这个问题。 调试日志包含应用程序使用数据,也就包括您的用户名、您访问的房间或社区的 ID 或名,以及其他用户的用户名,但不包括聊天记录。",
"Debug Logs Submission": "发送调试日志",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "密码修改成功。在您在其他设备上重新登录之前,其他设备不会收到推送通知",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "尝试加载此房间的时间线的特定时间点,但是无法找到。",
@ -1118,7 +1118,7 @@
"Missing roomId.": "找不到此聊天室 ID 所对应的聊天室。",
"You have been banned from %(roomName)s by %(userName)s.": "您已被 %(userName)s 从聊天室 %(roomName)s 中封禁。",
"You have been banned from this room by %(userName)s.": "您已被 %(userName)s 从此聊天室中封禁。",
"Every page you use in the app": "您在 Riot 中使用的每一个页面",
"Every page you use in the app": "您在 Riot 中使用的所有页面",
"e.g. <CurrentPageURL>": "例如:<CurrentPageURL>",
"Your User Agent": "您的 User Agent",
"Your device resolution": "您设备的分辨率",
@ -1126,7 +1126,7 @@
"At this time it is not possible to reply with a file so this will be sent without being a reply.": "目前无法以文件作为回复的内容,所以此文件将不作为回复,独立发送。",
"Unable to reply": "无法回复",
"At this time it is not possible to reply with an emote.": "目前无法使用表情符号作为回复内容。",
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "您将被带到一个第三方网站以便验证您的账号使用 %(integrationsUrl)s 提供的集成。您希望继续吗?",
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "您将被带到一个第三方网站以便验证您的账号使用 %(integrationsUrl)s 提供的集成。您希望继续吗?",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "目前机器人检查CAPTCHA在桌面端不可用——请使用 <a>浏览器</a>",
"The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "无法更新聊天室 %(roomName)s 在社区 “%(groupId)s” 中的可见性。",
"Minimize apps": "最小化小部件",
@ -1178,7 +1178,7 @@
"This homeserver has hit its Monthly Active User limit so some users will not be able to log in. Please <a>contact your service administrator</a> to get this limit increased.": "此主服务器已达到其每月活跃用户数量限制,所以,一些用户将无法登录。若要提高此数量限制,请 <a>联系您的服务提供者</a> 。",
"Warning: This widget might use cookies.": "警告:此小挂件可能会使用 cookies。",
"Failed to remove widget": "移除小挂件失败",
"An error ocurred whilst trying to remove the widget from the room": "尝试从聊天室中移除小部件时发生了一个错误",
"An error ocurred whilst trying to remove the widget from the room": "尝试从聊天室中移除小部件时发生了错误",
"Reload widget": "刷新小挂件",
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "您确定要移除(删除)此事件吗?注意,如果删除了聊天室名称或话题的变化,就会撤销此更改。",
"This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. <b>This action is irreversible.</b>": "这将使您的账户永远不再可用。您将不能登录,或使用相同的用户 ID 重新注册。您的账户将退出所有已加入的聊天室,身份服务器上的账户信息也会被删除。<b>此操作是不可逆的。</b>",
@ -1226,5 +1226,54 @@
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "已向 %(emailAddress)s 发送了一封电子邮件。点开邮件中的链接后,请点击下面。",
"This homeserver has hit its Monthly Active User limit": "此主服务器已达到其每月活跃用户数量上限",
"Please contact your service administrator to continue using this service.": "请联系您的服务提供者以继续使用此服务。",
"Try the app first": "先试试 Riot.im 应用吧"
"Try the app first": "先试试 Riot.im 应用吧",
"Registration Required": "需要注册",
"You need to register to do this. Would you like to register now?": "您必须注册以继续。您想现在就注册吗?",
"Forces the current outbound group session in an encrypted room to be discarded": "强制丢弃加密聊天室中的当前出站群组会话",
"Unable to connect to Homeserver. Retrying...": "无法连接至主服务器。正在重试…",
"Sorry, your homeserver is too old to participate in this room.": "对不起,您的主服务器的程序版本过旧以至于无法加入此聊天室。",
"Increase performance by only loading room members on first view": "仅在首次查看时加载聊天室成员以改善性能",
"Mirror local video feed": "镜像本地视频源",
"This room has been replaced and is no longer active.": "此聊天室已被取代,且不再活跃。",
"The conversation continues here.": "对话在这里继续。",
"Upgrade room to version %(ver)s": "将聊天室升级为版本 %(ver)s",
"Internal room ID: ": "内部聊天室 ID ",
"Room version number: ": "聊天室版本号: ",
"There is a known vulnerability affecting this room.": "一个已知的威胁正在影响此聊天室。",
"This room version is vulnerable to malicious modification of room state.": "此版本的聊天室难以抵御对聊天室状态的恶意篡改。",
"Click here to upgrade to the latest room version and ensure room integrity is protected.": "点击这里以升级至最新版本的聊天室,并保证聊天室能受到完整的保护。",
"Only room administrators will see this warning": "此警告仅聊天室管理员可见",
"This room is a continuation of another conversation.": "此聊天室是另一个对话的延续之处。",
"Click here to see older messages.": "点击这里以查看更早的消息。",
"Failed to indicate account erasure": "无法指示帐户删除",
"Failed to upgrade room": "聊天室升级失败",
"The room upgrade could not be completed": "聊天室可能没有完整地升级",
"Upgrade this room to version %(version)s": "升级此聊天室至版本 %(version)s",
"Upgrade Room Version": "更新聊天室版本",
"Upgrading this room requires closing down the current instance of the room and creating a new room it its place. To give room members the best possible experience, we will:": "升级此聊天室需要关闭当前聊天室并创建一个新的聊天室。为了给聊天室成员提供最佳切换体验,我们将:",
"Create a new room with the same name, description and avatar": "创建一个拥有相同的名称,描述与头像的新聊天室",
"Update any local room aliases to point to the new room": "更新所有本地聊天室别名以使其指向新聊天室",
"Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "阻止用户在旧聊天室中发言,并发送消息建议用户迁移至新聊天室",
"Put a link back to the old room at the start of the new room so people can see old messages": "在新聊天室的消息开始处发送一条旧聊天室的链接,以便用户查看旧消息",
"Lazy loading members not supported": "不支持延迟加载成员",
"Lazy loading is not supported by your current homeserver.": "您当前使用的主服务器尚不支持延迟加载。",
"Legal": "法律信息",
"Unable to query for supported registration methods": "无法请求支持的注册方式",
"This homeserver has hit its Monthly Active User limit.": "此主服务器已达到其每月活跃用户限制。",
"This homeserver has exceeded one of its resource limits.": "本服务器已达到其使用量限制之一。",
"This homeserver has hit its Monthly Active User limit so <b>some users will not be able to log in</b>.": "本服务器已达到其每月活跃用户限制,<b>部分用户将无法登录</b>。",
"This homeserver has exceeded one of its resource limits so <b>some users will not be able to log in</b>.": "本主服务器已达到其使用量限制之一,<b>部分用户将无法登录</b>。",
"Please <a>contact your service administrator</a> to continue using this service.": "请 <a>联系您的服务管理员</a> 以继续使用本服务。",
"Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.": "您的消息未被发送,因为本主服务器已达到其使用量限制之一。请 <a>联系您的服务管理员</a> 以继续使用本服务。",
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.": "您的消息未被发送,因为本主服务器已达到其每月活跃用户限制。请 <a>联系您的服务管理员</a> 以继续使用本服务。",
"Please <a>contact your service administrator</a> to continue using the service.": "请 <a>联系您的服务管理员</a> 以继续使用本服务。",
"Please contact your homeserver administrator.": "请 联系您主服务器的管理员。",
"Please <a>contact your service administrator</a> to get this limit increased.": "请 <a>联系您的服务管理员</a> 以增加此限制的额度。",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s 添加了聊天室地址 %(addedAddresses)s。",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s 添加了一个聊天室地址 %(addedAddresses)s。",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s 移除了聊天室地址 %(removedAddresses)s。",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s 移除了一个聊天室地址 %(removedAddresses)s。",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s 添加了聊天室地址 %(addedAddresses)s 并移除了地址 %(removedAddresses)s。",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s 将此聊天室的主地址设为了 %(address)s。",
"%(senderName)s removed the main address for this room.": "%(senderName)s 移除了此聊天室的主地址。"
}

View file

@ -1270,5 +1270,24 @@
"Registration Required": "需要註冊",
"You need to register to do this. Would you like to register now?": "您必須註冊以繼續。您想要現在註冊嗎?",
"Unable to query for supported registration methods": "無法查詢支援的註冊方式",
"Unable to connect to Homeserver. Retrying...": "無法連線到家伺服器。正在重試……"
"Unable to connect to Homeserver. Retrying...": "無法連線到家伺服器。正在重試……",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s 新增了 %(addedAddresses)s 為此聊天室的位置。",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s 新增了 %(addedAddresses)s 為此聊天室的位置。",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s 移除了 %(removedAddresses)s 為此聊天室的位置。",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s 移除了 %(removedAddresses)s 為此聊天室的位置。",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s 新增了 %(addedAddresses)s 並移除了 %(removedAddresses)s 為此聊天室的位置。",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s 為此聊天室設定了主要位置 %(address)s。",
"%(senderName)s removed the main address for this room.": "%(senderName)s 移除了此聊天室的主要位置。",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "在遞交紀錄檔前,您必須<a>建立 GitHub 議題</a>以描述您的問題。",
"What GitHub issue are these logs for?": "這些紀錄檔的 GitHub 議題是什麼?",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot 現在僅使用三分之一到五分之一的記憶體,僅在需要時才會載入其他使用者的資訊。請等待我們與伺服器重新同步!",
"Updating Riot": "正在更新 Riot",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>您社群頁面的 HTML</h1>\n<p>\n 使用長描述以向新成員介紹社群,或是散佈\n 一些重要<a href=\"foo\">連結</a>\n</p>\n<p>\n 您也可以使用「img」標籤\n</p>\n",
"Submit Debug Logs": "遞交除錯紀錄",
"An email address is required to register on this homeserver.": "在此家伺服器上註冊必須填入電子郵件。",
"A phone number is required to register on this homeserver.": "在此伺服器上註冊必須填入電話號碼。",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "您之前曾在 %(host)s 上使用 Riot 並啟用成員列表的延遲載入。在此版本中延遲載入已停用。由於本機快取在這兩個設定間不相容Riot 必須重新同步您的帳號。",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "若其他分頁仍有不同版本的 Riot請將其關閉因為在同一個主機上同時啟用和停用延遲載入將會發生問題。",
"Incompatible local cache": "不相容的本機快取",
"Clear cache and resync": "清除快取並重新同步"
}

View file

@ -97,19 +97,17 @@ export function _t(text, variables, tags) {
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
const translated = safeCounterpartTranslate(text, args);
let substituted = substitute(translated, variables, tags);
const substituted = substitute(translated, variables, tags);
// For development/testing purposes it is useful to also output the original string
// Don't do that for release versions
if (ANNOTATE_STRINGS) {
if (typeof substituted === 'string') {
return `@@${text}##${substituted}@@`
}
else {
return `@@${text}##${substituted}@@`;
} else {
return <span className='translated-string' data-orig-string={text}>{substituted}</span>;
}
}
else {
} else {
return substituted;
}
}

View file

@ -16,7 +16,7 @@ limitations under the License.
'use strict';
var PushRuleVectorState = require('./PushRuleVectorState');
const PushRuleVectorState = require('./PushRuleVectorState');
module.exports = {
/**
@ -32,7 +32,7 @@ module.exports = {
*/
parseContentRules: function(rulesets) {
// first categorise the keyword rules in terms of their actions
var contentRules = this._categoriseContentRules(rulesets);
const contentRules = this._categoriseContentRules(rulesets);
// Decide which content rules to display in Vector UI.
// Vector displays a single global rule for a list of keywords
@ -54,41 +54,38 @@ module.exports = {
rules: contentRules.loud,
externalRules: [].concat(contentRules.loud_but_disabled, contentRules.on, contentRules.on_but_disabled, contentRules.other),
};
}
else if (contentRules.loud_but_disabled.length) {
} else if (contentRules.loud_but_disabled.length) {
return {
vectorState: PushRuleVectorState.OFF,
rules: contentRules.loud_but_disabled,
externalRules: [].concat(contentRules.on, contentRules.on_but_disabled, contentRules.other),
};
}
else if (contentRules.on.length) {
} else if (contentRules.on.length) {
return {
vectorState: PushRuleVectorState.ON,
rules: contentRules.on,
externalRules: [].concat(contentRules.on_but_disabled, contentRules.other),
};
}
else if (contentRules.on_but_disabled.length) {
} else if (contentRules.on_but_disabled.length) {
return {
vectorState: PushRuleVectorState.OFF,
rules: contentRules.on_but_disabled,
externalRules: contentRules.other,
}
} else {
};
} else {
return {
vectorState: PushRuleVectorState.ON,
rules: [],
externalRules: contentRules.other,
}
};
}
},
_categoriseContentRules: function(rulesets) {
var contentRules = {on: [], on_but_disabled:[], loud: [], loud_but_disabled: [], other: []};
for (var kind in rulesets.global) {
for (var i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
var r = rulesets.global[kind][i];
const contentRules = {on: [], on_but_disabled: [], loud: [], loud_but_disabled: [], other: []};
for (const kind in rulesets.global) {
for (let i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
const r = rulesets.global[kind][i];
// check it's not a default rule
if (r.rule_id[0] === '.' || kind !== 'content') {
@ -101,16 +98,14 @@ module.exports = {
case PushRuleVectorState.ON:
if (r.enabled) {
contentRules.on.push(r);
}
else {
} else {
contentRules.on_but_disabled.push(r);
}
break;
case PushRuleVectorState.LOUD:
if (r.enabled) {
contentRules.loud.push(r);
}
else {
} else {
contentRules.loud_but_disabled.push(r);
}
break;

View file

@ -24,11 +24,11 @@ module.exports = {
// }
// to a list of push actions.
encodeActions: function(action) {
var notify = action.notify;
var sound = action.sound;
var highlight = action.highlight;
const notify = action.notify;
const sound = action.sound;
const highlight = action.highlight;
if (notify) {
var actions = ["notify"];
const actions = ["notify"];
if (sound) {
actions.push({"set_tweak": "sound", "value": sound});
}
@ -50,19 +50,19 @@ module.exports = {
// }
// If the actions couldn't be decoded then returns null.
decodeActions: function(actions) {
var notify = false;
var sound = null;
var highlight = false;
let notify = false;
let sound = null;
let highlight = false;
for (var i = 0; i < actions.length; ++i) {
var action = actions[i];
for (let i = 0; i < actions.length; ++i) {
const action = actions[i];
if (action === "notify") {
notify = true;
} else if (action === "dont_notify") {
notify = false;
} else if (typeof action === 'object') {
if (action.set_tweak === "sound") {
sound = action.value
sound = action.value;
} else if (action.set_tweak === "highlight") {
highlight = action.value;
} else {
@ -80,7 +80,7 @@ module.exports = {
highlight = true;
}
var result = {notify: notify, highlight: highlight};
const result = {notify: notify, highlight: highlight};
if (sound !== null) {
result.sound = sound;
}

View file

@ -16,10 +16,10 @@ limitations under the License.
'use strict';
var StandardActions = require('./StandardActions');
var NotificationUtils = require('./NotificationUtils');
const StandardActions = require('./StandardActions');
const NotificationUtils = require('./NotificationUtils');
var states = {
const states = {
/** The push rule is disabled */
OFF: "off",
@ -48,8 +48,7 @@ module.exports = {
actionsFor: function(pushRuleVectorState) {
if (pushRuleVectorState === this.ON) {
return StandardActions.ACTION_NOTIFY;
}
else if (pushRuleVectorState === this.LOUD) {
} else if (pushRuleVectorState === this.LOUD) {
return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND;
}
},
@ -62,21 +61,21 @@ module.exports = {
* state. Returns null if it does not match these categories.
*/
contentRuleVectorStateKind: function(rule) {
var decoded = NotificationUtils.decodeActions(rule.actions);
const decoded = NotificationUtils.decodeActions(rule.actions);
if (!decoded) {
return null;
}
// Count tweaks to determine if it is a ON or LOUD rule
var tweaks = 0;
let tweaks = 0;
if (decoded.sound) {
tweaks++;
}
if (decoded.highlight) {
tweaks++;
}
var stateKind = null;
let stateKind = null;
switch (tweaks) {
case 0:
stateKind = this.ON;
@ -89,6 +88,6 @@ module.exports = {
},
};
for (var k in states) {
for (const k in states) {
module.exports[k] = states[k];
};
}

View file

@ -18,8 +18,8 @@ limitations under the License.
import { _td } from '../languageHandler';
var StandardActions = require('./StandardActions');
var PushRuleVectorState = require('./PushRuleVectorState');
const StandardActions = require('./StandardActions');
const PushRuleVectorState = require('./PushRuleVectorState');
class VectorPushRuleDefinition {
constructor(opts) {
@ -30,16 +30,16 @@ class VectorPushRuleDefinition {
// Translate the rule actions and its enabled value into vector state
ruleToVectorState(rule) {
var enabled = false;
var actions = null;
let enabled = false;
let actions = null;
if (rule) {
enabled = rule.enabled;
actions = rule.actions;
}
for (var stateKey in PushRuleVectorState.states) {
var state = PushRuleVectorState.states[stateKey];
var vectorStateToActions = this.vectorStateToActions[state];
for (const stateKey in PushRuleVectorState.states) {
const state = PushRuleVectorState.states[stateKey];
const vectorStateToActions = this.vectorStateToActions[state];
if (!vectorStateToActions) {
// No defined actions means that this vector state expects a disabled (or absent) rule
@ -58,7 +58,7 @@ class VectorPushRuleDefinition {
JSON.stringify(rule));
return undefined;
}
};
}
/**
* The descriptions of rules managed by the Vector UI.
@ -71,8 +71,8 @@ module.exports = {
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
off: StandardActions.ACTION_DISABLED
}
off: StandardActions.ACTION_DISABLED,
},
}),
// Messages containing user's username (localpart/MXID)
@ -82,8 +82,8 @@ module.exports = {
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
off: StandardActions.ACTION_DISABLED
}
off: StandardActions.ACTION_DISABLED,
},
}),
// Messages just sent to the user in a 1:1 room
@ -93,8 +93,8 @@ module.exports = {
vectorStateToActions: {
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
off: StandardActions.ACTION_DONT_NOTIFY
}
off: StandardActions.ACTION_DONT_NOTIFY,
},
}),
// Messages just sent to a group chat room
@ -106,8 +106,8 @@ module.exports = {
vectorStateToActions: {
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
off: StandardActions.ACTION_DONT_NOTIFY
}
off: StandardActions.ACTION_DONT_NOTIFY,
},
}),
// Invitation for the user
@ -117,8 +117,8 @@ module.exports = {
vectorStateToActions: {
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
off: StandardActions.ACTION_DISABLED
}
off: StandardActions.ACTION_DISABLED,
},
}),
// Incoming call
@ -128,8 +128,8 @@ module.exports = {
vectorStateToActions: {
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_NOTIFY_RING_SOUND,
off: StandardActions.ACTION_DISABLED
}
off: StandardActions.ACTION_DISABLED,
},
}),
// Notifications from bots
@ -141,6 +141,6 @@ module.exports = {
on: StandardActions.ACTION_DISABLED,
loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
off: StandardActions.ACTION_DONT_NOTIFY,
}
},
}),
};

View file

@ -60,11 +60,11 @@ class ConsoleLogger {
};
Object.keys(consoleFunctionsToLevels).forEach((fnName) => {
const level = consoleFunctionsToLevels[fnName];
let originalFn = consoleObj[fnName].bind(consoleObj);
const originalFn = consoleObj[fnName].bind(consoleObj);
consoleObj[fnName] = (...args) => {
this.log(level, ...args);
originalFn(...args);
}
};
});
}
@ -116,7 +116,7 @@ class IndexedDBLogStore {
* @return {Promise} Resolves when the store is ready.
*/
connect() {
let req = this.indexedDB.open("logs");
const req = this.indexedDB.open("logs");
return new Promise((resolve, reject) => {
req.onsuccess = (event) => {
this.db = event.target.result;
@ -127,7 +127,7 @@ class IndexedDBLogStore {
req.onerror = (event) => {
const err = (
"Failed to open log database: " + event.target.errorCode
"Failed to open log database: " + event.target.error.name
);
console.error(err);
reject(new Error(err));
@ -137,7 +137,7 @@ class IndexedDBLogStore {
req.onupgradeneeded = (event) => {
const db = event.target.result;
const logObjStore = db.createObjectStore("logs", {
keyPath: ["id", "index"]
keyPath: ["id", "index"],
});
// Keys in the database look like: [ "instance-148938490", 0 ]
// Later on we need to query everything based on an instance id.
@ -146,15 +146,15 @@ class IndexedDBLogStore {
logObjStore.add(
this._generateLogEntry(
new Date() + " ::: Log database was created."
)
new Date() + " ::: Log database was created.",
),
);
const lastModifiedStore = db.createObjectStore("logslastmod", {
keyPath: "id",
});
lastModifiedStore.add(this._generateLastModifiedTime());
}
};
});
}
@ -206,21 +206,21 @@ class IndexedDBLogStore {
resolve();
return;
}
let txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
let objStore = txn.objectStore("logs");
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
const objStore = txn.objectStore("logs");
txn.oncomplete = (event) => {
resolve();
};
txn.onerror = (event) => {
console.error(
"Failed to flush logs : ", event
"Failed to flush logs : ", event,
);
reject(
new Error("Failed to write logs: " + event.target.errorCode)
new Error("Failed to write logs: " + event.target.errorCode),
);
}
};
objStore.add(this._generateLogEntry(lines));
let lastModStore = txn.objectStore("logslastmod");
const lastModStore = txn.objectStore("logslastmod");
lastModStore.put(this._generateLastModifiedTime());
});
return this.flushPromise;
@ -241,20 +241,27 @@ class IndexedDBLogStore {
// Returns: a string representing the concatenated logs for this ID.
function fetchLogs(id) {
const o = db.transaction("logs", "readonly").objectStore("logs");
return selectQuery(o.index("id"), IDBKeyRange.only(id),
(cursor) => {
return {
lines: cursor.value.lines,
index: cursor.value.index,
}
}).then((linesArray) => {
// We have been storing logs periodically, so string them all
// together *in order of index* now
linesArray.sort((a, b) => {
return a.index - b.index;
})
return linesArray.map((l) => l.lines).join("");
const objectStore = db.transaction("logs", "readonly").objectStore("logs");
return new Promise((resolve, reject) => {
const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), 'next');
let lines = '';
query.onerror = (event) => {
reject(new Error("Query failed: " + event.target.errorCode));
};
query.onsuccess = (event) => {
const cursor = event.target.result;
if (!cursor) {
resolve(lines);
return; // end of results
}
lines += cursor.value.lines;
if (lines.length >= MAX_LOG_SIZE) {
resolve(lines);
} else {
cursor.continue();
}
};
});
}
@ -262,7 +269,7 @@ class IndexedDBLogStore {
function fetchLogIds() {
// To gather all the log IDs, query for all records in logslastmod.
const o = db.transaction("logslastmod", "readonly").objectStore(
"logslastmod"
"logslastmod",
);
return selectQuery(o, undefined, (cursor) => {
return {
@ -280,7 +287,7 @@ class IndexedDBLogStore {
function deleteLogs(id) {
return new Promise((resolve, reject) => {
const txn = db.transaction(
["logs", "logslastmod"], "readwrite"
["logs", "logslastmod"], "readwrite",
);
const o = txn.objectStore("logs");
// only load the key path, not the data which may be huge
@ -292,7 +299,7 @@ class IndexedDBLogStore {
}
o.delete(cursor.primaryKey);
cursor.continue();
}
};
txn.oncomplete = () => {
resolve();
};
@ -300,8 +307,8 @@ class IndexedDBLogStore {
reject(
new Error(
"Failed to delete logs for " +
`'${id}' : ${event.target.errorCode}`
)
`'${id}' : ${event.target.errorCode}`,
),
);
};
// delete last modified entries
@ -310,21 +317,18 @@ class IndexedDBLogStore {
});
}
let allLogIds = await fetchLogIds();
const allLogIds = await fetchLogIds();
let removeLogIds = [];
let logs = [];
const logs = [];
let size = 0;
for (let i = 0; i < allLogIds.length; i++) {
let lines = await fetchLogs(allLogIds[i]);
const lines = await fetchLogs(allLogIds[i]);
// always include at least one log file, but only include
// subsequent ones if they won't take us over the MAX_LOG_SIZE
if (i > 0 && size + lines.length > MAX_LOG_SIZE) {
// the remaining log IDs should be removed. If we go out of
// bounds this is just []
//
// XXX: there's nothing stopping the current session exceeding
// MAX_LOG_SIZE. We ought to think about culling it.
removeLogIds = allLogIds.slice(i + 1);
break;
}
@ -343,7 +347,7 @@ class IndexedDBLogStore {
console.log(`Removed ${removeLogIds.length} old logs.`);
}, (err) => {
console.error(err);
})
});
}
return logs;
}
@ -352,7 +356,7 @@ class IndexedDBLogStore {
return {
id: this.id,
lines: lines,
index: this.index++
index: this.index++,
};
}
@ -377,7 +381,7 @@ class IndexedDBLogStore {
function selectQuery(store, keyRange, resultMapper) {
const query = store.openCursor(keyRange);
return new Promise((resolve, reject) => {
let results = [];
const results = [];
query.onerror = (event) => {
reject(new Error("Query failed: " + event.target.errorCode));
};
@ -390,7 +394,7 @@ function selectQuery(store, keyRange, resultMapper) {
}
results.push(resultMapper(cursor));
cursor.continue();
}
};
});
}
@ -414,7 +418,7 @@ module.exports = {
let indexedDB;
try {
indexedDB = window.indexedDB;
} catch(e) {}
} catch (e) {}
if (indexedDB) {
global.mx_rage_store = new IndexedDBLogStore(indexedDB, global.mx_rage_logger);
@ -451,7 +455,7 @@ module.exports = {
getLogsForReport: async function() {
if (!global.mx_rage_logger) {
throw new Error(
"No console logger, did you forget to call init()?"
"No console logger, did you forget to call init()?",
);
}
// If in incognito mode, store is null, but we still want bug report
@ -460,8 +464,7 @@ module.exports = {
// flush most recent logs
await global.mx_rage_store.flush();
return await global.mx_rage_store.consume();
}
else {
} else {
return [{
lines: global.mx_rage_logger.flush(true),
id: "-",

View file

@ -22,7 +22,7 @@ import MatrixClientPeg from '../MatrixClientPeg';
import PlatformPeg from '../PlatformPeg';
import { _t } from '../languageHandler';
import rageshake from './rageshake'
import rageshake from './rageshake';
// polyfill textencoder if necessary
@ -59,8 +59,7 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
let version = "UNKNOWN";
try {
version = await PlatformPeg.get().getAppVersion();
}
catch (err) {} // PlatformPeg already logs this.
} catch (err) {} // PlatformPeg already logs this.
let userAgent = "UNKNOWN";
if (window.navigator && window.navigator.userAgent) {
@ -85,7 +84,7 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
if (opts.sendLogs) {
progressCallback(_t("Collecting logs"));
const logs = await rageshake.getLogsForReport();
for (let entry of logs) {
for (const entry of logs) {
// encode as UTF-8
const buf = new TextEncoder().encode(entry.lines);

View file

@ -88,7 +88,7 @@ export const SETTINGS = {
displayName: _td("Increase performance by only loading room members on first view"),
supportedLevels: LEVELS_FEATURE,
controller: new LazyLoadingController(),
default: false,
default: true,
},
"MessageComposerInput.dontSuggestEmoji": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,

View file

@ -104,7 +104,7 @@ export default class DMRoomMap {
.some((ids) => ids.roomId === roomId);
});
guessedUserIdsThatChanged.forEach(({userId, roomId}) => {
let roomIds = userToRooms[userId];
const roomIds = userToRooms[userId];
if (!roomIds) {
userToRooms[userId] = [roomId];
} else {

View file

@ -32,13 +32,18 @@ try {
* @param {Object} opts options to pass to Matrix.createClient. This will be
* extended with `sessionStore` and `store` members.
*
* @param {bool} useIndexedDb True to attempt to use indexeddb, or false to force
* use of the memory store. Default: true.
*
* @property {string} indexedDbWorkerScript Optional URL for a web worker script
* for IndexedDB store operations. By default, indexeddb ops are done on
* the main thread.
*
* @returns {MatrixClient} the newly-created MatrixClient
*/
export default function createMatrixClient(opts) {
export default function createMatrixClient(opts, useIndexedDb) {
if (useIndexedDb === undefined) useIndexedDb = true;
const storeOpts = {
useAuthorizationHeader: true,
};
@ -47,10 +52,7 @@ export default function createMatrixClient(opts) {
storeOpts.sessionStore = new Matrix.WebStorageSessionStore(localStorage);
}
if (indexedDB && localStorage) {
// FIXME: bodge to remove old database. Remove this after a few weeks.
indexedDB.deleteDatabase("matrix-js-sdk:default");
if (indexedDB && localStorage && useIndexedDb) {
storeOpts.store = new Matrix.IndexedDBStore({
indexedDB: indexedDB,
dbName: "riot-web-sync",

View file

@ -0,0 +1,72 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import expect from 'expect';
import {phasedRollOutExpiredForUser} from '../src/PhasedRollOut';
const OFFSET = 6000000;
// phasedRollOutExpiredForUser enables users in bucks of 1 minute
const MS_IN_MINUTE = 60 * 1000;
describe('PhasedRollOut', function() {
it('should return true if phased rollout is not configured', function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, null)).toBeTruthy();
});
it('should return true if phased rollout feature is not configured', function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, {
"feature_other": {offset: 0, period: 0},
})).toBeTruthy();
});
it('should return false if phased rollout for feature is misconfigured', function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, {
"feature_test": {},
})).toBeFalsy();
});
it("should return false if phased rollout hasn't started yet", function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 5000000, {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE},
})).toBeFalsy();
});
it("should start to return true in bucket 2/10 for '@user:hs'", function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 2) - 1, {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeFalsy();
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 2), {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeTruthy();
});
it("should start to return true in bucket 4/10 for 'alice@other-hs'", function() {
expect(phasedRollOutExpiredForUser("alice@other-hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 4) - 1, {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeFalsy();
expect(phasedRollOutExpiredForUser("alice@other-hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 4), {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeTruthy();
});
it("should return true after complete rollout period'", function() {
expect(phasedRollOutExpiredForUser("user:hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 20), {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeTruthy();
});
});

View file

@ -0,0 +1,175 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import expect from 'expect';
import QueryMatcher from '../../src/autocomplete/QueryMatcher';
const OBJECTS = [
{ name: "Mel B", nick: "Scary" },
{ name: "Mel C", nick: "Sporty" },
{ name: "Emma", nick: "Baby" },
{ name: "Geri", nick: "Ginger" },
{ name: "Victoria", nick: "Posh" },
];
const NONWORDOBJECTS = [
{ name: "B.O.B" },
{ name: "bob" },
];
describe('QueryMatcher', function() {
it('Returns results by key', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
const results = qm.match('Geri');
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
});
it('Returns results by prefix', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
const results = qm.match('Ge');
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
});
it('Matches case-insensitive', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
const results = qm.match('geri');
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
});
it('Matches ignoring accents', function() {
const qm = new QueryMatcher([{name: "Gëri", foo: 46}], {keys: ["name"]});
const results = qm.match('geri');
expect(results.length).toBe(1);
expect(results[0].foo).toBe(46);
});
it('Returns multiple results in order of search string appearance', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name", "nick"]});
const results = qm.match('or');
expect(results.length).toBe(2);
expect(results[0].name).toBe('Mel C');
expect(results[1].name).toBe('Victoria');
qm.setObjects(OBJECTS.slice().reverse());
const reverseResults = qm.match('or');
// should still be in the same order: search string position
// takes precedence over input order
expect(reverseResults.length).toBe(2);
expect(reverseResults[0].name).toBe('Mel C');
expect(reverseResults[1].name).toBe('Victoria');
});
it('Returns results with search string in same place in insertion order', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
const results = qm.match('Mel');
expect(results.length).toBe(2);
expect(results[0].name).toBe('Mel B');
expect(results[1].name).toBe('Mel C');
qm.setObjects(OBJECTS.slice().reverse());
const reverseResults = qm.match('Mel');
expect(reverseResults.length).toBe(2);
expect(reverseResults[0].name).toBe('Mel C');
expect(reverseResults[1].name).toBe('Mel B');
});
it('Returns numeric results in correct order (input pos)', function() {
// regression test for depending on object iteration order
const qm = new QueryMatcher([
{name: "123456badger"},
{name: "123456"},
], {keys: ["name"]});
const results = qm.match('123456');
expect(results.length).toBe(2);
expect(results[0].name).toBe('123456badger');
expect(results[1].name).toBe('123456');
});
it('Returns numeric results in correct order (query pos)', function() {
const qm = new QueryMatcher([
{name: "999999123456"},
{name: "123456badger"},
], {keys: ["name"]});
const results = qm.match('123456');
expect(results.length).toBe(2);
expect(results[0].name).toBe('123456badger');
expect(results[1].name).toBe('999999123456');
});
it('Returns results by function', function() {
const qm = new QueryMatcher(OBJECTS, {
keys: ["name"],
funcs: [x => x.name.replace('Mel', 'Emma')],
});
const results = qm.match('Emma');
expect(results.length).toBe(3);
expect(results[0].name).toBe('Mel B');
expect(results[1].name).toBe('Mel C');
expect(results[2].name).toBe('Emma');
});
it('Matches words only by default', function() {
const qm = new QueryMatcher(NONWORDOBJECTS, { keys: ["name"] });
const results = qm.match('bob');
expect(results.length).toBe(2);
expect(results[0].name).toBe('B.O.B');
expect(results[1].name).toBe('bob');
});
it('Matches all chars with words-only off', function() {
const qm = new QueryMatcher(NONWORDOBJECTS, {
keys: ["name"],
shouldMatchWordsOnly: false,
});
const results = qm.match('bob');
expect(results.length).toBe(1);
expect(results[0].name).toBe('bob');
});
it('Matches only by prefix with shouldMatchPrefix on', function() {
const qm = new QueryMatcher([
{name: "Victoria"},
{name: "Tori"},
], {
keys: ["name"],
shouldMatchPrefix: true,
});
const results = qm.match('tori');
expect(results.length).toBe(1);
expect(results[0].name).toBe('Tori');
});
});