David Baker 5 years ago
parent 7d59742a22
commit 6b6fa59f3e
  1. 14
      README.md
  2. 3
      config.sample.json
  3. 26
      docs/translating-dev.md
  4. 57
      docs/translating.md
  5. 1
      package.json
  6. 99
      scripts/copy-res.js
  7. 13
      src/components/structures/BottomLeftMenu.js
  8. 73
      src/components/structures/RightPanel.js
  9. 60
      src/components/structures/RoomDirectory.js
  10. 11
      src/components/structures/RoomSubList.js
  11. 3
      src/components/structures/SearchBox.js
  12. 23
      src/components/views/context_menus/MessageContextMenu.js
  13. 37
      src/components/views/context_menus/RoomTileContextMenu.js
  14. 13
      src/components/views/elements/ImageView.js
  15. 3
      src/components/views/globals/MatrixToolbar.js
  16. 17
      src/components/views/login/VectorCustomServerDialog.js
  17. 3
      src/components/views/login/VectorLoginFooter.js
  18. 37
      src/components/views/messages/DateSeparator.js
  19. 14
      src/components/views/rooms/DNDRoomTile.js
  20. 58
      src/components/views/settings/Notifications.js
  21. 1
      src/i18n/basefile.json
  22. 88
      src/i18n/be.json
  23. 84
      src/i18n/da.json
  24. 122
      src/i18n/de_DE.json
  25. 120
      src/i18n/en_EN.json
  26. 66
      src/i18n/fr.json
  27. 5
      src/i18n/ml.json
  28. 1
      src/i18n/pl.json
  29. 116
      src/i18n/pt.json
  30. 124
      src/i18n/pt_BR.json
  31. 119
      src/i18n/ru.json
  32. 1
      src/notifications/VectorPushRulesDefinitions.js
  33. 3
      src/skins/vector/css/matrix-react-sdk/structures/login/_Login.scss
  34. 46
      src/vector/index.js
  35. 200
      yarn-error.log
  36. 6076
      yarn.lock

@ -4,6 +4,8 @@ Riot
Riot (formerly known as Vector) is a Matrix web client built using the Matrix
React SDK (https://github.com/matrix-org/matrix-react-sdk).
[<img src="https://translate.nordgedanken.de/widgets/riot-web/-/multi-auto.svg" alt="Translationsstatus" width="340">](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget)
Getting Started
===============
@ -281,6 +283,16 @@ If any of these steps error with, `file table overflow`, you are probably on a m
which has a very low limit on max open files. Run `ulimit -Sn 1024` and try again.
You'll need to do this in each new terminal you open before building Riot.
How to add a new translation?
=============================
Head to the [translating doc](docs/translating.md)
Adding Strings to the translations (Developer Guide)
====================================================
Head to the [translating dev doc](docs/translating-dev.md)
Triaging issues
===============
@ -300,7 +312,7 @@ bug or feature:
* feature
bug severity:
* cosmetic - feature works functionally but UI/UX is broken
* critical - whole app doesn't work
* major - entire feature doesn't work

@ -10,5 +10,6 @@
"servers": [
"matrix.org"
]
}
},
"languages": ["en", "de", "pt-br", "ru", "da"]
}

@ -0,0 +1,26 @@
# How to translate riot-web (Dev Guide)
## Requirements
- A working [Development Setup](../../#setting-up-a-dev-environment)
- Be able to understand English
- Be able to understand the language you want to translate riot-web into
## Adding new strings
1. Check if the import ``import _t from 'counterpart-riot'`` is present. If not add it to the other import statements.
2. Add ``_t()`` to your string. (Don't forget curly braces when you assign an expression to JSX attributes in the render method)
3. Add the String to the ``en_EN.json`` file in ``src/i18n`` or if you are working in matrix-react-sdk you can find the json file in ``src/i18n/strings``
## Adding variables inside a string.
1. Extend your ``_t()`` call. Instead of ``_t(STRING)`` use ``_t(STRING, {})``
2. Decide how to name it. Please think about if the person who has to translate it can understand what it does.
3. Add it to the array in ``_t`` for example ``_t(STRING, {variable: this.variable})``
4. Add the variable inside the string. The syntax for variables is ``%(variable)s``. Please note the s at the end. The name of the variable has to match the previous used name.
## Things to know/Style Guides
- Do not use it inside ``getDefaultProps`` at the point where ``getDefaultProps`` is initialized the translations aren't loaded yet and it causes missing translations.
- Do use ``Array.push()`` instead of directly defining it inside the array. Arrays are not able to access ``_t()`` at runtime.
- Do not include full stops, Emoji or similiar miscellaneous Things to the strings. They are not required to be translated.

@ -0,0 +1,57 @@
# How to translate riot-web
## Requirements
- Web Browser
- Be able to understand English
- Be able to understand the language you want to translate riot-web into
## Step 1: Preparing your Weblate Profile
1. Head to https://translate.nordgedanken.de and register either via Github or email
2. After register check if you got a email to verify your account and click the link (if there is none head to step 1.4)
3. Log into weblate
4. Head to https://translate.nordgedanken.de/accounts/profile/ and select the languages you know and maybe another language you know too.
6. Head to https://translate.nordgedanken.de/accounts/profile/#subscriptions and select Riot Web as Project
## How to check if your language already is being translated
Go to https://translate.nordgedanken.de/projects/riot-web/ and in all 3 sub projects if your language is listed.
If it is listed go to Step 2a if not go to Step 2b
## Step 2a: Helping on existing languages.
1. Head to one of the projects listed https://translate.nordgedanken.de/projects/riot-web/
2. Click on the ``translate`` button on the right side of your language
3. Fill in the translations in the writeable field. You will see the original English string and the String of your second language above.
Head to the explanations under Steb 2b
## Step 2b: Adding a new language
1. Go to one of the projects listed https://translate.nordgedanken.de/projects/riot-web/
2. Click the ``Start new language`` button at the bottom
3. Select our language
4. Start translating like in 2a.3
5. Repeat these steps for the other projects which are listed at the link of step 2b.1
6. Add your language to the array at the [config example](../../blob/develop/config.sample.json#L14)
### What means the green button under the text field?
The green button let you save our translations directly. Please only use it if you are 100% sure about that translation. If you do not know a translation please DO NOT click that button. Use the arrows above the translations field and click to the right.
### What means the yellow button under the text field?
The yellow button has to be used if you are unsure about the translation but you have a rough idea. It ads a new suggestion to the string which can than be reviewed by others.
### What are "%(something)s"?
These things are variables that are filled inside the code. They can be room names, usernames or similiar. If you find one use it for changing the word order but do not delete it as thing are missing if you do so.
### "I want to come back to this string. How?"
You can use inside the translation field "Review needed" checkbox. It will be shown as Strings that need to be reviewed.
### Further reading
The official Doc provides some more in-deepth explanation on how to do translations and talks about do and don't's. You can find it at: https://docs.weblate.org/en/latest/user/translating.html

@ -56,6 +56,7 @@
"babel-runtime": "^6.11.6",
"browser-request": "^0.3.3",
"classnames": "^2.1.2",
"counterpart-riot": "^0.17.9",
"draft-js": "^0.8.1",
"extract-text-webpack-plugin": "^0.9.1",
"favico.js": "^0.3.10",

@ -13,12 +13,32 @@ const COPY_LIST = [
["src/skins/vector/{fonts,img}/**", "webapp"],
["node_modules/emojione/assets/svg/*", "webapp/emojione/svg/"],
["node_modules/emojione/assets/png/*", "webapp/emojione/png/"],
["./config.json", "webapp", {directwatch: 1}],
["./config.json", "webapp", { directwatch: 1 }],
["src/i18n/", "webapp/i18n/", { languages: 1 }],
["node_modules/matrix-react-sdk/src/i18n/strings/", "webapp/i18n/", { languages: 1 }],
];
const parseArgs = require('minimist');
const Cpx = require('cpx');
const chokidar = require('chokidar');
const fs = require('fs');
const rimraf = require('rimraf');
// cleanup language files before copying them.
//rimraf("webapp/", function () { console.log('cleanup language files'); });
//From http://stackoverflow.com/a/20525865/4929236
function generateFileArray(dir, files_) {
files_ = files_ || [];
var files = fs.readdirSync(dir);
for (var i in files) {
var name = files[i];
if (name != 'basefile.json') {
files_.push(name);
}
}
return files_;
}
const argv = parseArgs(
process.argv.slice(2), {}
@ -45,8 +65,32 @@ function next(i, err) {
const source = ent[0];
const dest = ent[1];
const opts = ent[2] || {};
let cpx = undefined;
const cpx = new Cpx.Cpx(source, dest);
if (opts.languages) {
const sourceFiles = generateFileArray(source);
let Sourcelanguages = {};
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest);
}
sourceFiles.forEach(file => {
const fileContents = fs.readFileSync(source + file).toString();
Sourcelanguages[file] = JSON.parse(fileContents);
});
sourceFiles.forEach(file => {
if (!fs.existsSync(dest + file)) {
let o = Object.assign({}, Sourcelanguages[file]);
fs.writeFileSync(dest + file, JSON.stringify(o, null, 4));
} else {
const fileContents = fs.readFileSync(dest + file).toString();
let o = Object.assign(JSON.parse(fileContents), Sourcelanguages[file]);
fs.writeFileSync(dest + file, JSON.stringify(o, null, 4));
}
});
} else {
cpx = new Cpx.Cpx(source, dest);
}
if (verbose) {
cpx.on("copy", (event) => {
@ -57,7 +101,7 @@ function next(i, err) {
});
}
const cb = (err) => {next(i+1, err)};
const cb = (err) => { next(i + 1, err) };
if (watch) {
if (opts.directwatch) {
@ -65,20 +109,65 @@ function next(i, err) {
// which in the case of config.json is '.', which inevitably takes
// ages to crawl. So we create our own watcher on the files
// instead.
const copy = () => {cpx.copy(errCheck)};
const copy = () => { cpx.copy(errCheck) };
chokidar.watch(source)
.on('add', copy)
.on('change', copy)
.on('ready', cb)
.on('error', errCheck);
} else if (opts.languages) {
if (verbose) {
console.log('don\'t copy language file');
}
next(i + 1, err);
} else {
cpx.on('watch-ready', cb);
cpx.on("watch-error", cb);
cpx.watch();
}
} else if (opts.languages) {
if (verbose) {
console.log('don\'t copy language file');
}
next(i + 1, err);
} else {
cpx.copy(cb);
}
}
next(0);
// Generate Language List
const testFolder = 'src/i18n/';
let languages = {};
// Check if webapp exists
if (!fs.existsSync('webapp')) {
fs.mkdirSync('webapp');
}
// Check if i18n exists
if (!fs.existsSync('webapp/i18n/')) {
fs.mkdirSync('webapp/i18n/');
}
if (!fs.existsSync('webapp/i18n/languages.json')) {
rimraf("webapp/i18n/languages.json", function() { console.log('cleanup languages.json file'); });
}
fs.readdir(testFolder, function(err, files) {
if (err) {
throw err;
}
files.forEach(function(file) {
var normalizedLanguage = file.toLowerCase().replace("_", "-").split('.json')[0];
var languageParts = normalizedLanguage.split('-');
if (file != 'basefile.json') {
if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
languages[languageParts[0]] = file;
} else {
languages[normalizedLanguage] = file;
}
}
});
fs.writeFile('webapp/i18n/languages.json', JSON.stringify(languages, null, 4));
})
next(0);

@ -18,7 +18,8 @@ limitations under the License.
var React = require('react');
var ReactDOM = require('react-dom');
var sdk = require('matrix-react-sdk')
var sdk = require('matrix-react-sdk');
import _t from 'counterpart-riot';
var dis = require('matrix-react-sdk/lib/dispatcher');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
@ -120,7 +121,7 @@ module.exports = React.createClass({
homeButton = (
<AccessibleButton className="mx_BottomLeftMenu_homePage" onClick={ this.onHomeClick } onMouseEnter={ this.onHomeMouseEnter } onMouseLeave={ this.onHomeMouseLeave } >
<TintableSvg src="img/icons-home.svg" width="25" height="25" />
{ this.getLabel("Welcome page", this.state.homeHover) }
{ this.getLabel(_t("Welcome page"), this.state.homeHover) }
</AccessibleButton>
);
}
@ -131,19 +132,19 @@ module.exports = React.createClass({
{ homeButton }
<AccessibleButton className="mx_BottomLeftMenu_people" onClick={ this.onPeopleClick } onMouseEnter={ this.onPeopleMouseEnter } onMouseLeave={ this.onPeopleMouseLeave } >
<TintableSvg src="img/icons-people.svg" width="25" height="25" />
{ this.getLabel("Start chat", this.state.peopleHover) }
{ this.getLabel(_t("Start chat"), this.state.peopleHover) }
</AccessibleButton>
<AccessibleButton className="mx_BottomLeftMenu_directory" onClick={ this.onDirectoryClick } onMouseEnter={ this.onDirectoryMouseEnter } onMouseLeave={ this.onDirectoryMouseLeave } >
<TintableSvg src="img/icons-directory.svg" width="25" height="25"/>
{ this.getLabel("Room directory", this.state.directoryHover) }
{ this.getLabel(_t("Room directory"), this.state.directoryHover) }
</AccessibleButton>
<AccessibleButton className="mx_BottomLeftMenu_createRoom" onClick={ this.onRoomsClick } onMouseEnter={ this.onRoomsMouseEnter } onMouseLeave={ this.onRoomsMouseLeave } >
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
{ this.getLabel("Create new room", this.state.roomsHover) }
{ this.getLabel(_t("Create new room"), this.state.roomsHover) }
</AccessibleButton>
<AccessibleButton className="mx_BottomLeftMenu_settings" onClick={ this.onSettingsClick } onMouseEnter={ this.onSettingsMouseEnter } onMouseLeave={ this.onSettingsMouseLeave } >
<TintableSvg src="img/icons-settings.svg" width="25" height="25" />
{ this.getLabel("Settings", this.state.settingsHover) }
{ this.getLabel(_t("Settings"), this.state.settingsHover) }
</AccessibleButton>
</div>
</div>

@ -16,14 +16,15 @@ limitations under the License.
'use strict';
var React = require('react');
var sdk = require('matrix-react-sdk');
var Matrix = require("matrix-js-sdk");
var dis = require('matrix-react-sdk/lib/dispatcher');
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');
var Modal = require('matrix-react-sdk/lib/Modal');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
import React from 'react';
import _t from 'counterpart-riot';
import sdk from 'matrix-react-sdk';
import Matrix from "matrix-js-sdk";
import dis from 'matrix-react-sdk/lib/dispatcher';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
import Modal from 'matrix-react-sdk/lib/Modal';
import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
module.exports = React.createClass({
displayName: 'RightPanel',
@ -34,7 +35,7 @@ module.exports = React.createClass({
collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel
},
Phase : {
Phase: {
MemberList: 'MemberList',
FilePanel: 'FilePanel',
NotificationPanel: 'NotificationPanel',
@ -91,8 +92,8 @@ module.exports = React.createClass({
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't invite users. Please register to invite."
title: _t('Please Register'),
description: _t('Guest users can\'t invite users. Please register to invite') + '.'
});
return;
}
@ -188,7 +189,7 @@ module.exports = React.createClass({
<div className="mx_RightPanel_icon" >
<TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
</div>
<div className="mx_RightPanel_message">Invite to this room</div>
<div className="mx_RightPanel_message">{ _t('Invite to this room') }</div>
</AccessibleButton>;
}
@ -197,30 +198,30 @@ module.exports = React.createClass({
if (this.props.roomId) {
buttonGroup =
<div className="mx_RightPanel_headerButtonGroup">
<AccessibleButton className="mx_RightPanel_headerButton"
title="Members" onClick={ this.onMemberListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span>&nbsp;</span>}</div>
<TintableSvg src="img/icons-people.svg" width="25" height="25"/>
{ membersHighlight }
</AccessibleButton>
<AccessibleButton
className="mx_RightPanel_headerButton mx_RightPanel_filebutton"
title="Files" onClick={ this.onFileListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-files.svg" width="25" height="25"/>
{ filesHighlight }
</AccessibleButton>
<AccessibleButton
className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton"
title="Notifications" onClick={ this.onNotificationListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-notifications.svg" width="25" height="25"/>
{ notificationsHighlight }
</AccessibleButton>
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" title="Hide panel" onClick={ this.onCollapseClick }>
<TintableSvg src="img/minimise.svg" width="10" height="16"/>
</div>
</div>;
<AccessibleButton className="mx_RightPanel_headerButton"
title={ _t('Members') } onClick={ this.onMemberListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span>&nbsp;</span>}</div>
<TintableSvg src="img/icons-people.svg" width="25" height="25"/>
{ membersHighlight }
</AccessibleButton>
<AccessibleButton
className="mx_RightPanel_headerButton mx_RightPanel_filebutton"
title={ _t('Files') } onClick={ this.onFileListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-files.svg" width="25" height="25"/>
{ filesHighlight }
</AccessibleButton>
<AccessibleButton
className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton"
title={ _t('Notifications') } onClick={ this.onNotificationListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-notifications.svg" width="25" height="25"/>
{ notificationsHighlight }
</AccessibleButton>
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" title="Hide panel" onClick={ this.onCollapseClick }>
<TintableSvg src="img/minimise.svg" width="10" height="16"/>
</div>
</div>;
}
if (!this.props.collapsed) {

@ -30,6 +30,8 @@ var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
var sanitizeHtml = require('sanitize-html');
var q = require('q');
import _t from 'counterpart-riot';
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
linkifyMatrix(linkify);
@ -80,8 +82,8 @@ module.exports = React.createClass({
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to get protocol list from Home Server",
description: "The Home Server may be too old to support third party networks",
title: _t('Failed to get protocol list from Home Server'),
description: _t('The Home Server may be too old to support third party networks'),
});
});
@ -176,8 +178,8 @@ module.exports = React.createClass({
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to get public room list",
description: ((err && err.message) ? err.message : "The server may be unavailable or overloaded"),
title: _t('Failed to get public room list'),
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded'))
});
});
},
@ -191,31 +193,31 @@ module.exports = React.createClass({
*/
removeFromDirectory: function(room) {
var alias = get_display_alias_for_room(room);
var name = room.name || alias || "Unnamed room";
var name = room.name || alias || _t('Unnamed room');
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
var desc;
if (alias) {
desc = `Delete the room alias '${alias}' and remove '${name}' from the directory?`;
desc = _t('Delete the room alias %(alias)s and remove %(name)s from the directory?', {alias: alias, name: name});
} else {
desc = `Remove '${name}' from the directory?`;
desc = _t('Remove %(name)s from the directory?', {name: name});
}
Modal.createDialog(QuestionDialog, {
title: "Remove from Directory",
title: _t('Remove from Directory'),
description: desc,
onFinished: (should_delete) => {
if (!should_delete) return;
var Loader = sdk.getComponent("elements.Spinner");
var modal = Modal.createDialog(Loader);
var step = `remove '${name}' from the directory.`;
var step = _t('remove %(name)s from the directory', {name: name}) + '.';
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
if (!alias) return;
step = 'delete the alias.';
step = _t('delete the alias') + '.';
return MatrixClientPeg.get().deleteAlias(alias);
}).done(() => {
modal.close();
@ -225,8 +227,8 @@ module.exports = React.createClass({
this.refreshRoomList();
console.error("Failed to " + step + ": " + err);
Modal.createDialog(ErrorDialog, {
title: "Failed to " + step,
description: ((err && err.message) ? err.message : "The server may be unavailable or overloaded"),
title: _t('Error'),
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded'))
});
});
}
@ -314,8 +316,8 @@ module.exports = React.createClass({
if (!fields) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Unable to join network",
description: "Riot does not know how to join a room on this network",
title: _t('Unable to join network'),
description: _t('Riot does not know how to join a room on this network'),
});
return;
}
@ -325,15 +327,15 @@ module.exports = React.createClass({
} else {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Room not found",
description: "Couldn't find a matching Matrix room",
title: _t('Room not found'),
description: _t('Couldn\'t find a matching Matrix room'),
});
}
}, (e) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Fetching third party location failed",
description: "Unable to look up room ID from server",
title: _t('Fetching third party location failed'),
description: _t('Unable to look up room ID from server'),
});
});
}
@ -353,8 +355,8 @@ module.exports = React.createClass({
if (!room.world_readable && !room.guest_can_join) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Failed to join the room",
description: "This room is inaccessible to guests. You may be able to join if you register."
title: _t('Failed to join the room'),
description: _t('This room is inaccessible to guests. You may be able to join if you register') + '.'
});
return;
}
@ -368,7 +370,7 @@ module.exports = React.createClass({
avatarUrl: room.avatar_url,
// XXX: This logic is duplicated from the JS SDK which
// would normally decide what the name is.
name: room.name || room_alias || "Unnamed room",
name: room.name || room_alias || _t('Unnamed room'),
};
}
// It's not really possible to join Matrix rooms by ID because the HS has no way to know
@ -393,18 +395,18 @@ module.exports = React.createClass({
var self = this;
var guestRead, guestJoin, perms;
for (var i = 0; i < rooms.length; i++) {
var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || "Unnamed room";
var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
guestRead = null;
guestJoin = null;
if (rooms[i].world_readable) {
guestRead = (
<div className="mx_RoomDirectory_perm">World readable</div>
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
);
}
if (rooms[i].guest_can_join) {
guestJoin = (
<div className="mx_RoomDirectory_perm">Guests can join</div>
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
);
}
@ -493,7 +495,7 @@ module.exports = React.createClass({
if (this.state.protocolsLoading) {
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" />
<SimpleRoomHeader title={ _t('Directory') } />
<Loader />
</div>
);
@ -511,7 +513,7 @@ module.exports = React.createClass({
// request from the scrollpanel because there isn't one
let scrollpanel_content;
if (rows.length == 0) {
scrollpanel_content = <i>No rooms to show</i>;
scrollpanel_content = <i>{ _t('No rooms to show') }</i>;
} else {
scrollpanel_content = <table ref="directory_table" className="mx_RoomDirectory_table">
<tbody>
@ -545,9 +547,9 @@ module.exports = React.createClass({
}
let placeholder = 'Search for a room';
let placeholder = _t('Search for a room');
if (!this.state.instanceId) {
placeholder = '#example:' + this.state.roomServer;
placeholder = _t('#example') + ':' + this.state.roomServer;
} else if (instance_expected_field_type) {
placeholder = instance_expected_field_type.placeholder;
}
@ -564,7 +566,7 @@ module.exports = React.createClass({
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" icon="img/icons-directory.svg"/>
<SimpleRoomHeader title={ _t('Directory') } icon="img/icons-directory.svg" />
<div className="mx_RoomDirectory_list">
<div className="mx_RoomDirectory_listheader">
<DirectorySearchBox

@ -20,7 +20,8 @@ var React = require('react');
var ReactDOM = require('react-dom');
var classNames = require('classnames');
var DropTarget = require('react-dnd').DropTarget;
var sdk = require('matrix-react-sdk')
var sdk = require('matrix-react-sdk');
import _t from 'counterpart-riot';
var dis = require('matrix-react-sdk/lib/dispatcher');
var Unread = require('matrix-react-sdk/lib/Unread');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
@ -463,7 +464,7 @@ var RoomSubList = React.createClass({
return (
<AccessibleButton className="mx_RoomSubList_ellipsis" onClick={this._showFullMemberList}>
<div className="mx_RoomSubList_line"></div>
<div className="mx_RoomSubList_more">more</div>
<div className="mx_RoomSubList_more">{ _t("more") }</div>
<div className={ badgeClasses }>{ content }</div>
</AccessibleButton>
);
@ -509,8 +510,8 @@ var RoomSubList = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + self.props.tagName + " to room" + err);
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + self.props.tagName + " to room",
description: ((err && err.message) ? err.message : "Operation failed"),
title: _t('Failed to add tag %(tagName)s to room', {tagName: self.props.tagName}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
break;
@ -530,7 +531,7 @@ var RoomSubList = React.createClass({
var target;
if (this.state.sortedList.length == 0 && this.props.editable) {
target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
target = <RoomDropTarget label={ _t("Drop here %(toAction)s", {toAction: this.props.verb}) }/>;
}
if (this.state.sortedList.length > 0 || this.props.editable) {

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
import _t from 'counterpart-riot';
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');
@ -134,7 +135,7 @@ module.exports = React.createClass({
className="mx_SearchBox_search"
value={ this.state.searchTerm }
onChange={ this.onChange }
placeholder="Filter room names"
placeholder={ _t('Filter room names') }
/>
];
}

@ -21,6 +21,7 @@ var React = require('react');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var dis = require('matrix-react-sdk/lib/dispatcher');
var sdk = require('matrix-react-sdk');
import _t from 'counterpart-riot';
var Modal = require('matrix-react-sdk/lib/Modal');
var Resend = require("matrix-react-sdk/lib/Resend");
import * as UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore';
@ -74,8 +75,8 @@ module.exports = React.createClass({
// display error message stating you couldn't delete this.
var code = e.errcode || e.statusCode;
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "You cannot delete this message. (" + code + ")"
title: _t('Error'),
description: _t('You cannot delete this message. (%(code)s)', {code: code})
});
}).done();
},
@ -121,7 +122,7 @@ module.exports = React.createClass({
if (eventStatus === 'not_sent') {
resendButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onResendClick}>
Resend
{ _t('Resend') }
</div>
);
}
@ -129,7 +130,7 @@ module.exports = React.createClass({
if (!eventStatus && !this.props.mxEvent.isRedacted()) { // sent and not redacted
redactButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
Redact
{ _t('Redact') }
</div>
);
}
@ -137,21 +138,21 @@ module.exports = React.createClass({
if (eventStatus === "queued" || eventStatus === "not_sent") {
cancelButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}>
Cancel Sending
{ _t('Cancel Sending') }
</div>
);
}
viewSourceButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
View Source
{ _t('View Source') }
</div>
);
if (this.props.mxEvent.getType() !== this.props.mxEvent.getWireType()) {
viewClearSourceButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onViewClearSourceClick}>
View Decrypted Source
{ _t('View Decrypted Source') }
</div>
);
}
@ -160,7 +161,7 @@ module.exports = React.createClass({
if (this.props.eventTileOps.isWidgetHidden()) {
unhidePreviewButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onUnhidePreviewClick}>
Unhide Preview
{ _t('Unhide Preview') }
</div>
)
}
@ -170,13 +171,13 @@ module.exports = React.createClass({
permalinkButton = (
<div className="mx_MessageContextMenu_field">
<a href={ "https://matrix.to/#/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }
target="_blank" rel="noopener" onClick={ this.closeMenu }>Permalink</a>
target="_blank" rel="noopener" onClick={ this.closeMenu }>{ _t('Permalink') }</a>
</div>
);
const quoteButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onQuoteClick}>
Quote
{ _t('Quote') }
</div>
);
@ -185,7 +186,7 @@ module.exports = React.createClass({
externalURLButton = (
<div className="mx_MessageContextMenu_field">
<a href={ this.props.mxEvent.event.content.external_url }
rel="noopener" target="_blank" onClick={ this.closeMenu }>Source URL</a>
rel="noopener" target="_blank" onClick={ this.closeMenu }>{ _t('Source URL') }</a>
</div>
);
}

@ -21,6 +21,7 @@ import q from 'q';
import React from 'react';
import classNames from 'classnames';
import sdk from 'matrix-react-sdk';
import _t from 'counterpart-riot';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import dis from 'matrix-react-sdk/lib/dispatcher';
import DMRoomMap from 'matrix-react-sdk/lib/utils/DMRoomMap';
@ -70,8 +71,8 @@ module.exports = React.createClass({
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to remove tag " + tagNameOff + " from room",
description: ((err && err.message) ? err.message : "Operation failed"),
title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOff}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
}
@ -87,8 +88,8 @@ module.exports = React.createClass({
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + tagNameOn + " to room",
description: ((err && err.message) ? err.message : "Operation failed"),
title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOn}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
}
@ -148,8 +149,8 @@ module.exports = React.createClass({
}, (err) => {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to set Direct Message status of room",
description: ((err && err.message) ? err.message : "Operation failed"),
title: _t('Failed to set Direct Message status of room'),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
},
@ -187,8 +188,8 @@ module.exports = React.createClass({
var errCode = err.errcode || "unknown error code";
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: `Failed to forget room (${errCode})`,
description: ((err && err.message) ? err.message : "Operation failed"),
title: _t('Failed to forget room %(errCode)s', {errCode: errCode}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
@ -274,22 +275,22 @@ module.exports = React.createClass({
<div className={ alertMeClasses } onClick={this._onClickAlertMe} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off-copy.svg" width="16" height="12" />
All messages (loud)
{ _t('All messages (loud)') }
</div>
<div className={ allNotifsClasses } onClick={this._onClickAllNotifs} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off.svg" width="16" height="12" />
All messages
{ _t('All messages') }
</div>
<div className={ mentionsClasses } onClick={this._onClickMentions} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-mentions.svg" width="16" height="12" />
Mentions only
{ _t('Mentions only') }
</div>
<div className={ muteNotifsClasses } onClick={this._onClickMute} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute.svg" width="16" height="12" />
Mute
{ _t('Mute') }
</div>
</div>
);
@ -306,16 +307,16 @@ module.exports = React.createClass({
switch (membership) {
case "join":
leaveClickHandler = this._onClickLeave;
leaveText = "Leave";
leaveText = _t('Leave');
break;
case "leave":
case "ban":
leaveClickHandler = this._onClickForget;
leaveText = "Forget";
leaveText = _t('Forget');
break;
case "invite":
leaveClickHandler = this._onClickReject;
leaveText = "Reject";
leaveText = _t('Reject');
break;
}
@ -353,17 +354,17 @@ module.exports = React.createClass({
<div className={ favouriteClasses } onClick={this._onClickFavourite} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_fave.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_fave_on.svg" width="15" height="15" />
Favourite
{ _t('Favourite') }
</div>
<div className={ lowPriorityClasses } onClick={this._onClickLowPriority} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_low.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_low_on.svg" width="15" height="15" />
Low Priority
{ _t('Low Priority') }
</div>
<div className={ dmClasses } onClick={this._onClickDM} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_person.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_person_on.svg" width="15" height="15" />
Direct Chat
{ _t('Direct Chat') }
</div>
</div>
);

@ -25,6 +25,7 @@ var filesize = require('filesize');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
const Modal = require('matrix-react-sdk/lib/Modal');
const sdk = require('matrix-react-sdk');
import _t from 'counterpart-riot';
module.exports = React.createClass({
displayName: 'ImageView',
@ -76,8 +77,8 @@ module.exports = React.createClass({
// display error message stating you couldn't delete this.
var code = e.errcode || e.statusCode;
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "You cannot delete this image. (" + code + ")"
title: _t('Error'),
description: _t('You cannot delete this image. (%(code)s)', {code: code})
});
}).done();
}
@ -150,14 +151,14 @@ module.exports = React.createClass({
var eventMeta;
if(showEventMeta) {
eventMeta = (<div className="mx_ImageView_metadata">
Uploaded on { DateUtils.formatDate(new Date(this.props.mxEvent.getTs())) } by { this.props.mxEvent.getSender() }
{ _t('Uploaded on %(date)s by %(user)s', {date: DateUtils.formatDate(new Date(this.props.mxEvent.getTs())), user: this.props.mxEvent.getSender()}) }
</div>);
}
var eventRedact;
if(showEventMeta) {
eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
Redact
{ _t('Redact') }
</div>);
}
@ -169,7 +170,7 @@ module.exports = React.createClass({
<img src={this.props.src} style={style}/>
<div className="mx_ImageView_labelWrapper">
<div className="mx_ImageView_label">
<AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt="Close"/></AccessibleButton>
<AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt={ _t('Close') }/></AccessibleButton>
<div className="mx_ImageView_shim">
</div>
<div className="mx_ImageView_name">
@ -178,7 +179,7 @@ module.exports = React.createClass({
{ eventMeta }
<a className="mx_ImageView_link" href={ this.props.src } download={ this.props.name } target="_blank" rel="noopener">
<div className="mx_ImageView_download">
Download this file<br/>
{ _t('Download this file') }<br/>
<span className="mx_ImageView_size">{ size_res }</span>
</div>
</a>

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
import _t from 'counterpart-riot';
var Notifier = require("matrix-react-sdk/lib/Notifier");
var sdk = require('matrix-react-sdk')
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
@ -37,7 +38,7 @@ module.exports = React.createClass({
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/>
<div className="mx_MatrixToolbar_content">
You are not receiving desktop notifications. <a className="mx_MatrixToolbar_link" onClick={ this.onClick }>Enable them now</a>
{ _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>
</div>

@ -15,6 +15,7 @@ limitations under the License.
*/
var React = require("react");
import _t from 'counterpart-riot';
module.exports = React.createClass({
displayName: 'VectorCustomServerDialog',
@ -26,24 +27,14 @@ module.exports = React.createClass({
return (
<div className="mx_ErrorDialog">
<div className="mx_Dialog_title">
Custom Server Options
{ _t('Custom Server Options') }
</div>
<div className="mx_Dialog_content">
<span>
You can use the custom server options to sign into other Matrix
servers by specifying a different Home server URL.
<br/>
This allows you to use Riot with an existing Matrix account on
a different home server.
<br/>
<br/>
You can also set a custom identity server but you won't be able to
invite users by email address, or be invited by email address yourself.
</span>
<span dangerouslySetInnerHTML={{__html: _t('customServer_text')}} />
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.props.onFinished} autoFocus={true}>
Dismiss
{ _t('Dismiss') }
</button>
</div>
</div>

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
import _t from 'counterpart-riot';
module.exports = React.createClass({
displayName: 'VectorLoginFooter',
@ -30,7 +31,7 @@ module.exports = React.createClass({
<a href="https://medium.com/@RiotChat">blog</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://twitter.com/@RiotChat">twitter</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://github.com/vector-im/vector-web">github</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://matrix.org">powered by Matrix</a>
<a href="https://matrix.org">{ _t('powered by Matrix') }</a>
</div>
);
}

@ -14,19 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
];
// 'use strict';
import React from 'react';
import _t from 'counterpart-riot';
import DateUtils from 'matrix-react-sdk/lib/DateUtils';
function getdaysArray() {
var days = [];
days.push(_t('Sunday'));
days.push(_t('Monday'));
days.push(_t('Tuesday'));
days.push(_t('Wednesday'));
days.push(_t('Thursday'));
days.push(_t('Friday'));
days.push(_t('Saturday'));
return days;
}
module.exports = React.createClass({
displayName: 'DateSeparator',
@ -34,19 +38,20 @@ module.exports = React.createClass({
var date = new Date(this.props.ts);
var today = new Date();
var yesterday = new Date();
var days = getdaysArray();
yesterday.setDate(today.getDate() - 1);
var label;
if (date.toDateString() === today.toDateString()) {
label = "Today";
label = _t('Today');
}
else if (date.toDateString() === yesterday.toDateString()) {
label = "Yesterday";
label = _t('Yesterday');
}
else if (today.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
label = days[date.getDay()];
}
else {
label = date.toDateString();
label = DateUtils.formatFullDate(date);
}
return (

@ -23,6 +23,7 @@ import {DropTarget} from 'react-dnd';
import dis from 'matrix-react-sdk/lib/dispatcher';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import sdk from 'matrix-react-sdk';
import _t from 'counterpart-riot';
import RoomTile from 'matrix-react-sdk/lib/components/views/rooms/RoomTile';
import * as Rooms from 'matrix-react-sdk/lib/Rooms';
import Modal from 'matrix-react-sdk/lib/Modal';
@ -90,8 +91,8 @@ var roomTileSource = {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to set direct chat tag " + err);