Combine and reuse translations for keyboard shortcuts

Using i18next's "nesting" and "interpolation" features should reduce the
overall number of strings to translate, as well as provide a
standardized pattern for shortcut tooltips (if applicable).

Note that this approach is still allowing for flexibility regarding
differently structured sentences in each language.

Resolves #315
This commit is contained in:
Henrik Fehlauer 2020-06-21 18:00:00 +00:00
parent e3a9f6206f
commit 09f987ea07
9 changed files with 156 additions and 66 deletions

View file

@ -61,7 +61,8 @@
role="button"
aria-haspopup="true"
aria-expanded="false"
data-i18n="[title]navbar.export-tooltip"
data-i18n="[title]keyboard.generic-shortcut"
data-i18n-options='{ "action": "$t(navbar.export-tooltip)", "key": "X" }'
title="Export route"
>
<span class="fa fa-lg fa-cloud-download" aria-hidden="true"> </span>
@ -79,6 +80,12 @@
aria-haspopup="true"
aria-expanded="false"
data-i18n="[title]navbar.load.tooltip"
data-i18n-options='{
"tracksAction": "$t(navbar.load.tracks)",
"tracksKey": "O",
"nogosAction": "$t(navbar.load.nogos)",
"nogosKey": "$t(keyboard.shift)+O"
}'
title="Load route"
>
<span class="fa fa-lg fa-cloud-upload" aria-hidden="true"> </span>
@ -113,7 +120,8 @@
</div>
<div
class="nav-item"
data-i18n="[title]about.tooltip"
data-i18n="[title]keyboard.generic-shortcut"
data-i18n-options='{ "action": "$t(about.tooltip)", "key": "H" }'
title="Show more information about BRouter-Web"
>
<a class="nav-link" href="#" data-toggle="modal" data-target="#about"
@ -314,7 +322,7 @@
updated daily, see dates of
<a href="https://brouter.de/brouter/segments4/" target="_blank">data files</a>.
</p>
<p data-i18n="[html]about.details">
<p id="aboutLinks" data-i18n="[html]about.details">
<i><a href="{{privacyPolicyUrl}}" target="_blank">Privacy Policy</a></i
>,
<i
@ -709,7 +717,12 @@
<a
href="#tab_layers_control"
role="tab"
data-i18n="[title]sidebar.layers.tooltip"
data-i18n="[title]sidebar.tab-tooltip"
data-i18n-options='{
"action": "$t(sidebar.layers.tooltip)",
"toggleKey": "T",
"switchKey": "$t(keyboard.shift)+T"
}'
title="Select layers"
>
<!--
@ -738,7 +751,12 @@
<a
href="#tab_itinerary"
role="tab"
data-i18n="[title]sidebar.itinerary.tooltip"
data-i18n="[title]sidebar.tab-tooltip"
data-i18n-options='{
"action": "$t(sidebar.itinerary.tooltip)",
"toggleKey": "T",
"switchKey": "$t(keyboard.shift)+T"
}'
title="Show Itinerary"
><i class="fa fa-map-signs"></i
></a>
@ -747,7 +765,12 @@
<a
href="#tab_profile"
role="tab"
data-i18n="[title]sidebar.customize-profile.tooltip"
data-i18n="[title]sidebar.tab-tooltip"
data-i18n-options='{
"action": "$t(sidebar.customize-profile.tooltip)",
"toggleKey": "T",
"switchKey": "$t(keyboard.shift)+T"
}'
title="Customize profile"
><i class="fa fa-wrench"></i
></a>
@ -756,7 +779,12 @@
<a
href="#tab_data"
role="tab"
data-i18n="[title]sidebar.data.tooltip"
data-i18n="[title]sidebar.tab-tooltip"
data-i18n-options='{
"action": "$t(sidebar.data.tooltip)",
"toggleKey": "T",
"switchKey": "$t(keyboard.shift)+T"
}'
title="Show detailed route data table"
><i class="fa fa-table"></i
></a>
@ -765,7 +793,12 @@
<a
href="#tab_statistics"
role="tab"
data-i18n="[title]sidebar.analysis.tooltip"
data-i18n="[title]sidebar.tab-tooltip"
data-i18n-options='{
"action": "$t(sidebar.analysis.tooltip)",
"toggleKey": "T",
"switchKey": "$t(keyboard.shift)+T"
}'
title="Analyse route"
><i class="fa fa-pie-chart"></i
></a>
@ -1073,7 +1106,8 @@
id="elevation-btn"
aria-expanded="false"
aria-label="Toggle elevation chart"
data-i18n="[title]footer.elevation-chart"
data-i18n="[title]keyboard.generic-shortcut"
data-i18n-options='{ "action": "$t(footer.elevation-chart)", "key": "E" }'
title="Toggle elevation chart"
>
<span class="fa fa-area-chart"></span>

View file

@ -24,8 +24,8 @@ BR.Map = {
if (BR.Util.getResponsiveBreakpoint() >= '3md') {
L.control
.zoom({
zoomInTitle: i18next.t('map.zoomInTitle'),
zoomOutTitle: i18next.t('map.zoomOutTitle')
zoomInTitle: i18next.t('keyboard.generic-shortcut', { action: '$t(map.zoomInTitle)', key: '+' }),
zoomOutTitle: i18next.t('keyboard.generic-shortcut', { action: '$t(map.zoomOutTitle)', key: '-' })
})
.addTo(map);
}
@ -105,7 +105,7 @@ BR.Map = {
var locationControl = L.control
.locate({
strings: {
title: i18next.t('map.locate-me')
title: i18next.t('keyboard.generic-shortcut', { action: '$t(map.locate-me)', key: 'L' })
},
icon: 'fa fa-location-arrow',
iconLoading: 'fa fa-spinner fa-pulse'

View file

@ -48,7 +48,7 @@ BR.RoutingOptions = L.Evented.extend({
// append shortcut text to tooltip
var button = $('#profile-alternative-form button')[0];
button.title = button.title + i18next.t('navbar.profile-tooltip');
button.title = button.title + i18next.t('navbar.profile-tooltip', { key: 'G' });
},
getOptions: function() {

View file

@ -46,7 +46,10 @@
search = new BR.Search();
map.addControl(search);
$('#map .leaflet-control-geocoder > button')[0].title = i18next.t('map.geocoder');
$('#map .leaflet-control-geocoder > button')[0].title = i18next.t('keyboard.generic-shortcut', {
action: '$t(map.geocoder)',
key: 'F'
});
router = L.bRouter(); //brouterCgi dummyRouter
@ -59,7 +62,10 @@
routing.draw(false);
control.state('activate-draw');
},
title: i18next.t('map.draw-route-stop')
title: i18next.t('keyboard.generic-shortcut', {
action: '$t(map.draw-route-stop)',
key: '$t(keyboard.escape)'
})
},
{
stateName: 'activate-draw',
@ -68,7 +74,7 @@
routing.draw(true);
control.state('deactivate-draw');
},
title: i18next.t('map.draw-route-start')
title: i18next.t('keyboard.generic-shortcut', { action: '$t(map.draw-route-start)', key: 'D' })
}
]
});
@ -78,7 +84,7 @@
function() {
routing.reverse();
},
i18next.t('map.reverse-route')
i18next.t('keyboard.generic-shortcut', { action: '$t(map.reverse-route)', key: 'R' })
);
var deletePointButton = L.easyButton(
@ -86,7 +92,7 @@
function() {
routing.deleteLastPoint();
},
i18next.t('map.delete-last-point')
i18next.t('keyboard.generic-shortcut', { action: '$t(map.delete-last-point)', key: 'Z' })
);
deleteRouteButton = L.easyButton(
@ -94,7 +100,7 @@
function() {
clearRoute();
},
i18next.t('map.clear-route-tooltip')
i18next.t('keyboard.generic-shortcut', { action: '$t(map.clear-route)', key: '$t(keyboard.backspace)' })
);
L.DomEvent.addListener(
@ -175,8 +181,11 @@
profile.update(evt.options);
});
BR.NogoAreas.MSG_BUTTON = i18next.t('map.nogo.draw');
BR.NogoAreas.MSG_BUTTON_CANCEL = i18next.t('map.nogo.cancel');
BR.NogoAreas.MSG_BUTTON = i18next.t('keyboard.generic-shortcut', { action: '$t(map.nogo.draw)', key: 'N' });
BR.NogoAreas.MSG_BUTTON_CANCEL = i18next.t('keyboard.generic-shortcut', {
action: '$t(map.nogo.cancel)',
key: '$t(keyboard.escape)'
});
BR.NogoAreas.MSG_CREATE = i18next.t('map.nogo.click-drag');
BR.NogoAreas.MSG_DISABLED = i18next.t('map.nogo.edit');
BR.NogoAreas.MSG_ENABLED = i18next.t('map.nogo.help');
@ -313,7 +322,7 @@
map.addControl(
new BR.OpacitySliderControl({
id: 'route',
title: i18next.t('map.opacity-slider'),
title: i18next.t('map.opacity-slider-shortcut', { action: '$t(map.opacity-slider)', key: 'M' }),
muteKeyCode: 77, // m
callback: L.bind(routing.setOpacity, routing)
})
@ -456,8 +465,9 @@
}
},
function(err, t) {
jqueryI18next.init(i18next, $);
$('html').localize({
jqueryI18next.init(i18next, $, { useOptionsAttr: true });
$('html').localize();
$('#aboutLinks').localize({
privacyPolicyUrl: BR.conf.privacyPolicyUrl || 'https://brouter.de/privacypolicy.html'
});

View file

@ -25,7 +25,7 @@ BR.PoiMarkers = L.Control.extend({
onClick: function() {
self.draw(true);
},
title: i18next.t('map.draw-poi-start')
title: i18next.t('keyboard.generic-shortcut', { action: '$t(map.draw-poi-start)', key: 'P' })
},
{
stateName: 'deactivate-poi',
@ -33,7 +33,10 @@ BR.PoiMarkers = L.Control.extend({
onClick: function() {
self.draw(false);
},
title: i18next.t('map.draw-poi-stop')
title: i18next.t('keyboard.generic-shortcut', {
action: '$t(map.draw-poi-stop)',
key: '$t(keyboard.escape)'
})
}
]
}).addTo(map);

View file

@ -21,7 +21,7 @@ BR.RoutingPathQuality = L.Control.extend({
this.providers = {
incline: {
title: i18next.t('map.route-quality-incline'),
title: i18next.t('map.route-quality-shortcut', { action: '$t(map.route-quality-incline)', key: 'C' }),
icon: 'fa-line-chart',
provider: new HotLineQualityProvider({
hotlineOptions: {
@ -48,7 +48,7 @@ BR.RoutingPathQuality = L.Control.extend({
})
},
altitude: {
title: i18next.t('map.route-quality-altitude'),
title: i18next.t('map.route-quality-shortcut', { action: '$t(map.route-quality-altitude)', key: 'C' }),
icon: 'fa-area-chart',
provider: new HotLineQualityProvider({
hotlineOptions: {
@ -61,7 +61,7 @@ BR.RoutingPathQuality = L.Control.extend({
})
},
cost: {
title: i18next.t('map.route-quality-cost'),
title: i18next.t('map.route-quality-shortcut', { action: '$t(map.route-quality-cost)', key: 'C' }),
icon: 'fa-usd',
provider: new HotLineQualityProvider({
hotlineOptions: {

View file

@ -1,8 +1,8 @@
BR.stravaSegments = function(map, layersControl) {
var stravaControl = L.control
.stravaSegments({
runningTitle: i18next.t('map.strava-running'),
bikingTitle: i18next.t('map.strava-biking'),
runningTitle: i18next.t('map.strava-shortcut', { action: '$t(map.strava-running)', key: 'S' }),
bikingTitle: i18next.t('map.strava-shortcut', { action: '$t(map.strava-biking)', key: 'S' }),
loadingTitle: i18next.t('map.loading'),
stravaToken: BR.keys.strava
})

View file

@ -12,7 +12,7 @@
"details": "<i><a href=\"{{privacyPolicyUrl}}\" target=\"_blank\">Privacy Policy</a></i>, \n<i><a href=\"https://github.com/nrenner/brouter-web#credits-and-licenses\" target=\"_blank\">Credits</a></i>,\n<i><a href=\"https://github.com/nrenner/brouter-web/blob/master/CHANGELOG.md\" target=\"_blank\">Changelog</a></i> and\n<i><a href=\"https://github.com/nrenner/brouter-web#readme\" target=\"_blank\">more info</a></i> on the client.",
"support": "General discussions/questions, support",
"title": "About",
"tooltip": "Show more information about Brouter-Web (H key)"
"tooltip": "Show more information about Brouter-Web"
},
"credits": {
"brouter": "BRouter",
@ -42,7 +42,7 @@
"ascend": "Ascend",
"cost": "Cost",
"distance": "Distance",
"elevation-chart": "Toggle elevation chart (E key)",
"elevation-chart": "Toggle elevation chart",
"energy-per-100km": "Energy per 100 km",
"hours": "hours",
"hours-abbrev": "h",
@ -58,6 +58,12 @@
"total-energy": "Total Energy",
"travel-time": "Travel time"
},
"keyboard": {
"backspace": "Backspace",
"escape": "Escape",
"generic-shortcut": "{{action}} ({{key}} key)",
"shift": "Shift"
},
"layers": {
"add-base": "Add base layer",
"add-overlay": "Add overlay",
@ -82,19 +88,18 @@
"attribution-osm-long": "OpenStreetMap contributors",
"attribution-osm-short": "OpenStreetMap",
"clear-route": "Clear route data",
"clear-route-tooltip": "Clear route data (Backspace key)",
"copyright": "Copyright",
"cycling": "Cycling",
"delete-last-point": "Delete last point (Z key)",
"delete-last-point": "Delete last point",
"delete-nogo-areas": "Delete all no-go areas",
"delete-pois": "Delete all points of interest",
"delete-route": "Delete route",
"draw-poi-start": "Draw points of interest (P key)",
"draw-poi-stop": "Stop drawing points of interest (ESC key)",
"draw-route-start": "Draw route (D key)",
"draw-route-stop": "Stop drawing route (ESC key)",
"draw-poi-start": "Draw points of interest",
"draw-poi-stop": "Stop drawing points of interest",
"draw-route-start": "Draw route",
"draw-route-stop": "Stop drawing route",
"enter-poi-name": "Enter Point of Interest name",
"geocoder": "Search (F key)",
"geocoder": "Search",
"geocoder-placeholder": "Search…",
"hikebike-hillshading": "Hillshading",
"hiking": "Hiking",
@ -115,25 +120,28 @@
"topo": "OpenTopoMap"
},
"loading": "Loading…",
"locate-me": "Show me where I am (L key)",
"locate-me": "Show me where I am",
"nogo": {
"cancel": "Cancel drawing no-go area (ESC key)",
"cancel": "Cancel drawing no-go area",
"click-drag": "Click and drag to draw circle",
"draw": "Draw circular no-go area (N key)",
"draw": "Draw circular no-go area",
"edit": "Click to edit",
"help": "&square; = move / resize, <span class=\"fa fa-trash-o\"></span> = delete,<br>click circle to quit editing"
},
"opacity-slider": "Set transparency of route track and markers\n(Hold M key to mute temporarily)",
"opacity-slider": "Set transparency of route track and markers",
"opacity-slider-shortcut": "{{action}}\n(Hold {{key}} key to mute temporarily)",
"preview": "Preview",
"privacy": "Privacy",
"reverse-route": "Reverse route (R key)",
"route-quality-altitude": "Altitude coding (C key to toggle)",
"route-quality-cost": "Cost coding (C key to toggle)",
"route-quality-incline": "Incline coding (C key to toggle)",
"strava-biking": "Show Strava biking segments\n(S key to toggle layer, click to reload for current area)",
"strava-running": "Show Strava running segments\n(S key to toggle layer, click to reload for current area)",
"zoomInTitle": "Zoom in (+ key)",
"zoomOutTitle": "Zoom out (- key)"
"reverse-route": "Reverse route",
"route-quality-altitude": "Altitude coding",
"route-quality-cost": "Cost coding",
"route-quality-incline": "Incline coding",
"route-quality-shortcut": "{{action}} ({{key}} key to toggle)",
"strava-biking": "Show Strava biking segments",
"strava-running": "Show Strava running segments",
"strava-shortcut": "{{action}}\n({{key}} key to toggle layer, click to reload for current area)",
"zoomInTitle": "Zoom in",
"zoomOutTitle": "Zoom out"
},
"modal": {
"close": "Close"
@ -147,12 +155,12 @@
"third": "3rd alternative"
},
"export": "Export",
"export-tooltip": "Export route (X key)",
"export-tooltip": "Export route",
"load": {
"nogos": "No-go areas",
"nogos": "Load no-go areas",
"title": "Load",
"tooltip": "Load tracks (O key)\nLoad No-go areas (Shift+O)",
"tracks": "Tracks"
"tooltip": "{{tracksAction}} ({{tracksKey}} key)\n{{nogosAction}} ({{nogosKey}})",
"tracks": "Load tracks"
},
"profile": {
"car-eco": "Car (economic)",
@ -176,7 +184,7 @@
"vm-forum-liegerad-schnell": "Recumbent bike (fast)",
"vm-forum-velomobil-schnell": "Velomobile (fast)"
},
"profile-tooltip": "\n(G key to switch)"
"profile-tooltip": "\n({{key}} key to switch)"
},
"sidebar": {
"analysis": {
@ -192,20 +200,20 @@
"unknown": "Unknown"
},
"title": "Analysis",
"tooltip": "Analyse route\n(T key to toggle, Shift+T to switch to next tab)"
"tooltip": "Analyse route"
},
"customize-profile": {
"title": "Customize profile",
"tooltip": "Customize profile\n(T key to toggle, Shift+T to switch to next tab)"
"tooltip": "Customize profile"
},
"data": {
"sync-map": "Synchronize map",
"title": "Data",
"tooltip": "Show detailed route data table\n(T key to toggle, Shift+T to switch to next tab)"
"tooltip": "Show detailed route data table"
},
"itinerary": {
"title": "Itinerary",
"tooltip": "Show itinerary\n(T key to toggle, Shift+T to switch to next tab)"
"tooltip": "Show itinerary"
},
"layers": {
"category": {
@ -232,7 +240,7 @@
"type": "Type"
},
"title": "Layers",
"tooltip": "Select layers\n(T key to toggle, Shift+T to switch to next tab)"
"tooltip": "Select layers"
},
"profile": {
"apply": "Apply",
@ -242,7 +250,8 @@
"options": "Options",
"placeholder": "Write your custom profile here.",
"profile": "Profile"
}
},
"tab-tooltip": "{{action}}\n({{toggleKey}} key to toggle, {{switchKey}} to switch to next tab)"
},
"title": "BRouter web client",
"trackasroute": {

View file

@ -1,4 +1,33 @@
// this file contains translatable keys that are dynamic / not visible by i18n extractor tool
i18next.t('about.tooltip');
i18next.t('footer.elevation-chart');
i18next.t('keyboard.backspace');
i18next.t('keyboard.escape');
i18next.t('keyboard.shift');
i18next.t('map.delete-last-point');
i18next.t('map.draw-poi-start');
i18next.t('map.draw-poi-stop');
i18next.t('map.draw-route-start');
i18next.t('map.draw-route-stop');
i18next.t('map.geocoder');
i18next.t('map.locate-me');
i18next.t('map.nogo.cancel');
i18next.t('map.nogo.draw');
i18next.t('map.opacity-slider');
i18next.t('map.reverse-route');
i18next.t('map.route-quality-altitude');
i18next.t('map.route-quality-cost');
i18next.t('map.route-quality-incline');
i18next.t('map.strava-biking');
i18next.t('map.strava-running');
i18next.t('map.zoomInTitle');
i18next.t('map.zoomOutTitle');
i18next.t('navbar.export-tooltip');
i18next.t('navbar.profile.car-eco');
i18next.t('navbar.profile.car-fast');
i18next.t('navbar.profile.car-test');
@ -20,11 +49,16 @@ i18next.t('navbar.profile.trekking-steep');
i18next.t('navbar.profile.vm-forum-liegerad-schnell');
i18next.t('navbar.profile.vm-forum-velomobil-schnell');
i18next.t('sidebar.analysis.tooltip');
i18next.t('sidebar.customize-profile.tooltip');
i18next.t('sidebar.data.tooltip');
i18next.t('sidebar.itinerary.tooltip');
i18next.t('sidebar.layers.category.base-layers', 'Base layers');
i18next.t('sidebar.layers.category.worldwide-international', 'Worldwide international');
i18next.t('sidebar.layers.category.worldwide-monolingual', 'Worldwide monolingual');
i18next.t('sidebar.layers.category.country', 'Country');
i18next.t('sidebar.layers.category.europe', 'Europe');
i18next.t('sidebar.layers.category.europe-monolingual', 'Europe monolingual');
i18next.t('sidebar.layers.category.country', 'Country');
i18next.t('sidebar.layers.category.overlays', 'Overlays');
i18next.t('sidebar.layers.category.worldwide', 'Worldwide');
i18next.t('sidebar.layers.category.worldwide-international', 'Worldwide international');
i18next.t('sidebar.layers.category.worldwide-monolingual', 'Worldwide monolingual');
i18next.t('sidebar.layers.tooltip');