feature (translation): first step towards i18n #248 (#270)

This commit is contained in:
Mickael 2020-05-26 16:42:12 +10:00 committed by GitHub
parent 8c00161f73
commit f30787785c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 310 additions and 134 deletions

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { Input, Button, Modal, NgIf } from './';
import { alert } from '../helpers/';
import { Popup } from './popup';
import { t } from '../locales/';
export class ModalAlert extends Popup {
constructor(props){
@ -22,7 +23,7 @@ export class ModalAlert extends Popup {
onSubmit(e){
this.setState({appear: false}, () => {
requestAnimationFrame(() => this.state.fn())
requestAnimationFrame(() => this.state.fn && this.state.fn());
});
}
@ -36,7 +37,7 @@ export class ModalAlert extends Popup {
modalContentFooter(){
return (
<Button type="submit" theme="emphasis" onClick={this.onSubmit.bind(this)}>OK</Button>
<Button type="submit" theme="emphasis" onClick={this.onSubmit.bind(this)}>{ t("OK") }</Button>
);
}
}

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { Input, Button, Modal, NgIf } from './';
import { confirm } from '../helpers/';
import { Popup } from './popup';
import { t } from '../locales/';
export class ModalConfirm extends Popup{
constructor(props){
@ -44,8 +45,8 @@ export class ModalConfirm extends Popup{
modalContentFooter(){
return (
<div>
<Button type="button" onClick={this.no.bind(this)}>NO</Button>
<Button type="submit" theme="emphasis" onClick={this.yes.bind(this)}>YES</Button>
<Button type="button" onClick={this.no.bind(this)}>{ t("NO") } </Button>
<Button type="submit" theme="emphasis" onClick={this.yes.bind(this)}>{ t("YES") }</Button>
</div>
);
}

View file

@ -5,6 +5,7 @@ import { browserHistory, Redirect } from 'react-router';
import { Session, Admin } from '../model/';
import { Container, Loader, Icon, NgIf } from '../components/';
import { memory, currentShare } from '../helpers/';
import { t } from '../locales/';
import '../pages/error.scss';
@ -78,7 +79,7 @@ export function ErrorPage(WrappedComponent){
render(){
if(this.state.error !== null){
const message = this.state.error.message || "There is nothing in here";
const message = this.state.error.message || t("There is nothing in here");
return (
<div>
<Link onClick={this.navigate.bind(this)} to={`/${window.location.search}`} className="backnav">
@ -86,8 +87,8 @@ export function ErrorPage(WrappedComponent){
</Link>
<Container>
<div className="error-page">
<h1>Oops!</h1>
<h2>{message}</h2>
<h1>{ t("Oops!") }</h1>
<h2>{ message }</h2>
</div>
</Container>
</div>

View file

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Input, Textarea, Select, Enabler } from './';
import { FormObjToJSON, format, autocomplete, notify } from '../helpers/';
import { t } from '../locales/';
import "./formbuilder.scss";
@ -113,7 +114,7 @@ export class FormBuilder extends React.Component {
const FormElement = (props) => {
const id = props.id !== undefined ? {id: props.id} : {};
let struct = props.params;
let $input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="text" defaultValue={struct.value} placeholder={struct.placeholder} /> );
let $input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="text" defaultValue={struct.value} placeholder={ t(struct.placeholder) } /> );
switch(props.params["type"]){
case "text":
const onTextChange = (value) => {
@ -124,7 +125,7 @@ const FormElement = (props) => {
};
const list_id = struct.datalist ? "list_"+Math.random() : null;
$input = ( <Input list={list_id} onChange={(e) => onTextChange(e.target.value)} {...id} name={struct.label} type="text" value={struct.value || ""} placeholder={struct.placeholder} readOnly={struct.readonly}/> );
$input = ( <Input list={list_id} onChange={(e) => onTextChange(e.target.value)} {...id} name={struct.label} type="text" value={struct.value || ""} placeholder={ t(struct.placeholder) } readOnly={struct.readonly}/> );
if(list_id != null){
const filtered = function(multi, datalist, currentValue){
if(multi !== true || currentValue == null) return datalist;
@ -156,7 +157,7 @@ const FormElement = (props) => {
value = value === "" ? null : parseInt(value);
props.onChange(value);
};
$input = ( <Input onChange={(e) => onNumberChange(e.target.value)} {...id} name={struct.label} type="number" value={struct.value === null ? "" : struct.value} placeholder={struct.placeholder} /> );
$input = ( <Input onChange={(e) => onNumberChange(e.target.value)} {...id} name={struct.label} type="number" value={struct.value === null ? "" : struct.value} placeholder={ t(struct.placeholder) } /> );
break;
case "password":
const onPasswordChange = (value) => {
@ -165,7 +166,7 @@ const FormElement = (props) => {
}
props.onChange(value);
};
$input = ( <Input onChange={(e) => onPasswordChange(e.target.value)} {...id} name={struct.label} type="password" value={struct.value || ""} placeholder={struct.placeholder} /> );
$input = ( <Input onChange={(e) => onPasswordChange(e.target.value)} {...id} name={struct.label} type="password" value={struct.value || ""} placeholder={ t(struct.placeholder) } /> );
break;
case "long_password":
const onLongPasswordChange = (value) => {
@ -175,11 +176,11 @@ const FormElement = (props) => {
props.onChange(value);
};
$input = (
<Textarea {...id} disabledEnter={true} value={struct.value || ""} onChange={(e) => onLongPasswordChange(e.target.value)} type="text" rows="1" name={struct.label} placeholder={struct.placeholder} autoComplete="new-password" />
<Textarea {...id} disabledEnter={true} value={struct.value || ""} onChange={(e) => onLongPasswordChange(e.target.value)} type="text" rows="1" name={struct.label} placeholder={ t(struct.placeholder) } autoComplete="new-password" />
);
break;
case "long_text":
$input = ( <Textarea {...id} disabledEnter={true} value={struct.value || ""} onChange={(e) => props.onChange(e.target.value)} type="text" rows="3" name={struct.label} placeholder={struct.placeholder} autoComplete="new-password" /> );
$input = ( <Textarea {...id} disabledEnter={true} value={struct.value || ""} onChange={(e) => props.onChange(e.target.value)} type="text" rows="3" name={struct.label} placeholder={ t(struct.placeholder) } autoComplete="new-password" /> );
break;
case "bcrypt":
const onBcryptChange = (value) => {
@ -191,7 +192,7 @@ const FormElement = (props) => {
.then((bcrypt) => bcrypt.bcrypt_password(value))
.then((hash) => props.onChange(hash));
};
$input = ( <Input onChange={(e) => onBcryptChange(e.target.value)} {...id} name={struct.label} type="password" defaultValue={struct.value || ""} placeholder={struct.placeholder} /> );
$input = ( <Input onChange={(e) => onBcryptChange(e.target.value)} {...id} name={struct.label} type="password" defaultValue={struct.value || ""} placeholder={ t(struct.placeholder) } /> );
break;
case "hidden":
$input = ( <Input name={struct.label} type="hidden" defaultValue={struct.value} /> );
@ -200,16 +201,16 @@ const FormElement = (props) => {
$input = ( <Input onChange={(e) => props.onChange(e.target.checked)} {...id} name={struct.label} type="checkbox" checked={struct.value === null ? !!struct.default : struct.value} /> );
break;
case "select":
$input = ( <Select onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} choices={struct.options} value={struct.value === null ? struct.default : struct.value} placeholder={struct.placeholder} />);
$input = ( <Select onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} choices={struct.options} value={struct.value === null ? struct.default : struct.value} placeholder={ t(struct.placeholder) } />);
break;
case "enable":
$input = ( <Enabler onChange={(e) => props.onChange(e.target.checked)} {...id} name={struct.label} target={props.target} defaultValue={struct.value === null ? struct.default : struct.value} /> );
break;
case "date":
$input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="date" defaultValue={struct.value || ""} placeholder={struct.placeholder} /> );
$input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="date" defaultValue={struct.value || ""} placeholder={ t(struct.placeholder) } /> );
break;
case "datetime":
$input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="datetime-local" defaultValue={struct.value || ""} placeholder={struct.placeholder} /> );
$input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="datetime-local" defaultValue={struct.value || ""} placeholder={ t(struct.placeholder) } /> );
break;
case "image":
$input = ( <img {...id} src={struct.value} /> );

View file

@ -1,6 +1,7 @@
import React from 'react';
import { debounce } from '../helpers/';
import { Icon, Loader, NgIf } from './';
import { t } from '../locales/';
import './mapshot.scss';
export class MapShot extends React.Component {
@ -93,7 +94,7 @@ export class MapShot extends React.Component {
<div ref="$wrapper" className={"component_mapshot"+(this.state.tile_loaded === 9 ? " loaded" : "")+(this.state.error === true ? " error": "")} style={{height: (this.state.tile_size*3)+"px"}}>
<div className="wrapper">
<div className="mapshot_placeholder error">
<span><div>ERROR</div></span>
<span><div>t("ERROR")</div></span>
</div>
<div className="mapshot_placeholder loading">
<Loader/>
@ -122,5 +123,4 @@ export class MapShot extends React.Component {
</div>
);
}
}

View file

@ -3,6 +3,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import { NgIf, Icon } from './';
import { notify } from '../helpers/';
import { t } from '../locales/';
import './notification.scss';
export class Notification extends React.Component {
@ -71,7 +72,7 @@ export class Notification extends React.Component {
<NgIf key={this.state.message_text+this.state.message_type+this.state.appear} cond={this.state.appear === true} className="no-select">
<div className={"component_notification--container "+(this.state.message_type || 'info')}>
<div className="message">
{ this.state.message_text }
{ t(this.state.message_text || "") }
</div>
<div className="close" onClick={this.cancelAnimation.bind(this)}>
<Icon name="close" />

View file

@ -8,6 +8,7 @@
.buttons{
margin: 15px -20px 0 -20px;
display: flex;
> div {
display: flex;
width: 100%;
@ -19,6 +20,9 @@
width: 50%;
margin-left: auto;
}
button{
text-transform: uppercase;
}
}
.modal-error-message{
color: var(--error);

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { Input, Button, Modal, NgIf } from './';
import { prompt } from '../helpers/';
import { Popup } from './popup';
import { t } from '../locales/';
export class ModalPrompt extends Popup {
constructor(props){
@ -45,8 +46,8 @@ export class ModalPrompt extends Popup {
modalContentFooter(){
return (
<div>
<Button type="button" onClick={this.onCancel.bind(this)}>CANCEL</Button>
<Button type="submit" theme="emphasis" onClick={this.onSubmit.bind(this)}>OK</Button>
<Button type="button" onClick={this.onCancel.bind(this)}>{ t("CANCEL") }</Button>
<Button type="submit" theme="emphasis" onClick={this.onSubmit.bind(this)}>{ t("OK") }</Button>
</div>
);
}

View file

@ -4,6 +4,7 @@ import Path from 'path';
import { Files } from '../model/';
import { confirm, notify, upload } from '../helpers/';
import { Icon, NgIf } from './';
import { t } from '../locales/';
import './upload_queue.scss';
const MAX_POOL_SIZE = 15;
@ -76,7 +77,7 @@ export class UploadQueue extends React.Component {
if(navigator && navigator.clipboard){
navigator.clipboard.writeText(path);
notify.send("Copied to clipboard", "info");
notify.send(t("Copied to clipboard"), "info");
}
}
@ -282,15 +283,15 @@ export class UploadQueue extends React.Component {
speedStr = " ~ " + humanFileSize(avgSpeed) + "/s";
}
if (this.state.running) {
return "Running..." + speedStr;
return t("Running")+"..." + speedStr;
}
return "Done" + speedStr;
return t("Done") + speedStr;
}
onClose() {
if(this.state.running) {
confirm.now(
"Abort current uploads?",
t("Abort current uploads?"),
() => {
this.setState({
running: false,
@ -332,7 +333,7 @@ export class UploadQueue extends React.Component {
<NgIf cond={totalFiles > 0}>
<div className="component_upload_queue">
<h2>
CURRENT UPLOAD
{ t("CURRENT UPLOAD") }
<div className="count_block">
<span className="completed">{finished.length}</span>
<span className="grandTotal">{totalFiles}</span>
@ -344,7 +345,7 @@ export class UploadQueue extends React.Component {
{this.renderRows(
finished,
"done",
(_) => (<div className="file_state file_state_done">Done</div>),
(_) => (<div className="file_state file_state_done">{ t("Done") }</div>),
)}
{this.renderRows(
currents,
@ -362,7 +363,7 @@ export class UploadQueue extends React.Component {
processes,
"todo",
(_) => (
<div className="file_state file_state_todo">Waiting</div>
<div className="file_state file_state_todo">{ t("Waiting") }</div>
)
)}
{this.renderRows(
@ -371,9 +372,9 @@ export class UploadQueue extends React.Component {
(p) => (
(p.err && p.err.message == 'aborted')
?
<div className="file_state file_state_error">Aborted</div>
<div className="file_state file_state_error">{ t("Aborted") }</div>
:
<div className="file_state file_state_error">Error</div>
<div className="file_state file_state_error">{ t("Error") }</div>
),
(p) => (
<Icon name="refresh" onClick={(e) => this.retryFiles(p)} ></Icon>

View file

@ -15,3 +15,4 @@ export function decrypt(text, key){
new aesjs.ModeOfOperation.ctr(keyBytes, new aesjs.Counter(5)).decrypt(textBytes)
));
}

View file

@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
import Router from './router';
import { Config, Log } from "./model/";
import { http_get } from "./helpers/ajax";
import load from "little-loader";
import './assets/css/reset.scss';
@ -36,7 +37,7 @@ window.addEventListener("DOMContentLoaded", () => {
return Promise.resolve();
}
Promise.all([Config.refresh(), setup_xdg_open()]).then(() => {
Promise.all([Config.refresh(), setup_xdg_open(), translation()]).then(() => {
const timeSinceBoot = new Date() - window.initTime;
if(timeSinceBoot >= 1500){
const timeoutToAvoidFlickering = timeSinceBoot > 2500 ? 0 : 500;
@ -74,3 +75,18 @@ function setup_xdg_open(){
});
});
}
function translation(){
const userLanguage = navigator.language.split("-")[0];
const selectedLanguage = [
"fr",
// add new locales here
].indexOf(userLanguage) === -1 ? "en" : userLanguage;
if(selectedLanguage === "en"){
return Promise.resolve();
}
return http_get("/assets/locales/"+selectedLanguage+".json").then((d) => {
window.LNG = d;
});
}

108
client/locales/fr.json Normal file
View file

@ -0,0 +1,108 @@
{
"A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Un fichier nommé \"{{VALUE}}\" a été créé",
"A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Un dossier nommé \"{{VALUE}}\" a été créé",
"ABORTED": "avorté",
"ABORT_CURRENT_UPLOADS?": "Annuler les téléchargements en cours?",
"ACTIVITY": "activité",
"ADMIN_CONSOLE": "console administrateur",
"ADVANCED": "avancée",
"ALL_DONE": "Tout est bon!",
"ALREADY_EXIST": "existe déjà",
"BEAUTIFUL_URL": "id_du_lien",
"CAMERA": "appareil",
"CAN_RESHARE": "peut repartager",
"CANCEL": "annuler",
"CANNOT_ESTABLISH_A_CONNECTION": "Impossible d'établir une connexion",
"CANT_LOAD_THIS_PICTURE": "impossible de charger cette image",
"CANT_USE_FILESYSTEM": "Impossible d'utiliser le système de fichiers",
"CODE": "code",
"CONFIGURE": "configuration",
"CONFIRM_BY_TYPING": "confirmez en écrivant",
"CONNECT": "connection",
"COPIED_TO_CLIPBOARD": "Copié dans le presse-papier",
"TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "Congestion, réessayez plus tard",
"CREATE_A_NEW_LINK": "créer un lien partagé",
"CURRENT": "en cours",
"CURRENT_UPLOAD": "en cours",
"CUSTOM_LINK_URL": "URL du lien personnalisé",
"DASHBOARD": "dashboard",
"DATE": "date",
"DISPLAY_HIDDEN_FILES": "afficher les fichiers cachés",
"DO_YOU_WANT_TO_SAVE_THE_CHANGES": "voulez-vous enregistrer les modifications?",
"DOESNT_MATCH": "ne correspond pas",
"DONE": "terminé",
"DOWNLOAD": "Télécharger",
"DROP_HERE_TO_UPLOAD": "glisser-déposer pour charger vos fichiers et dossiers",
"EDITOR": "éditeur",
"EXISTING_LINKS": "liens existants",
"EXPIRATION": "expiration",
"EXPORT_AS_{{VALUE}}": "exporter au format {{VALUE}}",
"HIDE_HIDDEN_FILES": "masquer les fichiers cachés",
"EMPTY": "vide",
"ENDPOINT": "endpoint",
"ENCRYPTION_KEY": "clé de cryptage",
"ERROR": "erreur",
"FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "les dossiers d'accès fréquents apparaîtront ici",
"HOST_KEY": "clé d'hôte",
"HOSTNAME*": "nom d'hôte*",
"INCORRECT_PASSWORD": "mot de passe incorrect",
"INFO": "Information",
"INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "erreur interne: vous ne pouvez pas créer un {{VALUE}}",
"INVALID_PASSWORD": "mot de passe incorrect",
"INVALID_ACCOUNT": "compte invalide",
"LOCATION": "position",
"MISSING_DEPENDENCY": "dépendance manquante",
"NAVIGATE": "naviguer",
"NEW_FILE": "Nouveau Fichier",
"NEW_FILE::SHORT": "Nouv. Fichier",
"NEW_DIRECTORY": "Nouveau Dossier",
"NEW_DIRECTORY::SHORT": "Nouv. Dossier",
"NO": "non",
"NOT_ALLOWED": "interdit",
"NOT_AUTHORISED": "autorisation manquante",
"NOT_FOUND": "introuvable",
"NOT_IMPLEMENTED": "non implémenté",
"NOT_SUPPORTED": "non supporté",
"NOT_VALID": "pas valide",
"NUMBER_OF_CONNECTIONS": "nombre de connexion",
"OK": "ok",
"OOPS": "oops!",
"ONLY_FOR_USERS": "uniquement pour certains utilisateurs",
"PASSPHRASE": "mot de passe de clef",
"PATH": "chemin",
"PERMISSION_DENIED": "autorisation refusée",
"PROPERTIES": "propriétés",
"PORT": "port",
"PASSWORD": "mot de passe",
"PASSWORD_CANT_BE_EMPTY": "le mot de passe ne peut pas être vide",
"PICK_A_MASTER_PASSWORD": "choisissez un mot de passe principal",
"POWERED_BY": "propulsé par",
"PROTECT_ACCESS_WITH_A_PASSWORD": "protéger l'accès avec un mot de passe",
"REGION": "région",
"REMEMBER_ME": "se souvenir de moi",
"REMOVE": "supprimer",
"RESTRICTIONS": "restrictions",
"RUNNING": "en cours",
"SAVE_CURRENT_FILE": "enregistrer le fichier actuel",
"SEARCH": "recherche",
"SETTINGS": "réglage",
"SORT_BY_TYPE": "trier par type",
"SORT_BY_DATE": "trier par date",
"SORT_BY_NAME": "trier par nom",
"SUPPORT": "support",
"THERE_IS_NOTHING_HERE": "il n'y a rien ici",
"THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "le lien a été copié dans le presse-papiers",
"THE_LINK_WONT_BE_VALID_AFTER": "le lien ne sera plus valide après",
"THE_FILE_{{VALUE}}_WAS_RENAMED": "le fichier \"{{VALUE}}\" a été renommé",
"THE_FILE_{{VALUE}}_WAS_DELETED": "le fichier \"{{VALUE}}\" a été supprimé",
"TIMEOUT": "trop long",
"TODO": "todo",
"UPLOADER": "uploader",
"USERNAME": "nom d'utilisateur",
"VIEWER": "viewer",
"WAITING": "attente",
"YES": "oui",
"YOU_CANT_DO_THAT": "vous ne pouvez pas faire :)",
"YOUR_EMAIL_ADDRESS": "votre adresse email",
"YOUR_MASTER_PASSWORD": "votre mot de passe principal"
}

15
client/locales/index.js Normal file
View file

@ -0,0 +1,15 @@
export function t(str = "", replacementString, requestedKey){
const calculatedKey = str.toUpperCase().replace(/ /g, "_").replace(/[^a-zA-Z0-9\-\_\*\{\}\?]/g, "").replace(/\_+$/, "");
const value = requestedKey === undefined ? window.LNG && window.LNG[calculatedKey] : window.LNG && window.LNG[requestedKey];
return reformat(
value || str || "",
str
).replace("{{VALUE}}", replacementString);
}
function reformat(translated, initial){
if(initial[0] && initial[0].toLowerCase() === initial[0]){
return translated || "";
}
return (translated[0] && translated[0].toUpperCase() + translated.substring(1)) || "";
}

View file

@ -9,6 +9,7 @@ import { Icon, LoadingPage } from '../components/';
import { Config, Admin } from '../model';
import { notify } from '../helpers/';
import { HomePage, DashboardPage, ConfigPage, LogPage, PluginPage, SupportPage, SetupPage, LoginPage } from './adminpage/';
import { t } from '../locales/';
function AdminOnly(WrappedComponent){
@ -85,26 +86,26 @@ const SideMenu = (props) => {
<Icon name="arrow_left" />
<img src="/assets/logo/android-chrome-512x512.png" />
</NavLink>
<h2>Admin console</h2>
<h2>{ t("Admin console") }</h2>
<ul>
<li>
<NavLink activeClassName="active" to={props.url + "/dashboard"}>
Dashboard
{ t("Dashboard") }
</NavLink>
</li>
<li>
<NavLink activeClassName="active" to={props.url + "/configure"}>
Configure
{ t("Configure") }
</NavLink>
</li>
<li>
<NavLink activeClassName="active" to={props.url + "/activity"}>
Activity
{ t("Activity") }
</NavLink>
</li>
<li>
<NavLink activeClassName="active" to={props.url + "/support"}>
Support
{ t("Support") }
</NavLink>
</li>
</ul>

View file

@ -2,6 +2,7 @@ import React from 'react';
import { FormBuilder, Loader, Button, Icon } from '../../components/';
import { Config, Log } from '../../model/';
import { FormObjToJSON, notify, format } from '../../helpers/';
import { t } from '../../locales/';
import "./logger.scss";
@ -84,7 +85,7 @@ export class LogPage extends React.Component {
}
</pre>
<div>
<a href={Log.url()} download={filename()}><Button className="primary">Download</Button></a>
<a href={Log.url()} download={filename()}><Button className="primary">{ t("Download") }</Button></a>
</div>
</div>
);

View file

@ -3,6 +3,7 @@ import { Redirect } from 'react-router';
import { Input, Button, Container, Icon, Loader } from '../../components/';
import { Config, Admin } from '../../model/';
import { t } from '../../locales/';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
export class LoginPage extends React.Component {
@ -42,7 +43,7 @@ export class LoginPage extends React.Component {
return (
<Container maxWidth="300px" className="sharepage_component">
<form className={this.state.error ? "error" : ""} onSubmit={this.authenticate.bind(this)} style={marginTop()}>
<Input ref="$input" type="password" placeholder="Password" />
<Input ref="$input" type="password" placeholder={ t("Password") } />
<Button theme="transparent">
<Icon name={this.state.loading ? "loading" : "arrow_right"}/>
</Button>

View file

@ -4,6 +4,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import { ModalPrompt } from '../../components/';
import { memory, prompt, notify } from '../../helpers/';
import { t } from '../../locales/';
const CREDENTIALS_CACHE = "credentials",
CREDENTIALS_KEY = "credentials_key";
@ -53,9 +54,9 @@ export class Credentials extends React.Component {
promptForExistingPassword(){
prompt.now(
"Your Master Password",
t("Your Master Password"),
(key) => {
if(!key.trim()) return Promise.reject("Password can\'t be empty");
if(!key.trim()) return Promise.reject(t("Password can\'t be empty"));
this.setState({key: key});
memory.set(CREDENTIALS_KEY, key);
return this.hidrate_credentials(key);
@ -69,9 +70,9 @@ export class Credentials extends React.Component {
}
promptForNewPassword(){
prompt.now(
"Pick a Master Password",
t("Pick a Master Password"),
(key) => {
if(!key.trim()) return Promise.reject("Password can\'t be empty");
if(!key.trim()) return Promise.reject(t("Password can\'t be empty"));
memory.set(CREDENTIALS_KEY, key);
this.setState({key: key}, () => {
this.saveCreds(this.props.credentials);
@ -93,7 +94,7 @@ export class Credentials extends React.Component {
this.props.onCredentialsFound(credentials);
return Promise.resolve();
}catch(e){
return Promise.reject({message: "Incorrect password"});
return Promise.reject({message: t("Incorrect password")});
}
})
.catch((err) => notify.send(err && err.message, "error"))

View file

@ -1,5 +1,6 @@
import React from 'react';
import './forkme.scss';
import { t } from '../../locales/';
export const ForkMe = (props) => {
return (
@ -19,7 +20,7 @@ export const PoweredByFilestash = () => {
if(!window.CONFIG["fork_button"]) return null;
return (
<div className="component_poweredbyfilestash">
Powered by <strong><a href="https://www.filestash.app">Filestash</a></strong>
{ t('Powered by') } <strong><a href="https://www.filestash.app">Filestash</a></strong>
</div>
);
};

View file

@ -2,6 +2,7 @@ import React from "react";
import { Container, Card, NgIf, Input, Button, Textarea, FormBuilder } from "../../components/";
import { gid, settings_get, settings_put, createFormBackend, FormObjToJSON } from "../../helpers/";
import { Session, Backend } from "../../model/";
import { t } from "../../locales/";
import "./form.scss";
export class Form extends React.Component {
@ -136,7 +137,7 @@ export class Form extends React.Component {
} else if(struct.label === "advanced") return (
<label style={{color: "rgba(0,0,0,0.4)"}}>
{ $input }
advanced
{ t("Advanced") }
</label>
);
return (
@ -150,7 +151,7 @@ export class Form extends React.Component {
);
})
}
<Button theme="emphasis">CONNECT</Button>
<Button theme="emphasis">{ t("CONNECT") }</Button>
</form>
</div>
</Card>

View file

@ -36,6 +36,7 @@
button.emphasis{
margin-top: 10px;
color: white;
text-transform: uppercase;
}
.third-party{
text-align: center;

View file

@ -1,11 +1,12 @@
import React from 'react';
import { t } from '../../locales/';
import './rememberme.scss';
export const RememberMe = (props) => {
if(CONFIG.remember_me !== false){
return (
<label className="no-select component_rememberme">
<input checked={props.state} onChange={(e) => props.onChange(e.target.checked)} type="checkbox"/> Remember me
<input checked={props.state} onChange={(e) => props.onChange(e.target.checked)} type="checkbox"/> { t("Remember me") }
</label>
);
}

View file

@ -4,6 +4,7 @@ import { Files } from '../model/';
import { notify, upload } from '../helpers/';
import Path from 'path';
import { Observable } from "rxjs/Observable";
import { t } from '../locales/';
export const sort = function(files, type){
if(type === 'name'){
@ -73,7 +74,7 @@ export const onCreate = function(path, type, file){
if(type === 'file'){
return Files.touch(path, file)
.then(() => {
notify.send('A file named "'+Path.basename(path)+'" was created', 'success');
notify.send(t('A file named "{{VALUE}}" was created', Path.basename(path)), 'success');
return Promise.resolve();
})
.catch((err) => {
@ -82,34 +83,34 @@ export const onCreate = function(path, type, file){
});
}else if(type === 'directory'){
return Files.mkdir(path)
.then(() => notify.send('A folder named "'+Path.basename(path)+'" was created', 'success'))
.then(() => notify.send(t('A folder named "{{VALUE}}" was created"', Path.basename(path)), 'success'))
.catch((err) => notify.send(err, 'error'));
}else{
return Promise.reject({message: 'internal error: can\'t create a '+type.toString(), code: 'UNKNOWN_TYPE'});
return Promise.reject({message: t('internal error: can\'t create a {{VALUE}}', type.toString()), code: 'UNKNOWN_TYPE'});
}
};
export const onRename = function(from, to, type){
return Files.mv(from, to, type)
.then(() => notify.send('The file "'+Path.basename(from)+'" was renamed', 'success'))
.then(() => notify.send(t('The file "{{VALUE}}" was renamed', Path.basename(from)), 'success'))
.catch((err) => notify.send(err, 'error'));
};
export const onDelete = function(path, type){
return Files.rm(path, type)
.then(() => notify.send('The file "'+Path.basename(path)+'" was deleted', 'success'))
.then(() => notify.send(t('The file {{VALUE}} was deleted"', Path.basename(path)), 'success'))
.catch((err) => notify.send(err, 'error'));
};
export const onMultiDelete = function(arrOfPath){
return Promise.all(arrOfPath.map((p) => Files.rm(p)))
.then(() => notify.send('All done!', 'success'))
.then(() => notify.send(t('All done!'), 'success'))
.catch((err) => notify.send(err, 'error'));
}
export const onMultiRename = function(arrOfPath){
return Promise.all(arrOfPath.map((p) => Files.mv(p[0], p[1])))
.then(() => notify.send('All done!', 'success'))
.then(() => notify.send(t('All done!'), 'success'))
.catch((err) => notify.send(err, 'error'));
}

View file

@ -12,6 +12,7 @@ import { notify, debounce, goToFiles, goToViewer, event, settings_get, settings_
import { BreadCrumb, FileSystem, FrequentlyAccess, Submenu } from './filespage/';
import { MobileFileUpload } from './filespage/filezone';
import InfiniteScroll from 'react-infinite-scroller';
import { t } from '../locales/';
const PAGE_NUMBER_INIT = 2;
const LOAD_PER_SCROLL = 48;
@ -109,9 +110,9 @@ export class FilesPage extends React.Component {
this.setState({show_hidden: !this.state.show_hidden}, () => {
settings_put("filespage_show_hidden", this.state.show_hidden);
if(!!this.state.show_hidden){
notify.send("Display hidden files", "info");
notify.send(t("Display hidden files"), "info");
}else{
notify.send("Hide hidden files", "info");
notify.send(t("Hide hidden files"), "info");
}
});
this.onRefresh();

View file

@ -10,6 +10,7 @@ import { Container, NgIf, Icon } from '../../components/';
import { NewThing } from './thing-new';
import { ExistingThing } from './thing-existing';
import { FileZone } from './filezone';
import { t } from '../../locales/';
@DropTarget('__NATIVE_FILE__', {}, (connect, monitor) => ({
connectDropFile: connect.dropTarget(),
@ -40,7 +41,7 @@ export class FileSystem extends React.PureComponent {
<p className="empty_image">
<Icon name={this.props.isSearch ? "search" : "file"}/>
</p>
<p>There is nothing here</p>
<p>{ t("There is nothing here") }</p>
</NgIf>
</Container>
</div>

View file

@ -4,6 +4,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import { DropTarget } from 'react-dnd';
import { EventEmitter, Icon } from '../../components/';
import { t } from '../../locales/';
import './filezone.scss';
@EventEmitter
@ -23,7 +24,7 @@ export class FileZone extends React.Component{
render(){
return this.props.connectDropFile(
<div className={"component_filezone "+(this.props.fileIsOver ? "hover" : "")}>
DROP HERE TO UPLOAD
{ t("DROP HERE TO UPLOAD") }
</div>
);
}

View file

@ -6,6 +6,7 @@
margin-bottom: 10px;
text-align: center;
font-weight: bold;
text-transform: uppercase;
&.hover{
background: var(--emphasis-primary);
border: 2px dashed var(--primary);

View file

@ -4,6 +4,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import { Container, Icon, NgIf } from '../../components/';
import { Link } from 'react-router-dom';
import Path from 'path';
import { t } from '../../locales/';
import './frequently_access.scss';
@ -36,7 +37,7 @@ export class FrequentlyAccess extends React.Component {
<svg aria-hidden="true" focusable="false" data-icon="layer-group" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M12.41 148.02l232.94 105.67c6.8 3.09 14.49 3.09 21.29 0l232.94-105.67c16.55-7.51 16.55-32.52 0-40.03L266.65 2.31a25.607 25.607 0 0 0-21.29 0L12.41 107.98c-16.55 7.51-16.55 32.53 0 40.04zm487.18 88.28l-58.09-26.33-161.64 73.27c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.51 209.97l-58.1 26.33c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 276.3c16.55-7.5 16.55-32.5 0-40zm0 127.8l-57.87-26.23-161.86 73.37c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.29 337.87 12.41 364.1c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 404.1c16.55-7.5 16.55-32.5 0-40z"></path>
</svg>
Frequently access folders will be shown here
{ t("Frequently access folders will be shown here") }
</NgIf>
</Container>
</ReactCSSTransitionGroup>

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { NgIf, Icon, Button } from '../../components/';
import { Share } from '../../model/';
import { randomString, notify, absoluteToRelative, copyToClipboard, filetype } from '../../helpers/';
import { t } from '../../locales/';
import './share.scss';
export class ShareComponent extends React.Component {
@ -93,7 +94,7 @@ export class ShareComponent extends React.Component {
copyLinkInClipboard(link){
copyToClipboard(link);
notify.send("The link was copied in the clipboard", "INFO");
notify.send(t("The link was copied in the clipboard"), "INFO");
}
onRegisterLink(e){
@ -187,31 +188,31 @@ export class ShareComponent extends React.Component {
return (
<div className="component_share">
<h2>Create a New Link</h2>
<h2>{ t("Create a New Link") }</h2>
<div className="share--content link-type no-select">
{ this.props.type === "file" ? null :
<div onClick={this.updateState.bind(this, 'role', 'uploader')} className={this.state.role === "uploader" ? "active" : ""}>
Uploader
{ t("Uploader") }
</div>
}
<div onClick={this.updateState.bind(this, 'role', 'viewer')} className={this.state.role === "viewer" ? "active" : ""}>
Viewer
{ t("Viewer") }
</div>
<div onClick={this.updateState.bind(this, 'role', 'editor')} className={this.state.role === "editor" ? "active" : ""}>
Editor
{ t("Editor") }
</div>
</div>
<NgIf cond={this.state.role === null && !!this.state.existings && this.state.existings.length > 0}>
<h2>Existing Links</h2>
<h2>{ t("Existing Links") }</h2>
<div className="share--content existing-links" style={{"maxHeight": this.state.existings && this.state.existings.length > 5 ? '90px' : 'inherit'}}>
{
this.state.existings && this.state.existings.map((link, i) => {
return (
<div className="link-details" key={i}>
<span onClick={this.copyLinkInClipboard.bind(this, window.location.origin+"/s/"+link.id)} className="copy role">
{link.role}
{ t(link.role) }
</span>
<span onClick={this.copyLinkInClipboard.bind(this, window.location.origin+"/s/"+link.id)} className="copy path">{beautifulPath(this.props.path, link.path)}</span>
<Icon onClick={this.onDeleteLink.bind(this, link.id)} name="delete"/>
@ -224,24 +225,24 @@ export class ShareComponent extends React.Component {
</NgIf>
<NgIf cond={this.state.role !== null}>
<h2>Restrictions</h2>
<h2>{ t("Restrictions") }</h2>
<div className="share--content advanced-settings no-select">
<SuperCheckbox value={this.state.users} label="Only for users" placeholder="name0@email.com,name1@email.com" onChange={this.updateState.bind(this, 'users')} inputType="text"/>
<SuperCheckbox value={this.state.password} label="Password" placeholder="protect access with a password" onChange={this.updateState.bind(this, 'password')} inputType="password"/>
<SuperCheckbox value={this.state.users} label={ t("Only for users") } placeholder="name0@email.com,name1@email.com" onChange={this.updateState.bind(this, 'users')} inputType="text"/>
<SuperCheckbox value={this.state.password} label={ t("Password") } placeholder={ t("protect access with a password") } onChange={this.updateState.bind(this, 'password')} inputType="password"/>
</div>
<h2 className="no-select pointer" onClick={this.updateState.bind(this, 'show_advanced', !this.state.show_advanced)}>
Advanced
{ t("Advanced") }
<NgIf type="inline" cond={!!this.state.show_advanced}><Icon name="arrow_top"/></NgIf>
<NgIf type="inline" cond={!this.state.show_advanced}><Icon name="arrow_bottom"/></NgIf>
</h2>
<div className="share--content advanced-settings no-select">
<NgIf cond={this.state.show_advanced === true}>
<SuperCheckbox value={datify(this.state.expire)} label="Expiration" placeholder="The link won't be valid after" onChange={this.updateState.bind(this, 'expire')} inputType="date"/>
<SuperCheckbox value={datify(this.state.expire)} label={ t("Expiration") } placeholder={ t("The link won't be valid after") } onChange={this.updateState.bind(this, 'expire')} inputType="date"/>
<NgIf cond={this.state.role === "editor" && this.props.type !== "file"}>
<SuperCheckbox value={this.state.can_share} label="Can Reshare" onChange={this.updateState.bind(this, 'can_share')}/>
<SuperCheckbox value={this.state.can_share} label={ t("Can Reshare") } onChange={this.updateState.bind(this, 'can_share')}/>
</NgIf>
<SuperCheckbox value={this.state.url} label="Custom Link url" placeholder="beautiful_url" onChange={(val) => this.updateState('url', urlify(val))} inputType="text"/>
<SuperCheckbox value={this.state.url} label={ t("Custom Link url") } placeholder={ t("beautiful_url") } onChange={(val) => this.updateState('url', urlify(val))} inputType="text"/>
</NgIf>
</div>

View file

@ -4,6 +4,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import { Card, NgIf, Icon, EventEmitter, Dropdown, DropdownButton, DropdownList, DropdownItem, Container } from '../../components/';
import { pathBuilder, debounce, prompt } from '../../helpers/';
import { t } from '../../locales/';
import "./submenu.scss";
@EventEmitter
@ -49,7 +50,7 @@ export class Submenu extends React.Component {
}
onDelete(arrayOfPaths){
prompt.now(
"Confirm by typing \"remove\"",
t("Confirm by typing") + " \"remove\"",
(answer) => {
if(answer !== "remove"){
return Promise.resolve();
@ -116,14 +117,14 @@ export class Submenu extends React.Component {
<Container>
<div className={"menubar no-select "+(this.state.search_input_visible ? "search_focus" : "")}>
<NgIf cond={this.props.accessRight.can_create_file !== false && this.props.selected.length === 0} onClick={this.onNew.bind(this, 'file')} type="inline">
New File
{ window.innerWidth < 500 && t("New File").length > 10 ? t("New File", null, "NEW_FILE::SHORT") : t("New File") }
</NgIf>
<NgIf cond={this.props.accessRight.can_create_directory !== false && this.props.selected.length === 0} onClick={this.onNew.bind(this, 'directory')} type="inline">
New Directory
{ window.innerWidth < 500 && t("New Directory").length > 10 ? t("New Directory", null, "NEW_DIRECTORY::SHORT") : t("New Directory") }
</NgIf>
<NgIf cond={this.props.selected.length > 0} type="inline" onMouseDown={this.onDelete.bind(this, this.props.selected)}>
<ReactCSSTransitionGroup transitionName="submenuwithSelection" transitionLeave={false} transitionEnter={false} transitionAppear={true} transitionAppearTimeout={10000}>
<span>Remove</span>
<span>{ t("Remove") }</span>
</ReactCSSTransitionGroup>
</NgIf>
@ -132,9 +133,9 @@ export class Submenu extends React.Component {
<Icon name="sort"/>
</DropdownButton>
<DropdownList>
<DropdownItem name="type" icon={this.props.sort === "type" ? "check" : null}> Sort By Type </DropdownItem>
<DropdownItem name="date" icon={this.props.sort === "date" ? "check" : null}> Sort By Date </DropdownItem>
<DropdownItem name="name" icon={this.props.sort === "name" ? "check" : null}> Sort By Name </DropdownItem>
<DropdownItem name="type" icon={this.props.sort === "type" ? "check" : null}> { t("Sort By Type") } </DropdownItem>
<DropdownItem name="date" icon={this.props.sort === "date" ? "check" : null}> { t("Sort By Date") } </DropdownItem>
<DropdownItem name="name" icon={this.props.sort === "name" ? "check" : null}> { t("Sort By Name") } </DropdownItem>
</DropdownList>
</Dropdown>
<div className="view list-grid" onClick={this.onViewChange.bind(this)}><Icon name={this.props.view === "grid" ? "list" : "grid"}/></div>
@ -149,8 +150,8 @@ export class Submenu extends React.Component {
</NgIf>
</label>
<NgIf cond={this.state.search_input_visible !== null} type="inline">
<input ref="$input" onBlur={this.closeIfEmpty.bind(this, false)} style={{"width": this.state.search_input_visible ? "180px" : "0px"}} value={this.state.search_keyword} onChange={(e) => this.onSearchKeypress(e.target.value, true)} type="text" id="search" placeholder="search" name="search" autoComplete="off" />
<label htmlFor="search" className="hidden">search</label>
<input ref="$input" onBlur={this.closeIfEmpty.bind(this, false)} style={{"width": this.state.search_input_visible ? "180px" : "0px"}} value={this.state.search_keyword} onChange={(e) => this.onSearchKeypress(e.target.value, true)} type="text" id="search" placeholder={ t("search") } name="search" autoComplete="off" />
<label htmlFor="search" className="hidden">{ t("search") }</label>
</NgIf>
</form>
</NgIf>

View file

@ -10,6 +10,7 @@ import { Card, NgIf, Icon, EventEmitter, Button, img_placeholder } from '../../c
import { pathBuilder, basename, filetype, prompt, alert, leftPad, getMimeType, debounce, memory } from '../../helpers/';
import { Files } from '../../model/';
import { ShareComponent } from './share';
import { t } from '../../locales/';
const fileSource = {
beginDrag(props, monitor, component) {
@ -167,7 +168,7 @@ export class ExistingThing extends React.Component {
onDeleteRequest(filename){
prompt.now(
"Confirm by typing \""+this._confirm_delete_text()+"\"",
t("Confirm by typing") +" \""+this._confirm_delete_text()+"\"",
(answer) => { // click on ok
if(answer === this._confirm_delete_text()){
this.setState({icon: 'loading'});
@ -178,7 +179,7 @@ export class ExistingThing extends React.Component {
);
return Promise.resolve();
}else{
return Promise.reject("Doesn't match");
return Promise.reject(t("Doesn't match"));
}
},
() => { /* click on cancel */ });
@ -192,7 +193,7 @@ export class ExistingThing extends React.Component {
this.props.file.type
);
}else{
this.setState({delete_error: "Doesn't match"});
this.setState({delete_error: t("Doesn't match")});
}
}
onDeleteCancel(){

View file

@ -4,6 +4,7 @@ import { Redirect } from 'react-router';
import { Share } from '../model/';
import { notify, basename, filetype, findParams } from '../helpers/';
import { Loader, Input, Button, Container, ErrorPage, Icon, NgIf } from '../components/';
import { t } from '../locales/';
import './error.scss';
import './sharepage.scss';
@ -79,7 +80,7 @@ export class SharePage extends React.Component {
</div>
);
}
notify.send("You can't do that :)", "error");
notify.send(t("You can't do that :)"), "error");
}else if(filetype(this.state.path) === "directory"){
return ( <Redirect to={`/files/?share=${this.state.share}`} /> );
}else{
@ -95,7 +96,7 @@ export class SharePage extends React.Component {
return (
<Container maxWidth="300px" className="sharepage_component">
<form className={className} onSubmit={(e) => this.submitProof(e, "code", this.refs.$input.ref.value)} style={marginTop()}>
<Input ref="$input" type="text" placeholder="Code" />
<Input ref="$input" type="text" placeholder={ t("Code") } />
<Button theme="transparent">
<Icon name={this.state.loading ? "loading" : "arrow_right"}/>
</Button>
@ -106,7 +107,7 @@ export class SharePage extends React.Component {
return (
<Container maxWidth="300px" className="sharepage_component">
<form className={className} onSubmit={(e) => this.submitProof(e, "password", this.refs.$input.ref.value)} style={marginTop()}>
<Input ref="$input" type="password" placeholder="Password" />
<Input ref="$input" type="password" placeholder={ t("Password") } />
<Button theme="transparent">
<Icon name={this.state.loading ? "loading" : "arrow_right"}/>
</Button>
@ -117,7 +118,7 @@ export class SharePage extends React.Component {
return (
<Container maxWidth="300px" className="sharepage_component">
<form className={className} onSubmit={(e) => this.submitProof(e, "email", this.refs.$input.ref.value)} style={marginTop()}>
<Input ref="$input" type="text" placeholder="Your email address" />
<Input ref="$input" type="text" placeholder={ t("Your email address") } />
<Button theme="transparent">
<Icon name={this.state.loading ? "loading" : "arrow_right"}/>
</Button>
@ -128,8 +129,8 @@ export class SharePage extends React.Component {
return (
<div className="error-page">
<h1>Oops!</h1>
<h2>There's nothing in here</h2>
<h1>{ t("Oops!") }</h1>
<h2>{ t("There is nothing in here") }</h2>
</div>
);
}

View file

@ -3,10 +3,11 @@ import React from 'react';
import { MenuBar } from './menubar';
import { NgIf, Icon } from '../../components/';
import './filedownloader.scss';
import { t } from '../../locales/';
export class FileDownloader extends React.Component{
constructor(props){
super(props)
super(props);
this.state = {loading: false, id: null};
}
@ -16,7 +17,7 @@ export class FileDownloader extends React.Component{
loading: true,
id: window.setInterval(function(){
if(/download=yes/.test(document.cookie) === false){
this.setState({loading: false})
this.setState({loading: false});
window.clearInterval(this.state.id);
}
}.bind(this), 80)
@ -24,7 +25,7 @@ export class FileDownloader extends React.Component{
}
componentWillUnmount(){
window.clearInterval(this.state.id)
window.clearInterval(this.state.id);
}
render(){
@ -33,7 +34,7 @@ export class FileDownloader extends React.Component{
<div className="download_button">
<a download={this.props.filename} href={this.props.data}>
<NgIf onClick={this.onClick.bind(this)} cond={!this.state.loading}>
DOWNLOAD
{ t("DOWNLOAD") }
</NgIf>
</a>
<NgIf cond={this.state.loading}>

View file

@ -16,6 +16,7 @@
a > div {
font-size: 17px;
display: inline-block;
text-transform: uppercase;
}
// loading
> div {

View file

@ -9,7 +9,7 @@ import { confirm, currentShare } from '../../helpers/';
import { Editor } from './editor';
import { MenuBar } from './menubar';
import { OrgTodosViewer, OrgEventsViewer } from './org_viewer';
import { t } from '../../locales/';
import './ide.scss';
@ -32,7 +32,7 @@ export class IDE extends React.Component {
if(this.props.needSaving === false) return true;
confirm.now(
<div style={{textAlign: "center", paddingBottom: "5px"}}>
Do you want to save the changes ?
{ t("Do you want to save the changes ?") }
</div>,
() =>{
return this.save()
@ -121,15 +121,15 @@ export class IDE extends React.Component {
<Icon name={/download=yes/.test(document.cookie) ? "loading_white" : "download_white"}/>
</DropdownButton>
<DropdownList>
<DropdownItem name="na"><a download={this.props.filename} href={this.props.url}>Save current file</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/text/html"+this.props.path}>Export as HTML</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/application/pdf"+this.props.path}>Export as PDF</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/text/markdown"+this.props.path}>Export as Markdown</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/text/plain"+this.props.path}>Export as Text</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "tex")} href={"/api/export/"+(currentShare() || "private")+"/text/x-latex"+this.props.path}>Export as Latex</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "ics")} href={"/api/export/"+(currentShare() || "private")+"/text/calendar"+this.props.path}>Export as Calendar</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "odt")} href={"/api/export/"+(currentShare() || "private")+"/application/vnd.oasis.opendocument.text"+this.props.path}>Export as Open office</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "pdf")} href={"/api/export/"+(currentShare() || "private")+"/application/pdf"+this.props.path+"?mode=beamer"}>Export as Beamer</a></DropdownItem>
<DropdownItem name="na"><a download={this.props.filename} href={this.props.url}>{ t("Save current file") }</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/text/html"+this.props.path}>{ t("Export as {{VALUE}}", "HTML") }</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/application/pdf"+this.props.path}>{ t("Export as {{VALUE}}", "PDF") }</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/text/markdown"+this.props.path}>{ t("Export as {{VALUE}}", "Markdown") }</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "private")+"/text/plain"+this.props.path}>{ t("Export as {{VALUE}}", "TXT") }</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "tex")} href={"/api/export/"+(currentShare() || "private")+"/text/x-latex"+this.props.path}>{ t("Export as {{VALUE}}", "Latex") }</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "ics")} href={"/api/export/"+(currentShare() || "private")+"/text/calendar"+this.props.path}>{ t("Export as {{VALUE}}", "ical") }</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "odt")} href={"/api/export/"+(currentShare() || "private")+"/application/vnd.oasis.opendocument.text"+this.props.path}>{ t("Export as {{VALUE}}", "Open office") }</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "pdf")} href={"/api/export/"+(currentShare() || "private")+"/application/pdf"+this.props.path+"?mode=beamer"}>{ t("Export as {{VALUE}}", "Beamer") }</a></DropdownItem>
</DropdownList>
</Dropdown>

View file

@ -4,6 +4,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import { withRouter } from 'react-router-dom';
import { NgIf, Icon, EventReceiver, MapShot, Button } from '../../components/';
import { t } from '../../locales/';
import './image_exif.scss';
class Exif extends React.Component {
@ -152,19 +153,19 @@ export class SmallExif extends Exif{
return (
<div className="component_metadata">
<div>
<span className="label no-select">Date: </span>
<span className="label no-select">{ t("Date") }: </span>
<span className="value">{this.formatDate("-")}</span>
</div>
<div>
<span className="label no-select">Location: </span>
<span className="label no-select">{ t("Location") }: </span>
<span className="value small"><a href={"https://www.google.com/maps/search/?api=1&query="+display_location(this.state.location)}>{display_location(this.state.location)}</a></span>
</div>
<div>
<span className="label no-select">Settings: </span>
<span className="label no-select">{ t("Settings") }: </span>
<span className="value">{display_settings(this.state.aperture, this.state.shutter, this.state.iso)}</span>
</div>
<div>
<span className="label no-select">Camera: </span>
<span className="label no-select">{ t("Camera") }: </span>
<span className="value">{display_camera(this.state.model,this.state.focal)}</span>
</div>
</div>

View file

@ -6,6 +6,7 @@ import { MenuBar } from './menubar';
import { Bundle, Icon, NgIf, Loader, EventEmitter, EventReceiver } from '../../components/';
import { alert } from '../../helpers/';
import { Pager } from './pager';
import { t } from '../../locales/';
import './imageviewer.scss';
import './pager.scss';
@ -104,7 +105,7 @@ export class ImageViewer extends React.Component{
</div>
<div className={"images_aside scroll-y"+(this.state.show_exif ? " open": "")}>
<div className="header">
<div>Info</div>
<div>{ t("Info") }</div>
<div style={{flex: 1}}>
<Icon name="close" onClick={this.toggleExif.bind(this)} />
</div>
@ -230,7 +231,7 @@ class ImageFancy extends React.Component {
if(this.state.isError){
return (
<span className="error">
<div><div className="label">Can't load this picture</div></div>
<div><div className="label">{ t("Can't load this picture") }</div></div>
</span>
);
}

View file

@ -5,6 +5,7 @@ import { Modal, Container, NgIf, Icon, Dropdown, DropdownButton, DropdownList, D
import { extractEvents, extractTodos } from '../../helpers/org';
import { leftPad } from '../../helpers/common';
import { debounce } from '../../helpers/';
import { t } from '../../locales/';
import './org_viewer.scss';
export class OrgEventsViewer extends React.Component {
@ -256,13 +257,13 @@ class OrgViewer extends React.Component {
<h1>{this.props.title}</h1>
<NgIf className="search" cond={this.props.headlines.length > 0}>
<label className={this.state.search.length > 0 ? "active" : ""}>
<input type="text" onChange={(e) => this.search(e.target.value)} placeholder="Search ..."/>
<input type="text" onChange={(e) => this.search(e.target.value)} placeholder={t("Search")+" ..."}/>
<Icon name="search" />
</label>
</NgIf>
</div>
<NgIf cond={this.props.headlines.length === 0} className="nothing">
Nothing
{ t("empty") }
</NgIf>
<NgIf cond={this.props.headlines.length > 0}>
<StickyContainer className="container" style={{height: window.innerHeight > 750 ? 545 : window.innerHeight - 202}}>
@ -407,8 +408,8 @@ class Headline extends React.Component {
<Icon name="more" />
</DropdownButton>
<DropdownList>
<DropdownItem name="navigate" icon="arrow_right"> Navigate </DropdownItem>
<DropdownItem name="properties"> Properties </DropdownItem>
<DropdownItem name="navigate" icon="arrow_right"> { t("Navigate") } </DropdownItem>
<DropdownItem name="properties"> { t("Properties") } </DropdownItem>
</DropdownList>
</Dropdown>
</div>

View file

@ -17,13 +17,14 @@ var (
ErrPermissionDenied error = NewError("Permission Denied", 403)
ErrNotValid error = NewError("Not Valid", 405)
ErrConflict error = NewError("Already exist", 409)
ErrNotReachable error = NewError("Cannot Reach Destination", 502)
ErrNotReachable error = NewError("Cannot establish a connection", 502)
ErrInvalidPassword = NewError("Invalid Password", 403)
ErrNotImplemented = NewError("Not Implemented", 501)
ErrNotSupported = NewError("This feature is not supported", 501)
ErrNotSupported = NewError("Not supported", 501)
ErrFilesystemError = NewError("Can't use filesystem", 503)
ErrMissingDependency = NewError("Missing dependency", 424)
ErrNotAuthorized = NewError("Not authorized", 401)
ErrNotAuthorized = NewError("Not authorised", 401)
ErrAuthenticationFailed = NewError("Invalid account", 400)
ErrCongestion = NewError("Traffic congestion, try again later", 500)
ErrTimeout = NewError("Timeout", 500)
)

View file

@ -63,7 +63,7 @@ func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) {
home, err := model.GetHome(backend, session["path"])
if err != nil {
SendErrorResult(res, ErrInvalidPassword)
SendErrorResult(res, ErrAuthenticationFailed)
return
}

View file

@ -85,7 +85,7 @@ func (f Ftp) Init(params map[string]string, app *App) (IBackend, error) {
return backend, err
}
if _, err := client.ReadDir("/"); err != nil {
return backend, err
return backend, ErrAuthenticationFailed
}
backend = &Ftp{client}
}

View file

@ -193,7 +193,7 @@ func (s S3Backend) Cat(path string) (io.ReadCloser, error) {
} else if awsErr.Code() == "InvalidArgument" && strings.Contains(awsErr.Message(), "secret key was invalid") {
return nil, NewError("This file is encrypted file, you need the correct key!", 400)
} else if awsErr.Code() == "AccessDenied" {
return nil, NewError("Access denied", 403)
return nil, ErrNotAllowed
}
return nil ,err
}
@ -223,7 +223,7 @@ func (s S3Backend) Rm(path string) error {
client := s3.New(s.createSession(p.bucket))
if p.bucket == "" {
return NewError("Doesn't exist", 404)
return ErrNotFound
}
objs, err := client.ListObjects(&s3.ListObjectsInput{
@ -276,7 +276,7 @@ func (s S3Backend) Mv(from string, to string) error {
client := s3.New(s.createSession(f.bucket))
if f.path == "" {
return NewError("Can't move this", 403)
return ErrNotImplemented
}
input := &s3.CopyObjectInput{
@ -303,7 +303,7 @@ func (s S3Backend) Touch(path string) error {
client := s3.New(s.createSession(p.bucket))
if p.bucket == "" {
return NewError("Can't do that on S3", 403)
return ErrNotValid
}
input := &s3.PutObjectInput{
@ -324,7 +324,7 @@ func (s S3Backend) Save(path string, file io.Reader) error {
p := s.path(path)
if p.bucket == "" {
return NewError("Can't do that on S3", 403)
return ErrNotValid
}
uploader := s3manager.NewUploader(s.createSession(path))
input := s3manager.UploadInput{

View file

@ -93,13 +93,13 @@ func (s Sftp) Init(params map[string]string, app *App) (IBackend, error) {
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return &s, NewError("Connection denied", 502)
return &s, ErrAuthenticationFailed
}
s.SSHClient = client
session, err := sftp.NewClient(s.SSHClient)
if err != nil {
return &s, NewError("Can't establish connection", 502)
return &s, err
}
s.SFTPClient = session
SftpCache.Set(params, &s)

View file

@ -66,11 +66,12 @@ let config = {
}
}),
new CopyWebpackPlugin([
{ from: 'manifest.json', to: "assets/" },
{ from: 'worker/*.js', to: "assets/" },
{ from: 'assets/logo/*' },
{ from: 'assets/icons/*' },
{ from: 'assets/fonts/*' }
{ from: "locales/*.json", to: "assets/" },
{ from: "manifest.json", to: "assets/" },
{ from: "worker/*.js", to: "assets/" },
{ from: "assets/logo/*" },
{ from: "assets/icons/*" },
{ from: "assets/fonts/*" }
], { context: path.join(__dirname, 'client') }),
//new BundleAnalyzerPlugin()
]