0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-28 15:41:25 -05:00

♻️ Refactor all i18n subsystem.

This commit is contained in:
Andrey Antukh 2020-01-11 16:28:54 +01:00
parent b13488404e
commit f2d475d3d3
18 changed files with 1829 additions and 228 deletions

View file

@ -2,3 +2,117 @@
This guide intends to explain the essential details of the frontend
application.
**TODO**
## Translations (I18N) ##
### How it Works ###
All the translation strings of this application are stored in
`resources/locales.json` file. It has a self explanatory format that
looks like this:
```json
{
"auth.email-or-username" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:61" ],
"translations" : {
"en" : "Email or Username",
"fr" : "adresse email ou nom d'utilisateur"
}
},
"ds.num-projects" : {
"translations": {
"en": ["1 project", "%s projects"]
}
},
}
```
For development convenience, you can forget about the specific format
of that file, and just add a simple key-value entry pairs like this:
```
{
[...],
"foo1": "bar1",
"foo2": "bar2"
}
```
The file is automatically bundled into the `index.html` file on
compile time (in development and production). The bundled content is a
simplified version of this data structure for avoid load unnecesary
data.
The development environment has a watch process that detect changes on
that file and recompiles the `index.html`. **There are no hot reload
for translations strings**, you just need to refresh the browser tab
for refresh the translations in the running the application.
If you have used the short (key-value) format, the watch process will
automatically convert it to the apropriate format before generate the
`index.html`.
Finally, when you have finished to adding texts, execute the following
command for reformat the file, and track the usage locations (the
"used-in" list) before commiting the file into the repository:
```bash
clojure -Adev translations.clj collectmessages src/uxbox/main/ resources/locales.json
```
NOTE: Later, we will need to think and implement the way to export and
import to other formats (mainly for transifex and similar services
compatibility).
### How to use it ###
You have two aproaches for translate strings: one for general purpose
and other specific for React components (that leverages reactivity for
language changes).
The `uxbox.util.i18n/tr` is the general purpose function. This is a
simple use case example:
```clojure
(require '[uxbox.util.i18n :as i18n])
(i18n/tr "auth.email-or-username")
;; => "Email or Username"
```
If you have defined plurals for some translation resource, then you
need to pass an additional parameter marked as counter in order to
allow the system know when to show the plural:
```clojure
(require '[uxbox.util.i18n :as i18n])
(i18n/tr "ds.num-projects" (i18n/c 10))
;; => "10 projects"
(i18n/tr "ds.num-projects" (i18n/c 1))
;; => "1 project"
```
For React components, you have `uxbox.util.i18n/use-translations` hook:
```clojure
(mf/defc my-component
[props]
(let [tr (i18n/use-translations)]
[:div
[:span (tr "auth.email-or-username")]]))
```
You can use the general purpose function in React component but when
language is changed the component will not be rerendered
automatically.

View file

@ -15,7 +15,7 @@
funcool/cuerdas {:mvn/version "2.2.0"}
funcool/lentes {:mvn/version "1.4.0-SNAPSHOT"}
funcool/potok {:mvn/version "2.8.0-SNAPSHOT"}
funcool/promesa {:mvn/version "4.0.2"}
funcool/promesa {:mvn/version "5.0.0"}
funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"}
}
:paths ["src" "vendor" "resources" "../common"]
@ -25,7 +25,9 @@
{com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
com.bhauman/rebel-readline {:mvn/version "0.1.4"}
com.bhauman/figwheel-main {:mvn/version "0.2.3"}
org.clojure/tools.namespace {:mvn/version "0.3.1"}}
org.clojure/tools.namespace {:mvn/version "0.3.1"}
metosin/jsonista {:mvn/version "0.2.5"}
funcool/datoteka {:mvn/version "1.2.0"}}
:extra-paths ["test"]}
:repl

View file

@ -7,6 +7,8 @@ const rename = require("gulp-rename");
const gulpif = require("gulp-if");
const gzip = require("gulp-gzip");
const cleancss = require("gulp-clean-css");
const fs = require("fs");
const l = require("lodash");
const paths = {};
paths.app = "./resources/";
@ -79,6 +81,23 @@ gulp.task("scss:view", scssPipeline({
gulp.task("scss", gulp.parallel("scss:main", "scss:view"));
function readLocales() {
const path = __dirname + "/resources/locales.json";
const content = JSON.parse(fs.readFileSync(path, {encoding: "utf8"}));
let result = {};
for (let key of Object.keys(content)) {
const item = content[key];
if (l.isString(item)) {
result[key] = {"en": item};
} else if (l.isPlainObject(item) && l.isPlainObject(item.translations)) {
result[key] = item.translations;
}
}
return JSON.stringify(result);
}
// Templates
function templatePipeline(options) {
@ -86,8 +105,12 @@ function templatePipeline(options) {
const input = options.input;
const output = options.output;
const ts = Math.floor(new Date());
const locales = readLocales();
const tmpl = mustache({
ts: ts
ts: ts,
tr: JSON.stringify(locales),
});
return gulp.src(input)
@ -98,27 +121,28 @@ function templatePipeline(options) {
}
gulp.task("template:main", templatePipeline({
input: paths.app + "index.mustache",
output: paths.output,
input: paths.app + "templates/index.mustache",
output: paths.output
}));
gulp.task("template:view", templatePipeline({
input: paths.app + "view.mustache",
output: paths.output + "view/",
jspath: "/js/view.js",
csspath: "/css/view.css"
input: paths.app + "templates/view.mustache",
output: paths.output + "view/"
}));
gulp.task("template", gulp.parallel("template:view", "template:main"));
gulp.task("templates", gulp.parallel("template:view", "template:main"));
// Entry Point
gulp.task("watch:main", function() {
gulp.watch(paths.scss, gulp.task("scss"));
gulp.watch([paths.app + "templates/*.mustache",
paths.app + "locales.json"],
gulp.task("templates"));
});
gulp.task("watch", gulp.series(
gulp.parallel("scss", "template"),
gulp.parallel("scss", "templates"),
gulp.task("watch:main")
));
@ -142,7 +166,7 @@ gulp.task("dist:template:view", templatePipeline({
output: paths.dist + "view/",
}));
gulp.task("dist:template", gulp.parallel("dist:template:view", "dist:template:main"));
gulp.task("dist:templates", gulp.parallel("dist:template:view", "dist:template:main"));
// Styles
@ -184,7 +208,7 @@ gulp.task("dist:gzip", function() {
// Entry Point
gulp.task("dist", gulp.parallel(
gulp.task("dist:template"),
gulp.task("dist:templates"),
gulp.task("dist:scss"),
gulp.task("dist:copy")
));

223
frontend/legacy.edn Normal file
View file

@ -0,0 +1,223 @@
{"ds.projects" "PROJECTS"
"ds.new-file" "+ New File"
"ds.project-title" "Your projects"
"ds.project-new" "+ New project"
"ds.project-thumbnail.alt" "Project title"
"ds.ordering" "Sort by"
"ds.ordering.by-name" "name"
"ds.ordering.by-last-update" "last update"
"ds.ordering.by-creation-date" "creation date"
"ds.search.placeholder" "Search..."
"ds.uploaded-at" "Uploaded at %s"
"ds.updated-at" "Updated %s"
"ds.confirm-title" "Are you sure?"
"ds.confirm-ok" "Ok"
"ds.confirm-cancel" "Cancel"
"ds.multiselect-bar.copy" "Copy"
"ds.multiselect-bar.copy-to-library" "Copy to library"
"ds.multiselect-bar.move" "Move"
"ds.multiselect-bar.move-to-library" "Move to library"
"ds.multiselect-bar.rename" "Rename"
"ds.multiselect-bar.delete" "Delete"
"ds.elements" "ELEMENTS"
"ds.icons" "ICONS"
"ds.your-icons-title" "YOUR ICONS"
"ds.store-icons-title" "ICONS STORE"
"ds.icons-collection.new" "+ New collection"
"ds.icon-new" "+ New icon"
"ds.images" "IMAGES"
"ds.your-images-title" "YOUR IMAGES"
"ds.store-images-title" "IMAGES STORE"
"ds.images-collection.new" "+ New library"
"ds.image-new" "+ New image"
"ds.colors" "COLORS"
"ds.your-colors-title" "YOUR COLORS"
"ds.store-colors-title" "COLORS STORE"
"ds.colors-collection.new" "+ New library"
"ds.color-new" "+ New color"
"ds.color-lightbox.title" "New color"
"ds.color-lightbox.add" "+ Add color"
"ds.library-title" "Library: "
"ds.standard-title" "STANDARD"
"ds.your-libraries-title" "YOUR LIBRARIES"
"ds.default-library-title" "Unnamed Collection (%s)"
"ds.project.placeholder" "New project name"
"ds.project.new" "New project"
"common.accept" "Accept"
"common.cancel" "Cancel"
"workspace.sidebar.icons" "Icons"
"workspace.sidebar.element-options" "Element options"
"workspace.sidebar.draw-tools" "Draw tools"
"workspace.sidebar.sitemap" "Sitemap"
"workspace.sidebar.layers" "Layers"
"workspace.sidebar.document-history" "Document history"
"ds.page.placeholder" "Page name"
"ds.page.new" "New page"
"ds.page.edit" "Edit page"
"ds.history.versions" "History"
"ds.history.pinned" "Pinned"
"workspace.header.rect" "Box (Ctrl + B)"
"workspace.header.circle" "Circle (Ctrl + E)"
"workspace.header.line" "Line (Ctrl + L)"
"workspace.header.text" "Text"
"workspace.header.path" "Path"
"workspace.header.curve" "Curve"
"workspace.header.ruler" "Ruler"
"workspace.header.canvas" "Canvas"
"ds.user.profile" "Profile"
"ds.user.password" "Password"
"ds.user.notifications" "Notifications"
"ds.user.exit" "Exit"
"workspace.header.sitemap" "Sitemap (Ctrl + Shift + M)"
"workspace.header.draw-tools" "Draw tools (Ctrl + Shift + S)"
"workspace.header.color-palette" "Color Palette (---)"
"workspace.header.icons" "Icons (Ctrl + Shift + I)"
"workspace.header.layers" "Layers (Ctrl + Shift + L)"
"workspace.header.element-options" "Element options (Ctrl + Shift + O)"
"workspace.header.document-history" "History (Ctrl + Shift + H)"
"workspace.header.undo" "Undo (Ctrl + Z)"
"workspace.header.redo" "Redo (Ctrl + Shift + Z)"
"workspace.header.download" "Download (Ctrl + E)"
"workspace.header.image" "Image (Ctrl + I)"
"workspace.header.rules" "Rules"
"workspace.header.grid" "Grid (Ctrl + G)"
"workspace.header.grid-snap" "Snap to grid"
"workspace.header.align" "Align (Ctrl + A)"
"workspace.header.view-mode" "View mode (Ctrl + P)"
"workspace.options.radius" "Radius"
"workspace.options.size" "Size"
"workspace.options.width" "Width"
"workspace.options.height" "Height"
"workspace.options.stroke.style" "Style"
"workspace.options.stroke.none" "None"
"workspace.options.stroke.solid" "Solid"
"workspace.options.stroke.dotted" "Dotted"
"workspace.options.stroke.dashed" "Dashed"
"workspace.options.stroke.mixed" "Mixed"
"workspace.options.position" "Position"
"workspace.options.rotation" "Rotation"
"workspace.options.opacity" "Opacity"
"workspace.options.color" "Color"
"workspace.options.background-color" "Background color"
"workspace.options.font-family" "Font Family"
"workspace.options.font-weight" "Font Size & Weight"
"workspace.options.font-size" "Font Size"
"workspace.options.line-height-letter-spacing" "Line height and Letter spacing"
"workspace.options.line-height" "Line Height"
"workspace.options.letter-spacing" "Letter Spacing"
"workspace.options.text-align" "Text Alignment"
"workspace.options.name" "Name"
"workspace.options.go" "Go go go!"
"workspace.options.measures" "Size, position & rotation"
"workspace.options.font-options" "Fonts & Font Size"
"workspace.options.rotation-radius" "Rotation & Radius"
"workspace.options.stroke" "Stroke"
"workspace.options.grid-options" "Grid settings"
"element.fill" "Fill"
"element.text" "Text"
"element.interactions" "Interactions"
"image.new" "New image"
"image.select" "Select from library"
"image.upload" "Upload file"
"image.import-library" "Import image from library"
"auth.email-or-username" "Email or Username"
"auth.password" "Password"
"auth.signin" "Sign in"
"auth.forgot-password" "Forgot your password?"
"auth.no-account" "Don't have an account?"
"auth.message.recovery-token-sent" "Password recovery link sent to your inbox."
"auth.message.password-recovered" "Password successfully recovered."
"register.fullname.placeholder" "Full Name"
"register.username.placeholder" "Username"
"register.email.placeholder" "Email"
"register.password.placeholder" "Password"
"register.get-started" "Get started"
"register.already-have-account" "Already have an account?"
"recovery-request.username-or-email.placeholder" "username or email address"
"recovery-request.recover-password" "Recover password"
"recovery-request.go-back" "Go back!"
"recover.password.placeholder" "Password"
"recover.recover-password" "Recover password"
"recover.go-back" "Go back!"
"settings.profile" "PROFILE"
"settings.password" "PASSWORD"
"settings.notifications" "NOTIFICATIONS"
"settings.exit" "EXIT"
"settings.profile.profile-saved" "Profile saved successfully!"
"settings.profile.section-basic-data" "Name, username and email"
"settings.profile.section-i18n-data" "Default language"
"settings.profile.your-name" "Your name"
"settings.profile.your-username" "Your username"
"settings.profile.your-email" "Your email"
"settings.choose-color-theme" "Choose a theme"
"settings.profile.light-theme" "Light theme"
"settings.profile.dark-theme" "Dark theme"
"settings.profile.high-contrast-theme" "High-contrast theme"
"settings.profile.your-avatar" "Your avatar"
"settings.password.password-saved" "Password saved successfully!"
"settings.password.wrong-old-password" "Wrong old password"
"settings.password.change-password" "Change password"
"settings.password.old-password" "Old password"
"settings.password.new-password" "New password"
"settings.password.confirm-password" "Confirm password"
"settings.notifications.notifications-saved" "Notifications preferences saved successfully!"
"settings.notifications.prototype-notifications" "Prototype notifications"
"settings.notifications.description" "Get a roll up of prototype changes in your inbox."
"settings.notifications.none" "None"
"settings.notifications.every-hour" "Every hour"
"settings.notifications.every-day" "Every day"
"settings.update-settings" "Update settings"
"history.alert-message" "You are seeing version %s"
"errors.api.form.unexpected-error" "An unexpected error occurred."
"errors.api.form.old-password-not-match" "Incorrect old password"
"errors.api.form.registration-disabled" "The registration is currently disabled."
"errors.api.form.email-already-exists" "The email is already in use by another user."
"errors.api.form.username-already-exists" "The username is already in use by another user."
"errors.api.form.user-not-exists" "Username or email does not matches any existing user."
"errors.form.required" "This field is mandatory"
"errors.form.string" "Should be string"
"errors.form.number" "Invalid number"
"errors.form.integer" "Invalid integer"
"errors.form.bool" "Should be boolean"
"errors.form.min-len" "Should be greater than %s"
"errors.form.max-len" "Should be lesser than %s"
"errors.form.color" "Should be a valid color string"
"errors.form.password-not-match" "Password does not match"
"errors.auth.unauthorized" "Username or password seems to be wrong."
"errors.auth.invalid-recovery-token" "The recovery token is invalid."
"errors.profile.update-password" "Error updating password, probably your old password is wrong."
"errors.network" "Unable to connect to backend server."
"errors.generic" "Something wrong has happened."
"errors.conflict" "Conflict on saving data, please refresh and try again."
}

223
frontend/legacy.fr.edn Normal file
View file

@ -0,0 +1,223 @@
{"ds.projects" "PROJETS"
"ds.project-title" "Vos projets"
"ds.project-new" "+ Nouveau projet"
"ds.project-thumbnail.alt" "Titre du projet"
"ds.ordering" "Trier par"
"ds.ordering.by-name" "nom"
"ds.ordering.by-last-update" "dernière mise à jour"
"ds.ordering.by-creation-date" "date de création"
"ds.search.placeholder" "Rechercher..."
"ds.uploaded-at" "Mise en ligne : %s"
"ds.updated-at" "Mis à jour %s"
"ds.confirm-title" "Êtes-vous sûr ?"
"ds.confirm-ok" "Ok"
"ds.confirm-cancel" "Annuler"
"ds.multiselect-bar.copy" "Copier"
"ds.multiselect-bar.copy-to-library" "Copier vers la librairie"
"ds.multiselect-bar.move" "Déplacer"
"ds.multiselect-bar.move-to-library" "Déplacer vers la librairie"
"ds.multiselect-bar.rename" "Renommer"
"ds.multiselect-bar.delete" "Supprimer"
"ds.elements" "ÉLÉMENTS"
"ds.icons" "ICÔNES"
"ds.your-icons-title" "VOS ICÔNES"
"ds.store-icons-title" "BOUTIQUE"
"ds.icons-collection.new" "+ Nouvelle collection"
"ds.icon-new" "+ Nouvel icône"
"ds.images" "IMAGES"
"ds.your-images-title" "VOS IMAGES"
"ds.store-images-title" "BOUTIQUE"
"ds.images-collection.new" "+ Nouvelle librairie"
"ds.image-new" "+ Nouvelle image"
"ds.colors" "COULEURS"
"ds.your-colors-title" "VOS COULEURS"
"ds.store-colors-title" "BOUTIQUE"
"ds.colors-collection.new" "+ Nouvelle librairie"
"ds.color-new" "+ Nouvelle couleur"
"ds.color-lightbox.title" "Nouvelle couleur"
"ds.color-lightbox.add" "+ Ajouter couleur"
"ds.library-title" "Librairie : "
"ds.standard-title" "STANDARD"
"ds.your-libraries-title" "VOS LIBRAIRIES"
"ds.default-library-title" "Collection sans nom (%s)"
"ds.project.placeholder" "Nom du nouveau projet"
"ds.project.new" "Nouveau projet"
"ds.accept" "Accepter"
"ds.cancel" "Annuler"
"workspace.sidebar.icons" "Icônes"
"workspace.sidebar.element-options" "Options d'élément"
"workspace.sidebar.draw-tools" "Outils de dessin"
"workspace.sidebar.sitemap" "Plan du site"
"workspace.sidebar.layers" "Couches"
"workspace.sidebar.document-history" "Historique du document"
"ds.page.placeholder" "Nom de la page"
"ds.page.new" "Nouvelle page"
"ds.page.edit" "Éditer la page"
"ds.history.versions" "Historique"
"ds.history.pinned" "Épinglés"
"workspace.header.rect" "Boîte (Ctrl + B)"
"workspace.header.circle" "Cercle (Ctrl + E)"
"workspace.header.line" "Ligne (Ctrl + L)"
"workspace.header.text" "Texte"
"workspace.header.path" "Chemin"
"workspace.header.curve" "Courbe"
"workspace.header.ruler" "Règle"
"workspace.header.canvas" "Calque"
"ds.user.profile" "Profil"
"ds.user.password" "Mot de passe"
"ds.user.notifications" "Notifications"
"ds.user.exit" "Quitter"
"workspace.header.sitemap" "Plan du site (Ctrl + Maj + M)"
"workspace.header.draw-tools" "Outils de dessin (Ctrl + Maj + S)"
"workspace.header.color-palette" "Palette de couleurs (---)"
"workspace.header.icons" "Icônes (Ctrl + Maj + I)"
"workspace.header.layers" "Couches (Ctrl + Maj + L)"
"workspace.header.element-options" "Options d'élément (Ctrl + Maj + O)"
"workspace.header.document-history" "Historique du document (Ctrl + Maj + H)"
"workspace.header.undo" "Annuler (Ctrl + Z)"
"workspace.header.redo" "Rétablir (Ctrl + Maj + Z)"
"workspace.header.download" "Télécharger (Ctrl + E)"
"workspace.header.image" "Image (Ctrl + I)"
"workspace.header.rules" "Règles"
"workspace.header.grid" "Grille (Ctrl + G)"
"workspace.header.grid-snap" "Coller à la grille"
"workspace.header.align" "Aligner (Ctrl + A)"
"workspace.header.view-mode" "Mode visualisation (Ctrl + P)"
"workspace.options.radius" "Rayon"
"workspace.options.size" "Taille"
"workspace.options.width" "Largeur"
"workspace.options.height" "Hauteur"
"workspace.options.stroke.style" "Style"
"workspace.options.stroke.none" "Aucun"
"workspace.options.stroke.solid" "Solide"
"workspace.options.stroke.dotted" "Pointillé"
"workspace.options.stroke.dashed" "Tiré"
"workspace.options.stroke.mixed" "Mixte"
"workspace.options.position" "Position"
"workspace.options.rotation" "Rotation"
"workspace.options.opacity" "Opacité"
"workspace.options.color" "Couleur"
"workspace.options.background-color" "Couleur d'arrière-plan"
"workspace.options.font-family" "Police de caractères"
"workspace.options.font-weight" "Taille et graisse"
"workspace.options.font-size" "Taille de police"
"workspace.options.line-height-letter-spacing" "Hauteur de ligne et Espacement de caractères"
"workspace.options.line-height" "Hauteur de ligne"
"workspace.options.letter-spacing" "Espacement de caractères"
"workspace.options.text-align" "Alignement de texte"
"workspace.options.name" "Nom"
"workspace.options.go" "C'est parti !"
"workspace.options.measures" "Taille, position et rotation"
"workspace.options.font-options" "TODO"
"workspace.options.rotation-radius" "TODO"
"workspace.options.strokestroke" "Contour"
"workspace.options.grid-options" "Paramètres de la grille"
"element.fill" "Fond"
"element.text" "Texte"
"element.interactions" "Interactions"
"image.new" "Nouvelle image"
"image.select" "Choisir depuis une librairie"
"image.upload" "Envoyer un fichier"
"image.import-library" "Importer une image depuis une librairie"
"auth.email-or-username" "adresse email ou nom d'utilisateur"
"auth.password" "Mot de passe"
"auth.signin" "Se connecter"
"auth.forgot-password" "Mot de passe oublié ?"
"auth.no-account" "Vous n'avez pas de compte ?"
"auth.message.recovery-token-sent" "Lien de récupération de mot de passe envoyé."
"auth.message.password-recovered" "Mot de passe récupéré avec succès."
"register.fullname.placeholder" "Nom complet"
"register.username.placeholder" "Nom d'utilisateur"
"register.email.placeholder" "Adresse email"
"register.password.placeholder" "Mot de passe"
"register.get-started" "Commencer"
"register.already-have-account" "Vous avez déjà un compte ?"
"recovery-request.username-or-email.placeholder" "nom d'utilisateur ou adresse email"
"recovery-request.recover-password" "Récupérer le mot de passe"
"recovery-request.go-back" "Retour!"
"recover.password.placeholder" "Mot de passe"
"recover.recover-password" "Récupérer le mot de passe"
"recover.go-back" "Retour!"
"settings.profile" "PROFIL"
"settings.password" "MOT DE PASSE"
"settings.notifications" "NOTIFICATIONS"
"settings.exit" "QUITTER"
"settings.profile.profile-saved" "Profil enregistré avec succès !"
"settings.profile.section-basic-data" "Nom, nom d'utilisateur et adresse email"
"settings.profile.section-i18n-data" "Langue par défaut"
"settings.profile.your-name" "Votre nom complet"
"settings.profile.your-username" "Votre nom d'utilisateur"
"settings.profile.your-email" "Votre adresse email"
"settings.choose-color-theme" "Choisissez un thème"
"settings.profile.light-theme" "Thème Jour"
"settings.profile.dark-theme" "Thème Nuit"
"settings.profile.high-contrast-theme" "Thème Contraste élevé"
"settings.profile.your-avatar" "Votre avatar"
"settings.password.password-saved" "Mot de passe enregistré avec succès !"
"settings.password.wrong-old-password" "Ancien mot de passe incorrect"
"settings.password.change-password" "Changement de mot de passe"
"settings.password.old-password" "Ancien mot de passe"
"settings.password.new-password" "Nouveau mot de passe"
"settings.password.confirm-password" "Confirmez mot de passe"
"settings.notifications.notifications-saved" "Préférences de notifications enregistrées avec succès !"
"settings.notifications.prototype-notifications" "Notifications de prototypage"
"settings.notifications.description" "Obtenez un résumé des modifications apportées aux prototypes à votre adresse email."
"settings.notifications.none" "Aucune"
"settings.notifications.every-hour" "Chaque heure"
"settings.notifications.every-day" "Chaque jour"
"settings.update-settings" "Mettre à jour les paramètres"
"history.alert-message" "Vous voyez la version %s"
"errors.api.form.unexpected-error" "Une erreur inattendue c'est produite"
"errors.api.form.old-password-not-match" "Ancien mot de passe incorrect"
"errors.api.form.registration-disabled" "L'enregistrement est actuellement désactivé."
"errors.api.form.email-already-exists" "L'email est déjà utilisé par un autre utilisateur."
"errors.api.form.username-already-exists" "Le nom d'utilisateur est déjà utilisé par un autre utilisateur."
"errors.api.form.user-not-exists" "Le nom d'utilisateur ou l'e-mail ne correspond à aucun utilisateur existant."
"errors.form.required" "Ce champ est obligatoire"
"errors.form.string" "Devrait être une chaîne de caractères"
"errors.form.number" "Nombre invalide"
"errors.form.integer" "Entier invalide"
"errors.form.bool" "Devrait être un booléen"
"errors.form.min-len" "Devrait être supérieur à %s"
"errors.form.max-len" "Devrait être inférieur à %s"
"errors.form.color" "Devrait être une couleur valide"
"errors.form.password-not-match" "Le mot de passe ne correspond pas"
"errors.auth.unauthorized" "Le nom d'utilisateur ou le mot de passe semble être faux."
"errors.auth.invalid-recovery-token" "Le jeton de récupération n'est pas valide."
"errors.profile.update-password" "Erreur lors de la mise à jour du mot de passe, votre ancien mot de passe est probablement incorrect."
"errors.network" "Impossible de se connecter au serveur principal."
"errors.generic" "Quelque chose c'est mal passé."
"errors.conflict" "Conflit sur la sauvegarde des données, actualisez et réessayez."
}

View file

@ -0,0 +1,891 @@
{
"auth.email-or-username" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:61" ],
"translations" : {
"en" : "Email or Username",
"fr" : "adresse email ou nom d'utilisateur"
}
},
"auth.forgot-password" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:83" ],
"translations" : {
"en" : "Forgot your password?",
"fr" : "Mot de passe oublié ?"
}
},
"auth.message.password-recovered" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:178" ],
"translations" : {
"en" : "Password successfully recovered.",
"fr" : "Mot de passe récupéré avec succès."
}
},
"auth.message.recovery-token-sent" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:141" ],
"translations" : {
"en" : "Password recovery link sent to your inbox.",
"fr" : "Lien de récupération de mot de passe envoyé."
}
},
"auth.no-account" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:86" ],
"translations" : {
"en" : "Don't have an account?",
"fr" : "Vous n'avez pas de compte ?"
}
},
"auth.password" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:70" ],
"translations" : {
"en" : "Password",
"fr" : "Mot de passe"
}
},
"auth.signin" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:77" ],
"translations" : {
"en" : "Sign in",
"fr" : "Se connecter"
}
},
"ds.accept" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:113" ],
"translations" : {
"en" : null,
"fr" : "Accepter"
}
},
"ds.cancel" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:114" ],
"translations" : {
"en" : null,
"fr" : "Annuler"
}
},
"ds.color-lightbox.add" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/colors.cljs:52" ],
"translations" : {
"en" : "+ Add color",
"fr" : "+ Ajouter couleur"
}
},
"ds.color-new" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/colors.cljs:149" ],
"translations" : {
"en" : "+ New color",
"fr" : "+ Nouvelle couleur"
}
},
"ds.colors" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/header.cljs:48" ],
"translations" : {
"en" : "COLORS",
"fr" : "COULEURS"
}
},
"ds.colors-collection.new" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/colors.cljs:134" ],
"translations" : {
"en" : "+ New library",
"fr" : "+ Nouvelle librairie"
}
},
"ds.confirm-cancel" : {
"used-in" : [ "src/uxbox/main/ui/confirm.cljs:38" ],
"translations" : {
"en" : "Cancel",
"fr" : "Annuler"
}
},
"ds.confirm-ok" : {
"used-in" : [ "src/uxbox/main/ui/confirm.cljs:34" ],
"translations" : {
"en" : "Ok",
"fr" : "Ok"
}
},
"ds.confirm-title" : {
"used-in" : [ "src/uxbox/main/ui/confirm.cljs:28" ],
"translations" : {
"en" : "Are you sure?",
"fr" : "Êtes-vous sûr ?"
}
},
"ds.default-library-title" : {
"used-in" : [ "src/uxbox/main/data/images.cljs:99", "src/uxbox/main/data/icons.cljs:87", "src/uxbox/main/data/colors.cljs:68" ],
"translations" : {
"en" : "Unnamed Collection (%s)",
"fr" : "Collection sans nom (%s)"
}
},
"ds.foobar" : {
"translations" : {
"en" : null,
"fr" : null
},
"permanent" : true
},
"ds.height" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs:46" ],
"translations" : {
"en" : null,
"fr" : null
}
},
"ds.history.pinned" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:95" ],
"translations" : {
"en" : "Pinned",
"fr" : "Épinglés"
}
},
"ds.history.versions" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:92" ],
"translations" : {
"en" : "History",
"fr" : "Historique"
}
},
"ds.icon-new" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/icons.cljs:158" ],
"translations" : {
"en" : "+ New icon",
"fr" : "+ Nouvel icône"
}
},
"ds.icons" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/header.cljs:42" ],
"translations" : {
"en" : "ICONS",
"fr" : "ICÔNES"
}
},
"ds.icons-collection.new" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/icons.cljs:137" ],
"translations" : {
"en" : "+ New collection",
"fr" : "+ Nouvelle collection"
}
},
"ds.image-new" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:252" ],
"translations" : {
"en" : "+ New image",
"fr" : "+ Nouvelle image"
}
},
"ds.images" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/header.cljs:45" ],
"translations" : {
"en" : "IMAGES",
"fr" : "IMAGES"
}
},
"ds.images-collection.new" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:108" ],
"translations" : {
"en" : "+ New library",
"fr" : "+ Nouvelle librairie"
}
},
"ds.multiselect-bar.copy" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:196", "src/uxbox/main/ui/dashboard/images.cljs:171", "src/uxbox/main/ui/dashboard/colors.cljs:224", "src/uxbox/main/ui/dashboard/colors.cljs:201" ],
"translations" : {
"en" : "Copy",
"fr" : "Copier"
}
},
"ds.multiselect-bar.copy-to-library" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:200", "src/uxbox/main/ui/dashboard/images.cljs:175", "src/uxbox/main/ui/dashboard/colors.cljs:228", "src/uxbox/main/ui/dashboard/colors.cljs:205" ],
"translations" : {
"en" : "Copy to library",
"fr" : "Copier vers la librairie"
}
},
"ds.multiselect-bar.delete" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:190", "src/uxbox/main/ui/dashboard/colors.cljs:217" ],
"translations" : {
"en" : "Delete",
"fr" : "Supprimer"
}
},
"ds.multiselect-bar.move" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:179", "src/uxbox/main/ui/dashboard/colors.cljs:209" ],
"translations" : {
"en" : "Move",
"fr" : "Déplacer"
}
},
"ds.multiselect-bar.move-to-library" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:183", "src/uxbox/main/ui/dashboard/colors.cljs:213" ],
"translations" : {
"en" : "Move to library",
"fr" : "Déplacer vers la librairie"
}
},
"ds.multiselect-bar.rename" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:187" ],
"translations" : {
"en" : "Rename",
"fr" : "Renommer"
}
},
"ds.new-file" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/projects.cljs:143" ],
"translations" : {
"en" : "+ New File",
"fr" : null
}
},
"ds.position" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs:52" ],
"translations" : {
"en" : null,
"fr" : null
}
},
"ds.projects" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/header.cljs:39" ],
"translations" : {
"en" : "PROJECTS",
"fr" : "PROJETS"
}
},
"ds.rotation" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs:67" ],
"translations" : {
"en" : null,
"fr" : null
}
},
"ds.search.placeholder" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/projects.cljs:192" ],
"translations" : {
"en" : "Search...",
"fr" : "Rechercher..."
}
},
"ds.settings.document-history" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:86" ],
"translations" : {
"en" : null,
"fr" : null
}
},
"ds.settings.layers" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/layers.cljs:260" ],
"translations" : {
"en" : null,
"fr" : null
}
},
"ds.size" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs:33" ],
"translations" : {
"en" : null,
"fr" : null
}
},
"ds.store-colors-title" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/colors.cljs:129" ],
"translations" : {
"en" : "COLORS STORE",
"fr" : "BOUTIQUE"
}
},
"ds.store-icons-title" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/icons.cljs:131" ],
"translations" : {
"en" : "ICONS STORE",
"fr" : "BOUTIQUE"
}
},
"ds.store-images-title" : {
"used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:146", "src/uxbox/main/ui/dashboard/images.cljs:102" ],
"translations" : {
"en" : "IMAGES STORE",
"fr" : "BOUTIQUE"
}
},
"ds.updated-at" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/projects.cljs:110" ],
"translations" : {
"en" : "Updated %s",
"fr" : "Mis à jour %s"
}
},
"ds.uploaded-at" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/images.cljs:237", "src/uxbox/main/ui/dashboard/icons.cljs:303" ],
"translations" : {
"en" : "Uploaded at %s",
"fr" : "Mise en ligne : %s"
}
},
"ds.user.exit" : {
"used-in" : [ "src/uxbox/main/ui/users.cljs:43" ],
"translations" : {
"en" : "Exit",
"fr" : "Quitter"
}
},
"ds.user.notifications" : {
"used-in" : [ "src/uxbox/main/ui/users.cljs:40" ],
"translations" : {
"en" : "Notifications",
"fr" : "Notifications"
}
},
"ds.user.password" : {
"used-in" : [ "src/uxbox/main/ui/users.cljs:37" ],
"translations" : {
"en" : "Password",
"fr" : "Mot de passe"
}
},
"ds.user.profile" : {
"used-in" : [ "src/uxbox/main/ui/users.cljs:34" ],
"translations" : {
"en" : "Profile",
"fr" : "Profil"
}
},
"ds.width" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/icon_measures.cljs:36" ],
"translations" : {
"en" : null,
"fr" : null
}
},
"ds.your-colors-title" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/colors.cljs:126" ],
"translations" : {
"en" : "YOUR COLORS",
"fr" : "VOS COULEURS"
}
},
"ds.your-icons-title" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/icons.cljs:128" ],
"translations" : {
"en" : "YOUR ICONS",
"fr" : "VOS ICÔNES"
}
},
"ds.your-images-title" : {
"used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:143", "src/uxbox/main/ui/dashboard/images.cljs:99" ],
"translations" : {
"en" : "YOUR IMAGES",
"fr" : "VOS IMAGES"
}
},
"element.fill" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:44" ],
"translations" : {
"en" : "Fill",
"fr" : "Fond"
}
},
"errors.api.form.registration-disabled" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:39" ],
"translations" : {
"en" : "The registration is currently disabled.",
"fr" : "L'enregistrement est actuellement désactivé."
}
},
"errors.api.form.unexpected-error" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:51" ],
"translations" : {
"en" : "An unexpected error occurred.",
"fr" : "Une erreur inattendue c'est produite"
}
},
"errors.auth.invalid-recovery-token" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:174", "src/uxbox/main/data/auth.cljs:151" ],
"translations" : {
"en" : "The recovery token is invalid.",
"fr" : "Le jeton de récupération n'est pas valide."
}
},
"errors.auth.unauthorized" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:59" ],
"translations" : {
"en" : "Username or password seems to be wrong.",
"fr" : "Le nom d'utilisateur ou le mot de passe semble être faux."
}
},
"errors.generic" : {
"used-in" : [ "src/uxbox/main/ui.cljs:86" ],
"translations" : {
"en" : "Something wrong has happened.",
"fr" : "Quelque chose c'est mal passé."
}
},
"errors.network" : {
"used-in" : [ "src/uxbox/main/ui.cljs:80" ],
"translations" : {
"en" : "Unable to connect to backend server.",
"fr" : "Impossible de se connecter au serveur principal."
}
},
"header.sitemap" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:83" ],
"translations" : {
"en" : null,
"fr" : null
}
},
"history.alert-message" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:111" ],
"translations" : {
"en" : "You are seeing version %s",
"fr" : "Vous voyez la version %s"
}
},
"image.import-library" : {
"used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:137" ],
"translations" : {
"en" : "Import image from library",
"fr" : "Importer une image depuis une librairie"
}
},
"image.new" : {
"used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:71" ],
"translations" : {
"en" : "New image",
"fr" : "Nouvelle image"
}
},
"image.select" : {
"used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:75" ],
"translations" : {
"en" : "Select from library",
"fr" : "Choisir depuis une librairie"
}
},
"image.upload" : {
"used-in" : [ "src/uxbox/main/ui/workspace/images.cljs:80" ],
"translations" : {
"en" : "Upload file",
"fr" : "Envoyer un fichier"
}
},
"register.already-have-account" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:131" ],
"translations" : {
"en" : "Already have an account?",
"fr" : "Vous avez déjà un compte ?"
}
},
"register.fullname.placeholder" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:72" ],
"translations" : {
"en" : "Full Name",
"fr" : "Nom complet"
}
},
"register.get-started" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:127" ],
"translations" : {
"en" : "Get started",
"fr" : "Commencer"
}
},
"register.password.placeholder" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:115" ],
"translations" : {
"en" : "Password",
"fr" : "Mot de passe"
}
},
"settings.notifications" : {
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:43" ],
"translations" : {
"en" : "NOTIFICATIONS",
"fr" : "NOTIFICATIONS"
}
},
"settings.notifications.description" : {
"used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:19" ],
"translations" : {
"en" : "Get a roll up of prototype changes in your inbox.",
"fr" : "Obtenez un résumé des modifications apportées aux prototypes à votre adresse email."
}
},
"settings.notifications.every-day" : {
"used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:38" ],
"translations" : {
"en" : "Every day",
"fr" : "Chaque jour"
}
},
"settings.notifications.every-hour" : {
"used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:32" ],
"translations" : {
"en" : "Every hour",
"fr" : "Chaque heure"
}
},
"settings.notifications.none" : {
"used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:26" ],
"translations" : {
"en" : "None",
"fr" : "Aucune"
}
},
"settings.notifications.notifications-saved" : {
"used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:18" ],
"translations" : {
"en" : "Notifications preferences saved successfully!",
"fr" : "Préférences de notifications enregistrées avec succès !"
}
},
"settings.password" : {
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:40" ],
"translations" : {
"en" : "PASSWORD",
"fr" : "MOT DE PASSE"
}
},
"settings.password.change-password" : {
"used-in" : [ "src/uxbox/main/ui/settings/password.cljs:50" ],
"translations" : {
"en" : "Change password",
"fr" : "Changement de mot de passe"
}
},
"settings.password.confirm-password" : {
"used-in" : [ "src/uxbox/main/ui/settings/password.cljs:79" ],
"translations" : {
"en" : "Confirm password",
"fr" : "Confirmez mot de passe"
}
},
"settings.password.new-password" : {
"used-in" : [ "src/uxbox/main/ui/settings/password.cljs:69" ],
"translations" : {
"en" : "New password",
"fr" : "Nouveau mot de passe"
}
},
"settings.password.old-password" : {
"used-in" : [ "src/uxbox/main/ui/settings/password.cljs:58" ],
"translations" : {
"en" : "Old password",
"fr" : "Ancien mot de passe"
}
},
"settings.password.password-saved" : {
"used-in" : [ "src/uxbox/main/ui/settings/password.cljs:33" ],
"translations" : {
"en" : "Password saved successfully!",
"fr" : "Mot de passe enregistré avec succès !"
}
},
"settings.profile" : {
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:37" ],
"translations" : {
"en" : "PROFILE",
"fr" : "PROFIL"
}
},
"settings.profile.profile-saved" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:68" ],
"translations" : {
"en" : "Profile saved successfully!",
"fr" : "Profil enregistré avec succès !"
}
},
"settings.profile.section-basic-data" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:78" ],
"translations" : {
"en" : "Name, username and email",
"fr" : "Nom, nom d'utilisateur et adresse email"
}
},
"settings.profile.section-i18n-data" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:117" ],
"translations" : {
"en" : "Default language",
"fr" : "Langue par défaut"
}
},
"settings.profile.your-avatar" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:159" ],
"translations" : {
"en" : "Your avatar",
"fr" : "Votre avatar"
}
},
"settings.profile.your-email" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:111", "src/uxbox/main/ui/auth/register.cljs:101" ],
"translations" : {
"en" : "Your email",
"fr" : "Votre adresse email"
}
},
"settings.profile.your-name" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:86" ],
"translations" : {
"en" : "Your name",
"fr" : "Votre nom complet"
}
},
"settings.profile.your-username" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:98", "src/uxbox/main/ui/auth/register.cljs:87" ],
"translations" : {
"en" : "Your username",
"fr" : "Votre nom d'utilisateur"
}
},
"settings.update-settings" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:130", "src/uxbox/main/ui/settings/password.cljs:86", "src/uxbox/main/ui/settings/notifications.cljs:42" ],
"translations" : {
"en" : "Update settings",
"fr" : "Mettre à jour les paramètres"
}
},
"workspace.header.canvas" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:93" ],
"translations" : {
"en" : "Canvas",
"fr" : "Calque"
}
},
"workspace.header.circle" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:103" ],
"translations" : {
"en" : "Circle (Ctrl + E)",
"fr" : "Cercle (Ctrl + E)"
}
},
"workspace.header.color-palette" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:123" ],
"translations" : {
"en" : "Color Palette (---)",
"fr" : "Palette de couleurs (---)"
}
},
"workspace.header.curve" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:118" ],
"translations" : {
"en" : "Curve",
"fr" : "Courbe"
}
},
"workspace.header.document-history" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:143" ],
"translations" : {
"en" : "History (Ctrl + Shift + H)",
"fr" : "Historique du document (Ctrl + Maj + H)"
}
},
"workspace.header.download" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:156" ],
"translations" : {
"en" : "Download (Ctrl + E)",
"fr" : "Télécharger (Ctrl + E)"
}
},
"workspace.header.grid" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:170" ],
"translations" : {
"en" : "Grid (Ctrl + G)",
"fr" : "Grille (Ctrl + G)"
}
},
"workspace.header.grid-snap" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:175" ],
"translations" : {
"en" : "Snap to grid",
"fr" : "Coller à la grille"
}
},
"workspace.header.icons" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:128" ],
"translations" : {
"en" : "Icons (Ctrl + Shift + I)",
"fr" : "Icônes (Ctrl + Maj + I)"
}
},
"workspace.header.image" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:161" ],
"translations" : {
"en" : "Image (Ctrl + I)",
"fr" : "Image (Ctrl + I)"
}
},
"workspace.header.path" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:113" ],
"translations" : {
"en" : "Path",
"fr" : "Chemin"
}
},
"workspace.header.rect" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:98" ],
"translations" : {
"en" : "Box (Ctrl + B)",
"fr" : "Boîte (Ctrl + B)"
}
},
"workspace.header.rules" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:165" ],
"translations" : {
"en" : "Rules",
"fr" : "Règles"
}
},
"workspace.header.text" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:108" ],
"translations" : {
"en" : "Text",
"fr" : "Texte"
}
},
"workspace.header.view-mode" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:186" ],
"translations" : {
"en" : "View mode (Ctrl + P)",
"fr" : "Mode visualisation (Ctrl + P)"
}
},
"workspace.options.color" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:81", "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:125", "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:47" ],
"translations" : {
"en" : "Color",
"fr" : "Couleur"
}
},
"workspace.options.font-family" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:204" ],
"translations" : {
"en" : "Font Family",
"fr" : "Police de caractères"
}
},
"workspace.options.font-options" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:202" ],
"translations" : {
"en" : "Fonts & Font Size",
"fr" : "TODO"
}
},
"workspace.options.font-weight" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:213" ],
"translations" : {
"en" : "Font Size & Weight",
"fr" : "Taille et graisse"
}
},
"workspace.options.grid-options" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:113" ],
"translations" : {
"en" : "Grid settings",
"fr" : "Paramètres de la grille"
}
},
"workspace.options.line-height-letter-spacing" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:245" ],
"translations" : {
"en" : "Line height and Letter spacing",
"fr" : "Hauteur de ligne et Espacement de caractères"
}
},
"workspace.options.measures" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:70", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:64", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:65", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:62", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:115" ],
"translations" : {
"en" : "Size, position & rotation",
"fr" : "Taille, position et rotation"
}
},
"workspace.options.opacity" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:89", "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:58" ],
"translations" : {
"en" : "Opacity",
"fr" : "Opacité"
}
},
"workspace.options.position" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:99", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:93", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:93", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:91", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:74" ],
"translations" : {
"en" : "Position",
"fr" : "Position"
}
},
"workspace.options.rotation-radius" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:116", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:110", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:108", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:108", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:92" ],
"translations" : {
"en" : "Rotation & Radius",
"fr" : "TODO"
}
},
"workspace.options.size" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:72", "src/uxbox/main/ui/workspace/sidebar/options/rect.cljs:66", "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:115", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:69", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:64", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:38" ],
"translations" : {
"en" : "Size",
"fr" : "Taille"
}
},
"workspace.options.stroke" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:58" ],
"translations" : {
"en" : "Stroke",
"fr" : null
}
},
"workspace.options.stroke.dashed" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:69" ],
"translations" : {
"en" : "Dashed",
"fr" : "Tiré"
}
},
"workspace.options.stroke.dotted" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:68" ],
"translations" : {
"en" : "Dotted",
"fr" : "Pointillé"
}
},
"workspace.options.stroke.mixed" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:70" ],
"translations" : {
"en" : "Mixed",
"fr" : "Mixte"
}
},
"workspace.options.stroke.none" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:66" ],
"translations" : {
"en" : "None",
"fr" : "Aucun"
}
},
"workspace.options.stroke.solid" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:67" ],
"translations" : {
"en" : "Solid",
"fr" : "Solide"
}
},
"workspace.options.stroke.style" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:62" ],
"translations" : {
"en" : "Style",
"fr" : "Style"
}
},
"workspace.options.text-align" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:264" ],
"translations" : {
"en" : "Text Alignment",
"fr" : "Alignement de texte"
}
},
"workspace.sidebar.sitemap" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/sitemap.cljs:134" ],
"translations" : {
"en" : "Sitemap",
"fr" : "Plan du site"
}
}
}

View file

@ -15,6 +15,6 @@
<script src="/js/cljs_base.js?ts={{& ts}}"></script>
<script src="/js/main.js?ts={{& ts}}"></script>
<script>uxbox.main.init()</script>
<script>uxbox.main.init({{& tr }})</script>
</body>
</html>

View file

@ -10,8 +10,6 @@
[rumext.alpha :as mf]
[uxbox.main.data.auth :refer [logout]]
[uxbox.main.data.users :as udu]
[uxbox.main.locales.en :as en]
[uxbox.main.locales.fr :as fr]
[uxbox.main.store :as st]
[uxbox.main.ui :as ui]
[uxbox.main.ui.lightbox :refer [lightbox]]
@ -19,7 +17,7 @@
[uxbox.main.ui.loader :refer [loader]]
[uxbox.util.dom :as dom]
[uxbox.util.html.history :as html-history]
[uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.i18n :as i18n]
[uxbox.util.messages :as uum]
[uxbox.util.router :as rt]
[uxbox.util.storage :refer [storage]]
@ -30,15 +28,15 @@
(declare reinit)
(s/check-asserts true)
(i18n/update-locales! (fn [locales]
(-> locales
(assoc "en" en/locales)
(assoc "fr" fr/locales))))
;; (i18n/update-locales! (fn [locales]
;; (-> locales
;; (assoc "en" en/locales)
;; (assoc "fr" fr/locales))))
(i18n/on-locale-change!
(fn [new old]
(println "Locale changed from" old " to " new)
(reinit)))
;; (i18n/on-locale-change!
;; (fn [new old]
;; (println "Locale changed from" old " to " new)
;; (reinit)))
;; --- Error Handling
@ -81,7 +79,8 @@
(def app-sym (.for js/Symbol "uxbox.app"))
(defn ^:export init
[]
[translations]
(i18n/init! (js/JSON.parse translations))
(unchecked-set js/window app-sym "main")
(st/init)
(init-ui))

View file

@ -40,7 +40,7 @@
[{:keys [on-submit value] :as props}]
(let [local (mf/use-var value)]
[:div.lightbox-body
[:h3 (tr "ds.color-lightbox.title")]
[:h3 (tr "ds.color-lightbox.title" )]
[:form
[:div.row-flex.center
[:& colorpicker {:value (or @local "#00ccff")

View file

@ -190,75 +190,75 @@
[:li {:key (pr-str id)}
[:a {:on-click #(on-select % id)} name]])])))
(mf/def grid-options
:mixins [(mf/local) mf/memo]
;; (mf/def grid-options
;; :mixins [(mf/local) mf/memo]
:render
(fn [{:keys [::mf/local] :as own}
{:keys [id type selected] :as props}]
(letfn [(delete []
(st/emit! (di/delete-selected)))
(on-delete [event]
(modal/show! confirm-dialog {:on-accept delete}))
(on-toggle-copy [event]
(swap! local update :show-copy-tooltip not))
(on-toggle-move [event]
(swap! local update :show-move-tooltip not))
(on-copy [selected]
(swap! local assoc
:show-move-tooltip false
:show-copy-tooltip false)
(st/emit! (di/copy-selected selected)))
(on-move [selected]
(swap! local assoc
:show-move-tooltip false
:show-copy-tooltip false)
(st/emit! (di/move-selected selected)))
(on-rename [event]
(let [selected (first selected)]
(st/emit! (di/update-opts :edition selected))))]
;; :render
;; (fn [{:keys [::mf/local] :as own}
;; {:keys [id type selected] :as props}]
;; (letfn [(delete []
;; (st/emit! (di/delete-selected)))
;; (on-delete [event]
;; (modal/show! confirm-dialog {:on-accept delete}))
;; (on-toggle-copy [event]
;; (swap! local update :show-copy-tooltip not))
;; (on-toggle-move [event]
;; (swap! local update :show-move-tooltip not))
;; (on-copy [selected]
;; (swap! local assoc
;; :show-move-tooltip false
;; :show-copy-tooltip false)
;; (st/emit! (di/copy-selected selected)))
;; (on-move [selected]
;; (swap! local assoc
;; :show-move-tooltip false
;; :show-copy-tooltip false)
;; (st/emit! (di/move-selected selected)))
;; (on-rename [event]
;; (let [selected (first selected)]
;; (st/emit! (di/update-opts :edition selected))))]
;; MULTISELECT OPTIONS BAR
[:div.multiselect-bar
(if (or (= type :own) (nil? id))
;; if editable
[:div.multiselect-nav {}
[:span.move-item.tooltip.tooltip-top
{:alt (tr "ds.multiselect-bar.copy")
:on-click on-toggle-copy}
(when (:show-copy-tooltip @local)
(grid-options-tooltip {:selected id
:title (tr "ds.multiselect-bar.copy-to-library")
:on-select on-copy}))
i/copy]
[:span.move-item.tooltip.tooltip-top
{:alt (tr "ds.multiselect-bar.move")
:on-click on-toggle-move}
(when (:show-move-tooltip @local)
(grid-options-tooltip {:selected id
:title (tr "ds.multiselect-bar.move-to-library")
:on-select on-move}))
i/move]
(when (= 1 (count selected))
[:span.move-item.tooltip.tooltip-top
{:alt (tr "ds.multiselect-bar.rename")
:on-click on-rename}
i/pencil])
[:span.delete.tooltip.tooltip-top
{:alt (tr "ds.multiselect-bar.delete")
:on-click on-delete}
i/trash]]
;; ;; MULTISELECT OPTIONS BAR
;; [:div.multiselect-bar
;; (if (or (= type :own) (nil? id))
;; ;; if editable
;; [:div.multiselect-nav {}
;; [:span.move-item.tooltip.tooltip-top
;; {:alt (tr "ds.multiselect-bar.copy")
;; :on-click on-toggle-copy}
;; (when (:show-copy-tooltip @local)
;; (grid-options-tooltip {:selected id
;; :title (tr "ds.multiselect-bar.copy-to-library")
;; :on-select on-copy}))
;; i/copy]
;; [:span.move-item.tooltip.tooltip-top
;; {:alt (tr "ds.multiselect-bar.move")
;; :on-click on-toggle-move}
;; (when (:show-move-tooltip @local)
;; (grid-options-tooltip {:selected id
;; :title (tr "ds.multiselect-bar.move-to-library")
;; :on-select on-move}))
;; i/move]
;; (when (= 1 (count selected))
;; [:span.move-item.tooltip.tooltip-top
;; {:alt (tr "ds.multiselect-bar.rename")
;; :on-click on-rename}
;; i/pencil])
;; [:span.delete.tooltip.tooltip-top
;; {:alt (tr "ds.multiselect-bar.delete")
;; :on-click on-delete}
;; i/trash]]
;; if not editable
[:div.multiselect-nav
[:span.move-item.tooltip.tooltip-top
{:alt (tr "ds.multiselect-bar.copy")
:on-click on-toggle-copy}
(when (:show-copy-tooltip @local)
(grid-options-tooltip {:selected id
:title (tr "ds.multiselect-bar.copy-to-library")
:on-select on-copy}))
i/organize]])])))
;; ;; if not editable
;; [:div.multiselect-nav
;; [:span.move-item.tooltip.tooltip-top
;; {:alt (tr "ds.multiselect-bar.copy")
;; :on-click on-toggle-copy}
;; (when (:show-copy-tooltip @local)
;; (grid-options-tooltip {:selected id
;; :title (tr "ds.multiselect-bar.copy-to-library")
;; :on-select on-copy}))
;; i/organize]])])))
;; --- Grid Item
@ -349,7 +349,7 @@
:coll coll
:opts opts}]
(when (seq (:selected opts))
[:& grid-options {:id id :type type :selected (:selected opts)}])]]))
#_[:& grid-options {:id id :type type :selected (:selected opts)}])]]))
;; --- Icons Page

View file

@ -19,7 +19,6 @@
[uxbox.main.ui.modal :as modal]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.confirm :refer [confirm-dialog]]
[uxbox.main.ui.dashboard.projects-forms :refer [create-project-dialog]]
[uxbox.main.ui.dashboard.common :as common]
[uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom]

View file

@ -1,72 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.dashboard.projects-forms
(:require
[cljs.spec.alpha :as s]
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.main.data.projects :as udp]
[uxbox.main.store :as st]
[uxbox.main.ui.modal :as modal]
[uxbox.util.dom :as dom]
[uxbox.util.forms :as fm]
[uxbox.util.i18n :as t :refer [tr]]))
(s/def ::name ::fm/not-empty-string)
(s/def ::width ::fm/number-str)
(s/def ::height ::fm/number-str)
(s/def ::project-form
(s/keys :req-un [::name]))
(def defaults {:name ""})
;; --- Create Project Form
(defn- on-submit
[event form]
(dom/prevent-default event)
(let [data (:clean-data form)]
(st/emit! (udp/create-project data))
(modal/hide!)))
(defn- swap-size
[event {:keys [data] :as form}]
(swap! data assoc
:width (:height data)
:height (:width data)))
(mf/defc create-project-form
[props]
(let [{:keys [data] :as form} (fm/use-form ::project-form defaults)]
[:form {:on-submit #(on-submit % form)}
[:input.input-text
{:placeholder (tr "ds.project.placeholder")
:type "text"
:name "name"
:value (:name data)
:class (fm/error-class form :name)
:on-blur (fm/on-input-blur form :name)
:on-change (fm/on-input-change form :name)
:auto-focus true}]
;; Submit
[:input#project-btn.btn-primary
{:value (tr "ds.go")
:class (when-not (:valid form) "btn-disabled")
:disabled (not (:valid form))
:type "submit"}]]))
;; --- Create Project Lightbox
(mf/defc create-project-dialog
[props]
[:div.lightbox-body
[:h3 (tr "ds.project.new")]
[:& create-project-form]
[:a.close {:on-click modal/hide!} i/close]])

View file

@ -21,7 +21,7 @@
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
[uxbox.util.data :refer [classnames enumerate]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.router :as rt]))
;; --- Page Item
@ -128,7 +128,8 @@
(mf/defc sitemap-toolbox
[{:keys [file page] :as props}]
(let [on-create-click #(st/emit! dp/create-empty-page)]
(let [on-create-click #(st/emit! dp/create-empty-page)
tr (i18n/use-translations)]
[:div.sitemap.tool-window
[:div.tool-window-bar
[:span (tr "workspace.sidebar.sitemap")]

View file

@ -7,72 +7,86 @@
(ns uxbox.util.i18n
"A i18n foundation."
(:require [cuerdas.core :as str]
[uxbox.config :as cfg]
[uxbox.util.storage :refer [storage]]))
(:require
[cuerdas.core :as str]
[rumext.alpha :as mf]
[beicon.core :as rx]
[goog.object :as gobj]
[uxbox.config :as cfg]
[uxbox.util.transit :as t]
[uxbox.util.storage :refer [storage]]))
(defonce locale (atom (get storage ::locale cfg/default-language)))
(defonce state (atom {}))
(defonce locale (get storage ::locale cfg/default-language))
(defonce locale-sub (rx/subject))
(defonce translations #js {})
(defn update-locales!
[callback]
(swap! state callback))
;; The traslations `data` is a javascript object and should be treated
;; with `goog.object` namespace functions instead of a standart
;; clojure functions. This is for performance reasons because this
;; code is executed in the critical part (application bootstrap) and
;; used in many parts of the application.
(defn init!
[data]
(set! translations data))
(defn set-current-locale!
[v]
(swap! storage assoc ::locale v)
(reset! locale v))
(set! locale v)
(rx/push! locale-sub v))
(defn set-default-locale!
[]
(set-current-locale! cfg/default-language))
(defn on-locale-change!
[callback]
(add-watch locale ::main (fn [_ _ old-locale new-locale]
(when (not= old-locale new-locale)
(callback new-locale old-locale)))))
;; A marker type that is used just for mark
;; a parameter that reprsentes the counter.
(deftype C [val]
IDeref
(-deref [o] val))
(defn c
[x]
(C. x))
(defn ^boolean c?
[r]
(instance? C r))
(defn- internal-tr
([locale code]
(let [code (name code)
value (gobj/getValueByKeys translations code locale)
value (if (nil? value) code value)]
(if (array? value)
(aget value 0)
value)))
([locale code & args]
(let [code (name code)
value (gobj/getValueByKeys translations code locale)
value (if (nil? value) code value)
plural (first (filter c? args))
value (if (array? value)
(if (= @plural 1) (aget value 0) (aget value 1))
value)]
(apply str/format value (map #(if (c? %) @% %) args)))))
;; A main public api for translate strings.
;; A marker type that is used just for mark
;; a parameter that reprsentes the counter.
(defn c
[x]
(C. x))
(defn tr
"Translate the string."
([t]
(let [default (name t)
locale (deref locale)
value (or (get-in @state [locale t])
default)]
(if (vector? value)
(or (second value) default)
value)))
([t & args]
(let [locale (deref locale)
value (get-in @state [locale t] (name t))
plural (first (filter c? args))
args (mapv #(if (c? %) @% %) args)
value (cond
(and (vector? value)
(= 3 (count value)))
(nth value (min 2 @plural))
([code] (internal-tr locale code))
([code & args] (apply internal-tr locale code args)))
(vector? value)
(if (= @plural 1) (first value) (second value))
(defn use-translations
[]
(let [[locale set-locale] (mf/useState locale)
tr-fn (mf/useMemo (fn [] (partial internal-tr locale))
#js [locale])]
(mf/useEffect (fn []
(let [sub (rx/sub! locale-sub #(set-locale %))]
#(rx/dispose! sub)))
#js [])
tr-fn))
:else
value)]
(apply str/format value args))))

View file

@ -19,17 +19,17 @@
[uxbox.view.ui.lightbox :refer [lightbox]]
[uxbox.view.ui.loader :refer [loader]]))
(i18n/update-locales! (fn [locales]
(-> locales
(assoc "en" en/locales)
(assoc "fr" fr/locales))))
;; (i18n/update-locales! (fn [locales]
;; (-> locales
;; (assoc "en" en/locales)
;; (assoc "fr" fr/locales))))
(declare reinit)
(i18n/on-locale-change!
(fn [new old]
(println "Locale changed from" old " to " new)
(reinit)))
;; (i18n/on-locale-change!
;; (fn [new old]
;; (println "Locale changed from" old " to " new)
;; (reinit)))
(defn- on-navigate
[router path]

View file

@ -1,7 +1,10 @@
(require '[clojure.pprint :refer [pprint]])
(require '[clojure.java.shell :as shell])
(require '[figwheel.main.api :as figwheel])
(require '[environ.core :refer [env]])
(require '[clojure.pprint :refer [pprint]]
'[clojure.java.shell :as shell]
'[clojure.java.io :as io]
'[figwheel.main.api :as figwheel]
'[environ.core :refer [env]]
'[jsonista.core :as json]
'[cognitect.transit :as t])
(require '[cljs.build.api :as api]
'[cljs.repl :as repl]
'[cljs.repl.node :as node])
@ -12,6 +15,8 @@
'[rebel-readline.cljs.service.local]
'[rebel-readline.cljs.repl])
(import 'java.io.ByteArrayOutputStream)
(defmulti task first)
(defmethod task :default
@ -39,7 +44,7 @@
:closure-defines closure-defines
:optimizations :none
:infer-externs true
:verbose true
:verbose false
:source-map true
:static-fns false
:pretty-print true
@ -153,7 +158,7 @@
(api/build (api/inputs "src" "test")
(assoc default-build-options
:main 'uxbox.tests.main
:verbose true
:verbose false
:target :nodejs
:source-map true
:output-to "target/tests/main.js"

178
frontend/translations.clj Normal file
View file

@ -0,0 +1,178 @@
(require '[clojure.pprint :as pp :refer [pprint]])
(require '[clojure.java.shell :as shell])
(require '[environ.core :refer [env]])
(require '[clojure.walk :as walk]
'[clojure.edn :as edn]
'[clojure.set :as set])
(require '[datoteka.core :as fs]
'[jsonista.core :as json])
(require '[clojure.java.io :as io]
'[clojure.tools.reader :as r]
'[clojure.tools.reader.reader-types :as rt])
(import 'java.nio.file.Paths
'java.nio.file.Path
'java.nio.file.Files
'java.nio.file.SimpleFileVisitor
'java.nio.file.FileVisitResult)
(extend-protocol io/Coercions
Path
(as-file [it] (.toFile it))
(as-url [it] (io/as-url (.toFile it))))
(defmulti task first)
(defn- find-translations-in-form
[env form]
(->> form
(walk/postwalk
(fn [fm]
(when (and (list? fm)
(= (first fm) 'tr)
(string? (second fm)))
(let [m (meta (first fm))]
(swap! env conj {:code (second fm)
:file (:file m)
:line (:line m)})))
fm))))
(defn- find-translations-in-file
[env file]
(let [rdr (-> (io/as-file file)
(io/reader)
(rt/source-logging-push-back-reader 1 file))]
(try
(binding [r/*default-data-reader-fn* (constantly nil)
r/*alias-map* {'dw (create-ns 'user)
'fm (create-ns 'user)
'us (create-ns 'user)
'dp (create-ns 'user)
'cp (create-ns 'user)}]
(loop []
(let [form (r/read {:eof ::end} rdr)]
(when (not= ::end form)
(find-translations-in-form env form)
(recur)))))
(catch Exception e
;; (.printStackTrace e)
(println (str "ERROR: on procesing " file "; ignoring..."))))))
(defn- find-translations-in-directory
[env file]
(->> (proxy [SimpleFileVisitor] []
(visitFile [path attrs]
(when (= (fs/ext path) "cljs")
(find-translations-in-file env path))
FileVisitResult/CONTINUE)
(postVisitDirectory [dir exc]
FileVisitResult/CONTINUE))
(Files/walkFileTree (fs/path file))))
(defn- collect-translations
[path]
(let [env (atom [])]
(find-translations-in-directory env path)
@env))
(defn- read-json-file
[path]
(when (fs/regular-file? path)
(let [content (json/read-value (slurp (io/as-file path)))]
(into (sorted-map) content))))
(defn- read-edn-file
[path]
(when (fs/regular-file? path)
(let [content (edn/read-string (slurp (io/as-file path)))]
(into (sorted-map) content))))
(defn- add-translation
[data {:keys [code file line] :as translation}]
(let [rpath (str file ":" line)]
(if (contains? data code)
(update data code (fn [state]
(if (get state "permanent")
state
(-> state
(dissoc "unused")
(assoc "used-in" (->> (get state "used-in" [])
(remove #(= rpath %))
(into [rpath])))))))
(assoc data code {"translations" {"en" nil "fr" nil}
"used-in" [rpath]}))))
(defn- clean-removed-translations
[data imported]
(let [existing (into #{} (keys data))
toremove (set/difference existing imported)]
(reduce (fn [data code]
(if (get-in data [code "permanent"])
data
(-> data
(update code dissoc "used-in")
(update code assoc "unused" true))))
data
toremove)))
(defn- ensure-translations-format
[data]
(reduce-kv (fn [data k v]
(if (string? v)
(assoc data k {:translations {:en v}})
data))
data
data))
(defn- synchronize-translations
[data translations]
(loop [data (ensure-translations-format data)
imported #{}
c (first translations)
r (rest translations)]
(if (nil? c)
(clean-removed-translations data imported)
(recur (add-translation data c)
(conj imported (:code c))
(first r)
(rest r)))))
(defn- synchronize-legacy-translations
[data legacy-data lang]
(reduce-kv (fn [data k v]
(if (contains? data k)
(update-in data [k "translations"] assoc lang v)
data))
data
legacy-data))
(defn- write-result!
[data output-path]
(binding [*out* (io/writer (fs/path output-path))]
(let [mapper (json/object-mapper {:pretty true})]
(println (json/write-value-as-string data mapper))
(flush))))
(defn- update-translations
[{:keys [find-directory output-path] :as props}]
(let [data (read-json-file output-path)
translations (collect-translations find-directory)
data (synchronize-translations data translations)]
(write-result! data output-path)))
(defmethod task "collectmessages"
[[_ in-path out-path]]
(update-translations {:find-directory in-path
:output-path out-path}))
(defmethod task "merge-with-legacy"
[[_ path lang legacy-path]]
(let [ldata (read-edn-file legacy-path)
data (read-json-file path)
data (synchronize-legacy-translations data ldata lang)]
(write-result! data path)))
(task *command-line-args*)