Use Zanata to handle translations

This commit is contained in:
Lucas Verney 2018-07-20 15:12:28 +02:00
parent c5183a570f
commit 3da34cb148
12 changed files with 219 additions and 161 deletions

3
.gitignore vendored
View File

@ -6,6 +6,9 @@ yarn-debug.log*
yarn-error.log*
*.db
*.pyc
.zanata-cache
po/*.po
po/*.pot
# Editor directories and files
.idea

7
.po2json.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
for i in po/*.po
do
j=$(echo $i | cut -d '.' -f 1 | cut -d '/' -f 2)
po2json -i $i -t src/i18n/en.json --progress none | ./scripts/renest_json.py > po/$j.json
mv po/$j.json src/i18n/$j.json
done

View File

@ -107,6 +107,17 @@ python -m server
to spawn the server-side part, listening on `localhost:8081`.
## Translating
Translation is done directly on [Zanata](https://translate.zanata.org/iteration/view/cyclassist/master?dswid=7345).
To add new strings to localize, edit the `src/i18n/en.json` file with your new
strings (and only this file). Then, you can run `yarn push-locales` to send
the updated locales to translate and `yarn pull-locales` to fetch the
translated files. To use these scripts you will need the
Translate-toolkit(`pip install translate-toolkit`) and the Zanata CLI client.
## License
This software is licensed under an MIT license, unless explicitly mentionned
@ -125,3 +136,12 @@ Icons are made from the original works:
coming from Pixabay under CC0 license.
* [Accident icon](https://www.flaticon.com/free-icon/car-running-over-a-bicycle_91680) is
coming from Flaticon.
The map background is using tiles from <a
href=\"https://carto.com/location-data-services/basemaps/\">Carto.com</a> or
<a href=\"http://thunderforest.com/\">Thunderforest</a>, thanks to <a
href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap
contributors</a> and <a href=\"http://leafletjs.com/\">Leaflet</a>.
Manual location picking uses the awesome API from <a
href=\"https://adresse.data.gouv.fr\">adresse.data.gouv.fr</a>.

View File

@ -8,7 +8,9 @@
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"lint": "eslint --ext .js,.vue src",
"build": "node build/build.js"
"build": "node build/build.js",
"push-locales": "json2po -P -i src/i18n/en.json -t src/i18n/en.json -o po/cyclassist.pot && zanata-cli -q -B push",
"pull-locales": "zanata-cli -q -B pull && ./.po2json.sh"
},
"dependencies": {
"es6-promise": "^4.2.4",

0
po/.gitignore vendored Normal file
View File

20
scripts/renest_json.py Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env python
import json
import sys
locale = json.loads(sys.stdin.read())
nested_json = {}
for key, value in locale.items():
split_key = key.split('.')
d = nested_json
for key2 in split_key[:-1]:
try:
d[key2]
except KeyError:
d[key2] = {}
d = d[key2]
d[split_key[-1]] = value
d = nested_json
print(json.dumps(d, sort_keys=True, indent=4, separators=(',', ': ')))

View File

@ -1,79 +0,0 @@
// Keys should be sorted alphabetically
export default {
about: {
availableReportsTitle: 'The available reports so far are:',
geolocationDescription: 'As of current version, your precise geolocation is handled within your device and never sent from it to any external service. The map background is downloaded on demand from the tile provider and it has then access to an estimate of the displayed position. If you refuse to share your geolocation, you can still pick a location manually but you will miss some geolocation dependent features.',
license: 'It is released under an <a href="https://opensource.org/licenses/MIT">MIT license</a> (<a href="https://framagit.org/phyks/cyclassist">source code</a>). Icons are based on creations from Wikimedia, Vecteezy, Pixabay or Flaticon. The map background is using tiles from <a href="https://carto.com/location-data-services/basemaps/">Carto.com</a> or <a href="http://thunderforest.com/">Thunderforest</a>, thanks to <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a> and <a href="http://leafletjs.com/">Leaflet</a>. Collected reports are available under <a href="https://opendatacommons.org/licenses/odbl/">ODbL license</a>.',
summary: 'This app lets you track and share issues with bike lanes.',
usage: 'How to use',
usageDescription: 'Use the button in the lower right corner to add a new report at your current location. To add a report elsewhere, do a click where you want the report to be shown. Press on a marker on the map to display more informations and report the problem as being still there or solved.',
},
buttons: {
close: 'Close',
downvote: 'Downvote',
menu: 'Menu',
recenterMap: 'Recenter map',
reportProblem: 'Report problem',
upvote: 'Upvote',
},
geolocation: {
errorFetchingPosition: 'Error fetching your position:',
fetching: 'Fetching current position…',
geolocation: 'Geolocation',
permissionDenied: 'access to your position is denied.',
positionUnavailable: 'unable to retrieve your position.',
timeout: 'position was too long to acquire.',
unavailable: 'Sorry, geolocation is not available in your browser.',
},
intro: {
checkingPermissions: 'Checking permissions',
next: 'Next',
ready: 'Ready to start',
reportTypes: 'Report types',
startReporting: 'Start reporting!',
welcome: 'Welcome',
},
locationPicker: {
invalidSelection: 'Invalid selection',
pickALocationManually: 'pick a location manually',
},
menu: {
About: 'Help',
Map: 'Map',
Settings: 'Settings',
},
misc: {
discard: 'Discard',
or: 'or',
retry: 'Retry',
spaceBeforeDoublePunctuations: '',
},
reportCard: {
Reported: 'Reported',
},
reportDialog: {
unableToSendDescription: 'There was a network issue preventing from sending the latest report.',
unableToSendTitle: 'Unable to send latest report',
},
reportLabels: {
accident: 'Accident',
accidentDescription: 'Any accident on the road (automatically removed after one hour).',
gcum: 'GCUM',
gcumDescription: 'A car poorly parked on a bike lane. Such reports are automatically deleted after one hour, as they are by nature temporary (automatically removed after one hour).',
interrupt: 'Interruption',
interruptDescription: 'An interruption of the bike lane (works, unexpected end of the bike lane, etc.).',
misc: 'Other',
miscDescription: 'A problem on the road which does not fit in any other category.',
obstacle: 'Obstacle',
obstacleDescription: 'An obstacle on the bike lane (stones, bulky waste, etc.).',
pothole: 'Pothole',
potholeDescription: 'A pothole in the ground.',
},
settings: {
locale: 'Language',
preventSuspend: 'Prevent device from going to sleep',
save: 'Save',
skipOnboarding: 'Skip onboarding',
tileServer: 'Map tiles server',
},
};

78
src/i18n/en.json Normal file
View File

@ -0,0 +1,78 @@
{
"about": {
"availableReportsTitle": "The available reports so far are:",
"geolocationDescription": "As of current version, your precise geolocation is handled within your device and never sent from it to any external service. The map background is downloaded on demand from the tile provider and it has then access to an estimate of the displayed position. If you refuse to share your geolocation, you can still pick a location manually but you will miss some geolocation dependent features.",
"license": "It is released under an <a href=\"https://opensource.org/licenses/MIT\">MIT license</a> (<a href=\"https://framagit.org/phyks/cyclassist\">source code</a>). Icons are based on creations from Wikimedia, Vecteezy, Pixabay or Flaticon. The map background is using tiles from <a href=\"https://carto.com/location-data-services/basemaps/\">Carto.com</a> or <a href=\"http://thunderforest.com/\">Thunderforest</a>, thanks to <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap contributors</a> and <a href=\"http://leafletjs.com/\">Leaflet</a>. Collected reports are available under <a href=\"https://opendatacommons.org/licenses/odbl/\">ODbL license</a>. Manual location picking uses the awesome API from <a href=\"https://adresse.data.gouv.fr\">adresse.data.gouv.fr</a>.",
"summary": "This app lets you track and share issues with bike lanes.",
"usage": "How to use",
"usageDescription": "Use the button in the lower right corner to add a new report at your current location. To add a report elsewhere, do a click where you want the report to be shown. Press on a marker on the map to display more informations and report the problem as being still there or solved."
},
"buttons": {
"close": "Close",
"downvote": "Downvote",
"menu": "Menu",
"recenterMap": "Recenter map",
"reportProblem": "Report problem",
"upvote": "Upvote"
},
"geolocation": {
"errorFetchingPosition": "Error fetching your position:",
"fetching": "Fetching current position\u2026",
"geolocation": "Geolocation",
"permissionDenied": "access to your position is denied.",
"positionUnavailable": "unable to retrieve your position.",
"timeout": "position was too long to acquire.",
"unavailable": "Sorry, geolocation is not available in your browser."
},
"intro": {
"checkingPermissions": "Checking permissions",
"next": "Next",
"ready": "Ready to start",
"reportTypes": "Report types",
"startReporting": "Start reporting!",
"welcome": "Welcome"
},
"locationPicker": {
"invalidSelection": "Invalid selection",
"pickALocationManually": "pick a location manually"
},
"menu": {
"About": "Help",
"Map": "Map",
"Settings": "Settings"
},
"misc": {
"discard": "Discard",
"or": "or",
"retry": "Retry",
"spaceBeforeDoublePunctuations": ""
},
"reportCard": {
"Reported": "Reported"
},
"reportDialog": {
"unableToSendDescription": "There was a network issue preventing from sending the latest report.",
"unableToSendTitle": "Unable to send latest report"
},
"reportLabels": {
"accident": "Accident",
"accidentDescription": "Any accident on the road (automatically removed after one hour).",
"gcum": "GCUM",
"gcumDescription": "A car poorly parked on a bike lane. Such reports are automatically deleted after one hour, as they are by nature temporary (automatically removed after one hour).",
"interrupt": "Interruption",
"interruptDescription": "An interruption of the bike lane (works, unexpected end of the bike lane, etc.).",
"misc": "Other",
"miscDescription": "A problem on the road which does not fit in any other category.",
"obstacle": "Obstacle",
"obstacleDescription": "An obstacle on the bike lane (stones, bulky waste, etc.).",
"pothole": "Pothole",
"potholeDescription": "A pothole in the ground."
},
"settings": {
"locale": "Language",
"preventSuspend": "Prevent device from going to sleep",
"save": "Save",
"skipOnboarding": "Skip onboarding",
"tileServer": "Map tiles server"
}
}

View File

@ -1,79 +0,0 @@
// Keys should be sorted alphabetically
export default {
about: {
availableReportsTitle: "Les signalements disponibles pour l'instant sont :",
geolocationDescription: "Dans la version actuelle, votre position est traitée directement par votre appareil et n'est jamais envoyée à un service externe. Le fond de carte est téléchargé à la demande depuis le fournisseur de tuiles et il a donc accès à une estimation de la position affichée. Si vous refusez le partage de votre géolocalisation, vous pourrez saisir une adresse manuellement à la place mais vous perdrez les fonctionnalités avancées qui reposent sur la géolocalisation.",
license: "Le code source est sous <a href='https://opensource.org/licenses/MIT'>licence MIT</a> (<a href='https://framagit.org/phyks/cyclassist'>code source</a>). Les icônes sont basées sur des travaux de Wikimedia, Vecteezy, Pixabay ou Flaticon. Les tuiles de fond de carte proviennent de chez <a href='https://carto.com/location-data-services/basemaps/'>Carto.com</a> ou <a href='http://thunderforest.com/'>Thunderforest</a>, grâce aux <a href='https://www.openstreetmap.org/copyright'>contributeurs OpenStreetMap</a> et à <a href='http://leafletjs.com/'>Leaflet</a>. Les signalements sont disponibles sous <a href='https://opendatacommons.org/licenses/odbl/'>licence ODbL</a>.",
summary: 'Cette application vous permet de signaler et de partager des problèmes avec les itinéraires cyclables.',
usage: 'Utilisation',
usageDescription: "Utilisez le bouton en bas à droite pour ajouter un signalement à votre emplacement actuel. Pour ajouter un signalement ailleurs, faites un appui à l'emplacement souhaité sur la carte. Appuyer sur un marqueur sur la carte pour afficher plus d'informations et signaler que le problème est toujours présent ou non.",
},
buttons: {
close: 'Fermer',
downvote: 'Infirmer',
menu: 'Menu',
recenterMap: 'Recentrer la carte',
reportProblem: 'Nouveau signalement',
upvote: 'Confirmer',
},
geolocation: {
errorFetchingPosition: 'Impossible de récupérer votre position :',
fetching: 'En attente de votre position…',
geolocation: 'Géolocalisation',
permissionDenied: "l'accès a votre position a été refusé.",
positionUnavailable: 'impossible de déterminer votre position.',
timeout: 'votre position est trop longue à récupérer.',
unavailable: "Désolé, la géolocalisation n'est pas disponible dans votre navigateur.",
},
intro: {
checkingPermissions: 'Vérification des permissions',
next: 'Suivant',
ready: 'Tout est prêt !',
reportTypes: 'Types de signalements',
startReporting: 'Commencer à signaler !',
welcome: 'Bienvenue !',
},
locationPicker: {
invalidSelection: 'Sélection invalide',
pickALocationManually: 'choisir une position manuellement',
},
menu: {
About: 'Aide',
Map: 'Carte',
Settings: 'Préférences',
},
misc: {
discard: 'Annuler',
or: 'ou',
retry: 'Réessayer',
spaceBeforeDoublePunctuations: ' ',
},
reportCard: {
Reported: 'Signalé',
},
reportDialog: {
unableToSendDescription: "Une erreur de réseau empêche l'envoi du dernier signalement.",
unableToSendTitle: "Impossible d'envoyer le dernier signalement",
},
reportLabels: {
accident: 'Accident',
accidentDescription: 'Un accident sur la route (automatiquement supprimé après une heure).',
gcum: 'GCUM',
gcumDescription: 'Une voiture (mal) garée sur la piste cyclable (automatiquement supprimé après une heure).',
interrupt: 'Interruption',
interruptDescription: "Une interruption d'itinéraire cyclable (travaux, arrêt inattendu d'une piste cyclable, etc)",
misc: 'Autre',
miscDescription: 'Un problème qui ne rentre dans aucune autre catégorie.',
obstacle: 'Obstacle',
obstacleDescription: 'Un obstacle sur la piste cyclable (granit de bordure, encombrants, etc)',
pothole: 'Nid de poule',
potholeDescription: 'Un nid de poule dans la route.',
},
settings: {
locale: 'Langue',
preventSuspend: "Empêcher l'appareil de passer en veille",
save: 'Sauver',
skipOnboarding: "Sauter l'introduction",
tileServer: 'Serveur de tuiles pour la carte',
},
};

78
src/i18n/fr.json Normal file
View File

@ -0,0 +1,78 @@
{
"about": {
"availableReportsTitle": "Les signalements disponibles pour l'instant sont :",
"geolocationDescription": "Dans la version actuelle, votre position est trait\u00e9e directement par votre appareil et n'est jamais envoy\u00e9e \u00e0 un service externe. Le fond de carte est t\u00e9l\u00e9charg\u00e9 \u00e0 la demande depuis le fournisseur de tuiles et il a donc acc\u00e8s \u00e0 une estimation de la position affich\u00e9e. Si vous refusez le partage de votre g\u00e9olocalisation, vous pourrez saisir une adresse manuellement \u00e0 la place mais vous perdrez les fonctionnalit\u00e9s avanc\u00e9es qui reposent sur la g\u00e9olocalisation.",
"license": "Le code source est sous <a href=\"https://opensource.org/licenses/MIT\">licence MIT</a> (<a href=\"https://framagit.org/phyks/cyclassist\">code source</a>). Les ic\u00f4nes sont bas\u00e9es sur des travaux de Wikimedia, Vecteezy, Pixabay ou Flaticon. Les tuiles de fond de carte proviennent de chez <a href=\"https://carto.com/location-data-services/basemaps/\">Carto.com</a> ou <a href=\"http://thunderforest.com/\">Thunderforest</a>, gr\u00e2ce aux <a href=\"https://www.openstreetmap.org/copyright\">contributeurs OpenStreetMap</a> et \u00e0 <a href=\"http://leafletjs.com/\">Leaflet</a>. Les signalements sont disponibles sous <a href=\"https://opendatacommons.org/licenses/odbl/\">licence ODbL</a>. La saisie manuelle de la position utilise l'excellente API de <a href=\"https://adresse.data.gouv.fr\">adresse.data.gouv.fr</a>.",
"summary": "Cette application vous permet de signaler et de partager des probl\u00e8mes avec les itin\u00e9raires cyclables.",
"usage": "Utilisation",
"usageDescription": "Utilisez le bouton en bas \u00e0 droite pour ajouter un signalement \u00e0 votre emplacement actuel. Pour ajouter un signalement ailleurs, faites un appui \u00e0 l'emplacement souhait\u00e9 sur la carte. Appuyer sur un marqueur sur la carte pour afficher plus d'informations et signaler que le probl\u00e8me est toujours pr\u00e9sent ou non."
},
"buttons": {
"close": "Fermer",
"downvote": "Infirmer",
"menu": "Menu",
"recenterMap": "Recentrer la carte",
"reportProblem": "Nouveau signalement",
"upvote": "Confirmer"
},
"geolocation": {
"errorFetchingPosition": "Impossible de r\u00e9cup\u00e9rer votre position :",
"fetching": "En attente de votre position\u2026",
"geolocation": "G\u00e9olocalisation",
"permissionDenied": "l'acc\u00e8s a votre position a \u00e9t\u00e9 refus\u00e9.",
"positionUnavailable": "impossible de d\u00e9terminer votre position.",
"timeout": "votre position est trop longue \u00e0 r\u00e9cup\u00e9rer.",
"unavailable": "D\u00e9sol\u00e9, la g\u00e9olocalisation n'est pas disponible dans votre navigateur."
},
"intro": {
"checkingPermissions": "V\u00e9rification des permissions",
"next": "Suivant",
"ready": "Tout est pr\u00eat !",
"reportTypes": "Types de signalements",
"startReporting": "Commencer \u00e0 signaler !",
"welcome": "Bienvenue !"
},
"locationPicker": {
"invalidSelection": "S\u00e9lection invalide",
"pickALocationManually": "choisir une position manuellement"
},
"menu": {
"About": "Aide",
"Map": "Carte",
"Settings": "Pr\u00e9f\u00e9rences"
},
"misc": {
"discard": "Annuler",
"or": "ou",
"retry": "R\u00e9essayer",
"spaceBeforeDoublePunctuations": " "
},
"reportCard": {
"Reported": "Signal\u00e9"
},
"reportDialog": {
"unableToSendDescription": "Une erreur de r\u00e9seau emp\u00eache l'envoi du dernier signalement.",
"unableToSendTitle": "Impossible d'envoyer le dernier signalement"
},
"reportLabels": {
"accident": "Accident",
"accidentDescription": "Un accident sur la route (automatiquement supprim\u00e9 apr\u00e8s une heure).",
"gcum": "GCUM",
"gcumDescription": "Une voiture (mal) gar\u00e9e sur la piste cyclable (automatiquement supprim\u00e9 apr\u00e8s une heure).",
"interrupt": "Interruption",
"interruptDescription": "Une interruption d'itin\u00e9raire cyclable (travaux, arr\u00eat inattendu d'une piste cyclable, etc)",
"misc": "Autre",
"miscDescription": "Un probl\u00e8me qui ne rentre dans aucune autre cat\u00e9gorie.",
"obstacle": "Obstacle",
"obstacleDescription": "Un obstacle sur la piste cyclable (granit de bordure, encombrants, etc)",
"pothole": "Nid de poule",
"potholeDescription": "Un nid de poule dans la route."
},
"settings": {
"locale": "Langue",
"preventSuspend": "Emp\u00eacher l'appareil de passer en veille",
"save": "Sauver",
"skipOnboarding": "Sauter l'introduction",
"tileServer": "Serveur de tuiles pour la carte"
}
}

View File

@ -1,8 +1,8 @@
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import en from './en';
import fr from './fr';
import en from './en.json';
import fr from './fr.json';
export function getBrowserLocales() {
let langs = [];

8
zanata.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<config xmlns="http://zanata.org/namespace/config/">
<url>https://translate.zanata.org/</url>
<project>cyclassist</project>
<project-version>master</project-version>
<project-type>gettext</project-type>
</config>