diff --git a/bower.json b/bower.json
index 00f405129..452cf7824 100644
--- a/bower.json
+++ b/bower.json
@@ -26,6 +26,9 @@
"nz-tour": "^1.2.1",
"jquery": "^3.1.0",
"leaflet-plugins": "^3.0.0",
- "angularjs-slider": "^6.2.2"
+ "angularjs-slider": "^6.2.2",
+ "angular-translate": "^2.18.1",
+ "angular-translate-loader-static-files": "^2.18.1",
+ "angular-translate-interpolation-messageformat": "^2.18.1"
}
}
diff --git a/www/i18n/en.json b/www/i18n/en.json
new file mode 100644
index 000000000..2e356b6d3
--- /dev/null
+++ b/www/i18n/en.json
@@ -0,0 +1,247 @@
+{
+ "loading" : "Loading...",
+ "map-refresh": "Refresh",
+ "map-fixmap": "Fix Map",
+
+ "weekdays-all": "All",
+ "weekdays-select": "Select day of the week",
+
+ "post-trip-prompt":{
+ "notification-option-mute": "Mute",
+ "notification-option-snooze": "Snooze",
+ "notification-option-choose": "Choose",
+ "notification-title": "How and why did you come here?",
+ "choose-mode": "Choose Mode",
+ "skip": "Skip",
+ "snoozed-reminder": "Snoozed reminder",
+ "snoozed-reapper-message": "Will reappear in 30 mins",
+ "platform-specific-message-ios": "Swipe left or tap to add information about this trip.",
+ "platform-specific-message-android": "See options or tap to add information about this trip.",
+ "platform-specific-message-other": "Tap to add information about this trip.",
+ "notifications-muted": "Notifications for TRIP_END incident report muted",
+ "notifications-reenabled": "Can be re-enabled from the Profile -> Developer Zone screen. Select to re-enable now, clear to ignore",
+ "muted": "Muted",
+ "unmute": "Unmute",
+ "keep-muted": "Keep muted"
+ },
+
+ "post-trip-map-display-tour-incident": "Zoom in as much as possible to the location where the incident occurred and click on the blue line of the trip to mark a ☻ or ☹ incident",
+
+ "tour-next": "Next",
+ "tour-previous": "Previous",
+ "tour-finish": "Finish",
+
+ "trip-confirm": {
+ "recenttrip": "Recent trip from: {{startTime}} → to: {{endTime}}",
+ "continue": "Continue",
+ "done": "Done",
+ "services-please-fill-in": "Please fill in the {{text}} not listed.",
+ "services-cancel": "Cancel",
+ "services-save": "Save"
+ },
+
+ "place-common-place": "Common place",
+ "place-successor-trips": "Successor trips",
+ "place-trips-to": "{{trips}} trips to",
+ "place-usually-starts": "Usually starts at: {{hour}}:00",
+ "place-usually-takes": "Usually takes: {{duration}}",
+
+ "trip-start-hours": "Start hours",
+ "trip-start-duration": "Duration",
+
+ "control":{
+ "profile": "Profile",
+ "tracking": "Tracking",
+ "medium-accuracy": "Medium accuracy",
+ "dark-theme": "Dark theme",
+ "force-sync": "Force sync",
+ "share": "Share",
+ "check-ui-updates": "Check for UI updates",
+ "download-json-dump": "Download json dump",
+ "email-log": "Email log",
+ "user-data": "User data",
+ "erase-data": "Erase data",
+ "dev-zone": "Developer zone",
+ "refresh": "Refresh",
+ "end-trip-sync": "End trip + sync",
+ "check-consent": "Check consent",
+ "invalidate-cached-docs": "Invalidate cached docs",
+ "nuke-all": "Nuke all buffers and cache",
+ "set-ui-channel": "Set UI channel",
+ "check-log": "Check log",
+ "check-sensed-data": "Check sensed data",
+ "check-map": "Check map",
+ "collection": "Collection",
+ "sync": "Sync",
+ "transition-notify": "Transition Notify"
+ },
+
+ "general-settings":{
+ "choose-date" : "Choose date to download data",
+ "nuke-ui-state-only" : "UI state only",
+ "nuke-native-cache-only" : "Native cache only",
+ "nuke-everything" : "Everything",
+ "clear-data": "Clear data",
+ "cancel": "Cancel",
+ "user-data-erased": "User data erased.",
+ "consent-not-found": "Consent for data collection not found, consent now?",
+ "no-consent-message": "OK! Note that you won't get any personalized stats until you do!",
+ "consent-found": "Consent found!",
+ "consented-to": "Consented to protocol {{protocol_id}}, {{approval_date}}",
+ "consented-ok": "OK",
+ "share-message": "Join me in making transportation greener and healthier \nDownload the emission app:",
+ "share-subject": "Emission - UC Berkeley Research Project",
+ "share-url": "https://bic2cal.eecs.berkeley.edu/#download"
+ },
+
+ "metrics":{
+ "cancel": "Cancel",
+ "confirm": "Confirm",
+ "get": "Get",
+ "range": "Range",
+ "filter": "Filter",
+ "from": "From:",
+ "to": "To:",
+ "last-week": "Last week",
+ "frequency": "Frequency:",
+ "pandafreqoptions-daily": "DAILY",
+ "pandafreqoptions-weekly": "WEEKLY",
+ "pandafreqoptions-biweekly": "BIWEEKLY",
+ "pandafreqoptions-monthly": "MONTHLY",
+ "pandafreqoptions-yearly": "YEARLY",
+ "freqoptions-daily": "DAILY",
+ "freqoptions-monthly": "MONTHLY",
+ "freqoptions-yearly": "YEARLY",
+ "select-pandafrequency": "Select summary freqency",
+ "select-frequency": "Select summary freqency",
+ "chart-xaxis-date": "Date",
+ "chart-no-data": "No Data Available",
+ "trips-yaxis-number": "Number",
+ "calorie-data-change": " change",
+ "carbon-data-change": " change",
+ "carbon-data-calculating": "Calculating...",
+ "carbon-data-unknown": "Unknown",
+ "calorie-data-unknown": "Unknown...",
+ "calorie-data-change-increase": " increase over a week",
+ "calorie-data-change-decrease": " decrease over a week",
+ "carbon-data-change-increase": " increase over a week",
+ "carbon-data-change-decrease": " decrease over a week",
+ "pick-a-date": "Pick a date"
+ },
+
+ "diary": {
+ "current-trip": "Current Trip",
+ "current-yesterday": "Yesterday",
+ "current-weekago": "Week ago",
+ "history": "History",
+ "began": "Began {{startTime}}",
+ "report-incident": "Report Incident",
+ "draft": "DRAFT",
+ "distance-in-time": "{{distance}} km in {{time}}",
+ "distance": "Distance",
+ "time": "Time",
+ "mode": "Mode",
+ "purpose": "Purpose",
+ "choose-mode": "Choose Mode",
+ "choose-purpose": "Choose Purpose",
+ "how-did-you-get-here": "How did you get here?",
+ "why-did-you-come-here": "Why did you come here?",
+ "list-pick-a-date": "Pick a date"
+ },
+
+ "user-gender": "Gender",
+ "gender-male": "Male",
+ "gender-female": "Female",
+ "user-height": "Height",
+ "user-weight": "Weight",
+ "user-age": "Age",
+
+ "main-metrics":{
+ "dashboard": "Dashboard",
+ "summary": "Summary",
+ "chart": "Chart",
+ "change-data": "Change data:",
+ "distance": "Distance",
+ "trips": "Trips",
+ "duration": "Duration",
+ "speed": "Speed",
+ "footprint": "Footprint",
+ "optimal": "Optimal:",
+ "average": "Average:",
+ "worst": "Worst:",
+ "lastweek": "Last Week:",
+ "calories": "Calories",
+ "calibrate": "Calibrate",
+ "no-summary-data": "No summary data",
+ "median-speed": "Median Speed",
+ "equals-cookies": "Equals {cookies, plural, =0{0 homemade chocolate chip cookies} one {1 homemade chocolate chip cookie} other {# homemade chocolate chip cookies}}",
+ "equals-icecream": "Equals {icecream, plural, =0{0 half cups vanilla ice cream} one {1 half cup vanilla ice cream} other {# half cups vanilla ice cream}}",
+ "equals-bananas": "Equals {bananas, plural, =0{0 bananas} one {1 banana} other {# bananas}}"
+ },
+
+ "main-diary" : "Diary",
+
+ "main-heatmap":{
+ "title": "Heatmap",
+ "counts" : "Counts",
+ "stress" : "Stress",
+ "from" : "From:",
+ "to" : "To:",
+ "get" : "Get!",
+ "all": "ALL",
+ "none": "NONE",
+ "bicycling": "BICYCLING",
+ "walking": "WALKING",
+ "in-vehicle": "IN_VEHICLE",
+ "select-travel-mode" : "Select travel mode",
+ "cancel": "Cancel",
+ "tour-datepicker": "This heatmap shows the aggregate data for all E-mission users. Select the dates you want to see, and filter by hours of the day (24h format) and days of the week. For example, if you enter 16 and 19 in the last field, and select Monday and Friday, you'll see the Heatmap filtered to show the traffic on weekdays between 4pm and 7pm.",
+ "tour-mode": "Click here to filter your results by mode of transportation. The default is to show all modes.",
+ "tour-get": "Click here to generate the heatmap."
+ },
+
+ "details":{
+ "speed": "Speed",
+ "time": "Temps",
+ "tour-detail-content": "To report an incident, zoom in as much as possible to the location where the incident occurred and click on the trip to mark a ☻ or ☹ incident",
+ "tour-sectionList-content": "Trip sections, along with times and modes",
+ "tour-sectionPct-content": "% of time spent in each mode for this trip"
+ },
+
+ "list-explainDraft-alert": "This trip has not yet been analysed. If it stays in this state, please ask your sysadmin to check what is wrong.",
+ "list-datepicker-today": "Today",
+ "list-datepicker-close": "Close",
+ "list-datepicker-set": "Set",
+ "list-tour-datepicker-button" : "Use this to select the day you want to see.",
+ "list-tour-diary-entry" : "Click on the map to see more details about each trip.",
+ "list-tour-map-fix-button" : "Use this to fix the map tiles if they have not loaded properly.",
+
+ "service":{
+ "reading-server": "Reading from server...",
+ "reading-cache": "Reading from cache...",
+ "reading-unprocessed-data": "Reading unprocessed data..."
+ },
+
+
+ "post-trip-manual-incident-time" : "Choose incident time",
+
+ "recent":{
+ "email-account-not-configured": "Email account is not configured, cannot send email",
+ "email-account-mail-app": "You must have the mail app on your phone configured with an email address. Otherwise, this won't work",
+ "going-to-email": "Going to email database from {{parentDir}}/userCacheDB"
+ },
+
+ "consent":{
+ "button-accept": "I accept",
+ "button-decline": "I refuse"
+ },
+
+ "updatecheck":{
+ "downloading-update": "Downloading UI-only update",
+ "extracting-update": "Extracting UI-only update",
+ "done": "Update done, reloading...",
+ "download-new-ui": "Download new UI-only update?.",
+ "download-not-now": "Not now",
+ "download-apply": "Apply"
+ }
+}
diff --git a/www/index.html b/www/index.html
index d4f39f03f..dbd1648da 100644
--- a/www/index.html
+++ b/www/index.html
@@ -3,7 +3,7 @@
-
+
@@ -30,7 +30,13 @@
+
+
+
+
+
+
diff --git a/www/js/app.js b/www/js/app.js
index 0914f9960..2711e562e 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -11,7 +11,8 @@ angular.module('emission', ['ionic',
'emission.controllers','emission.services', 'emission.plugin.logger',
'emission.splash.customURLScheme', 'emission.splash.referral',
'emission.splash.updatecheck',
- 'emission.intro', 'emission.main'])
+ 'emission.intro', 'emission.main',
+ 'pascalprecht.translate'])
.run(function($ionicPlatform, $rootScope, $http, Logger,
CustomURLScheme, ReferralHandler, UpdateCheck) {
@@ -70,7 +71,7 @@ angular.module('emission', ['ionic',
console.log("Ending run");
})
-.config(function($stateProvider, $urlRouterProvider) {
+.config(function($stateProvider, $urlRouterProvider, $translateProvider) {
console.log("Starting config");
// alert("config");
@@ -102,5 +103,23 @@ angular.module('emission', ['ionic',
// alert("about to fall back to otherwise");
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/splash');
+
+ // Allow the use of MessageForm interpolation for Gender and Plural.
+ $translateProvider.addInterpolation('$translateMessageFormatInterpolation');
+
+ // Define where we can find the .json and the fallback language
+ $translateProvider
+ .fallbackLanguage('en')
+ .registerAvailableLanguageKeys(['en', 'fr'], {
+ 'en_*': 'en',
+ 'fr_*': 'fr',
+ '*': 'en'
+ })
+ .determinePreferredLanguage()
+ .useStaticFilesLoader({
+ prefix: 'i18n/',
+ suffix: '.json'
+ });
+
console.log("Ending config");
});
diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js
index 2f616fad7..c74b9ec08 100644
--- a/www/js/control/general-settings.js
+++ b/www/js/control/general-settings.js
@@ -21,13 +21,16 @@ angular.module('emission.main.control',['emission.services',
ControlCollectionHelper, ControlSyncHelper,
ControlTransitionNotifyHelper,
UpdateCheck,
- CalorieCal, ClientStats, CommHelper, Logger) {
+ CalorieCal, ClientStats, CommHelper, Logger,
+ $translate) {
var datepickerObject = {
- todayLabel: 'Today', //Optional
- closeLabel: 'Close', //Optional
- setLabel: 'Set', //Optional
- titleLabel: 'Choose date to download data',
+ todayLabel: $translate.instant('list-datepicker-today'), //Optional
+ closeLabel: $translate.instant('list-datepicker-close'), //Optional
+ setLabel: $translate.instant('list-datepicker-set'), //Optional
+ monthsList: moment.monthsShort(),
+ weeksList: moment.weekdaysMin(),
+ titleLabel: $translate.instant('general-settings.choose-date'),
setButtonType : 'button-positive', //Optional
todayButtonType : 'button-stable', //Optional
closeButtonType : 'button-stable', //Optional
@@ -61,7 +64,7 @@ angular.module('emission.main.control',['emission.services',
age: userDataFromStorage.age,
height: height + (userDataFromStorage.heightUnit == 1? ' cm' : ' ft'),
weight: weight + (userDataFromStorage.weightUnit == 1? ' kg' : ' lb'),
- gender: userDataFromStorage.gender == 1? 'Male' : 'Female'
+ gender: userDataFromStorage.gender == 1? $translate.instant('gender-male') : $translate.instant('gender-female')
}
for (var i in temp) {
$scope.userData.push({key: i, value: temp[i]});
@@ -183,16 +186,16 @@ angular.module('emission.main.control',['emission.services',
}
$scope.nukeUserCache = function() {
- var nukeChoiceActions = [{text: "UI state only",
+ var nukeChoiceActions = [{text: $translate.instant('general-settings.nuke-ui-state-only'),
action: KVStore.clearOnlyLocal},
- {text: 'Native cache only',
+ {text: $translate.instant('general-settings.nuke-native-cache-only'),
action: KVStore.clearOnlyNative},
- {text: 'Everything',
+ {text: $translate.instant('general-settings.nuke-everything'),
action: KVStore.clearAll}];
$ionicActionSheet.show({
- titleText: "Clear data",
- cancelText: "Cancel",
+ titleText: $translate.instant('general-settings.clear-data'),
+ cancelText: $translate.instant('general-settings.cancel'),
buttons: nukeChoiceActions,
buttonClicked: function(index, button) {
button.action();
@@ -414,7 +417,7 @@ angular.module('emission.main.control',['emission.services',
}
$scope.eraseUserData = function() {
CalorieCal.delete().then(function() {
- $ionicPopup.alert({template: 'User data erased.'});
+ $ionicPopup.alert({template: $translate.instant('general-settings.user-data-erased')});
});
}
$scope.parseState = function(state) {
@@ -456,13 +459,13 @@ angular.module('emission.main.control',['emission.services',
}
var handleNoConsent = function(resultDoc) {
- $ionicPopup.confirm({template: "Consent for data collection not found, consent now?"})
+ $ionicPopup.confirm({template: $translate.instant('general-settings.consent-not-found')})
.then(function(res){
if (res) {
$state.go("root.reconsent");
} else {
$ionicPopup.alert({
- template: "OK! Note that you won't get any personalized stats until you do!"});
+ template: $translate.instant('general-settings.no-consent-message')});
}
});
}
@@ -470,13 +473,13 @@ angular.module('emission.main.control',['emission.services',
var handleConsent = function(resultDoc) {
$scope.consentDoc = resultDoc;
$ionicPopup.confirm({
- template: 'Consented to protocol {{consentDoc.protocol_id}}, {{consentDoc.approval_date}}',
+ template: $translate.instant('general-settings.consented-to',{protocol_id: $scope.consentDoc.protocol_id,approval_date: $scope.consentDoc.approval_date}),
scope: $scope,
- title: "Consent found!",
+ title: $translate.instant('general-settings.consent-found'),
buttons: [
// {text: "View",
// type: 'button-calm'},
- {text: "OK",
+ {text: ""+ $translate.instant('general-settings.consented-ok') +"",
type: 'button-positive'} ]
}).finally(function(res) {
$scope.consentDoc = null;
@@ -496,9 +499,9 @@ angular.module('emission.main.control',['emission.services',
}
var prepopulateMessage = {
- message: 'Join me in making transportation greener and healthier \nDownload the emission app:', // not supported on some apps (Facebook, Instagram)
- subject: 'Emission - UC Berkeley Research Project', // fi. for email
- url: 'https://bic2cal.eecs.berkeley.edu/#download'
+ message: $translate.instant('general-settings.share-message'), // not supported on some apps (Facebook, Instagram)
+ subject: $translate.instant('general-settings.share-subject'), // fi. for email
+ url: $translate.instant('general-settings.share-url')
}
$scope.share = function() {
diff --git a/www/js/diary/current.js b/www/js/diary/current.js
index 3d87c4a50..996f3a453 100644
--- a/www/js/diary/current.js
+++ b/www/js/diary/current.js
@@ -8,7 +8,7 @@
'emission.plugin.logger'])
.controller('CurrMapCtrl', function($scope, Config, $state, $timeout, $ionicActionSheet,leafletData,
- Logger, $window, PostTripManualMarker, CommHelper, $http, KVStore, $ionicPlatform) {
+ Logger, $window, PostTripManualMarker, CommHelper, $http, KVStore, $ionicPlatform, $translate) {
console.log("controller CurrMapCtrl called from current.js");
var _map;
@@ -46,9 +46,9 @@
hideLimitLabels: true,
translate: function(value) {
if (value === 1) {
- return "Yesterday";
+ return $translate.instant('diary.current-yesterday');
} else if (value === 7) {
- return "Week ago";
+ return $translate.instant('diary.current-weekagos');
}
return "";
}
@@ -84,15 +84,9 @@
sel_region: null
};
- var startTimeFn = function(ts) {
- var date = new Date(ts*1000);
- var hours = date.getHours();
- var minutes = date.getMinutes();
- var amOrPm = hours < 12 ? 'AM' : 'PM';
- hours = hours % 12;
- hours = hours ? hours : 12;
- minutes = minutes < 10 ? '0'+ minutes : minutes;
- return hours + ':' + minutes + ' ' + amOrPm;
+ var startTimeFn = function (ts) {
+ var date = new Date(ts * 1000);
+ return moment(date).format("LT");
};
var getSpeed = function(curr_lglat, last_lglat, curr_ts, last_ts) {
diff --git a/www/js/diary/detail.js b/www/js/diary/detail.js
index afa67bc64..b887f9e7a 100644
--- a/www/js/diary/detail.js
+++ b/www/js/diary/detail.js
@@ -7,7 +7,7 @@ angular.module('emission.main.diary.detail',['ui-leaflet', 'ng-walkthrough',
.controller("DiaryDetailCtrl", function($scope, $rootScope, $window, $stateParams, $ionicActionSheet,
leafletData, leafletMapEvents, nzTour, KVStore,
Logger, Timeline, DiaryHelper, Config,
- CommHelper, PostTripManualMarker) {
+ CommHelper, PostTripManualMarker, $translate) {
console.log("controller DiaryDetailCtrl called with params = "+
JSON.stringify($stateParams));
@@ -85,7 +85,7 @@ angular.module('emission.main.diary.detail',['ui-leaflet', 'ng-walkthrough',
}
var dataset = {
values: data,
- key: 'Speed',
+ key: $translate.instant('details.speed'),
color: '#7777ff',
}
var chart = nv.models.lineChart()
@@ -97,10 +97,10 @@ angular.module('emission.main.diary.detail',['ui-leaflet', 'ng-walkthrough',
.showXAxis(true); //Show the x-axis
chart.xAxis
.tickFormat(d3.format(".1f"))
- .axisLabel('Time (mins)');
+ .axisLabel($translate.instant('details.time') + ' (mins)');
chart.yAxis //Chart y-axis settings
- .axisLabel('Speed (m/s)')
+ .axisLabel($translate.instant('details.speed') + ' (m/s)')
.tickFormat(d3.format('.1f'));
d3.select('#chart svg') //Select the