From 14e8a67866ccd624070728ff6d55d6b362542ff3 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Thu, 25 May 2023 20:58:49 -0400 Subject: [PATCH] use i18next instead of angular translate We rely heavily on Angular for internationalization, both directly in the HTML and in the JS behind the scenes. However, it's specific to Angular and we can't use it in React components. i18next is compatible with React / React Native Web and we will be able to use it in both frameworks at the same time. The i18n JSON files themselves can keep the same exact structure. Only a few strings were changed (the 'equals X cookies, equals X ice cream', etc) - i18next has a different mechanism for pluralization and we don't have to use ICU for only 3 strings. --- package.serve.json | 4 +- www/i18n/en.json | 10 ++- www/js/app.js | 72 +++++++++++------ www/js/appstatus/permissioncheck.js | 48 ++++++------ www/js/config/dynamic_config.js | 22 +++--- www/js/control/emailService.js | 14 ++-- www/js/control/general-settings.js | 49 ++++++------ www/js/control/uploadService.js | 16 ++-- www/js/diary/infinite_scroll_detail.js | 8 +- www/js/diary/infinite_scroll_list.js | 12 +-- www/js/diary/infinite_scroll_trip_item.js | 5 +- www/js/diary/services.js | 10 +-- www/js/i18n-utils.js | 4 +- www/js/intro.js | 6 +- www/js/join/join-ctrl.js | 15 +++- www/js/main.js | 4 +- www/js/metrics.js | 77 +++++++++---------- www/js/services.js | 7 +- www/js/splash/customURL.js | 8 -- www/js/splash/notifScheduler.js | 4 +- www/js/splash/storedevicesettings.js | 4 +- www/js/survey/enketo/answer.js | 13 ++-- .../survey/enketo/enketo-add-note-button.js | 4 +- www/js/survey/enketo/enketo-demographics.js | 6 +- .../survey/enketo/infinite_scroll_filters.js | 6 +- www/js/survey/enketo/launch.js | 6 +- www/js/survey/enketo/service.js | 7 +- www/js/survey/input-matcher.js | 2 +- .../multilabel/infinite_scroll_filters.js | 8 +- .../multilabel/trip-confirm-services.js | 22 +++--- www/templates/appstatus/permissioncheck.html | 16 ++-- www/templates/caloriePopup.html | 12 +-- www/templates/control/app-status-modal.html | 6 +- www/templates/control/main-control.html | 58 +++++++------- www/templates/control/qrc.html | 6 +- .../diary/infinite_scroll_detail.html | 19 +++-- www/templates/diary/infinite_scroll_list.html | 10 +-- www/templates/diary/mode-popover.html | 2 +- www/templates/diary/purpose-popover.html | 2 +- .../diary/replaced_mode-popover.html | 2 +- www/templates/diary/trip_list_item.html | 8 +- .../diary/untracked_time_list_item.html | 5 +- www/templates/intro/consent.html | 6 +- www/templates/intro/saveTokenFile.html | 12 +-- www/templates/intro/sensor_explanation.html | 6 +- www/templates/join/about-app.html | 18 ++--- www/templates/join/request_join.html | 18 ++--- www/templates/main-metrics.html | 67 ++++++++-------- www/templates/main.html | 6 +- .../metrics/arrow-greater-lesser.html | 16 ++-- www/templates/metrics/metrics-control.html | 22 +++--- www/templates/recent/sensedData.html | 2 +- .../survey/enketo/demographics-button.html | 2 +- www/templates/survey/enketo/form-base.html | 12 +-- www/templates/survey/enketo/inline.html | 22 +++--- www/templates/survey/enketo/modal.html | 6 +- .../survey/enketo/summary-trip-button.html | 10 +-- .../survey/multilabel/multi-label-ui.html | 8 +- 58 files changed, 442 insertions(+), 410 deletions(-) diff --git a/package.serve.json b/package.serve.json index 9743376ef..ceb0f1f7a 100644 --- a/package.serve.json +++ b/package.serve.json @@ -47,13 +47,13 @@ "angular-sanitize": "1.6.7", "angular-simple-logger": "^0.1.7", "angular-translate": "^2.18.1", - "angular-translate-interpolation-messageformat": "^2.18.1", "angular-translate-loader-static-files": "^2.18.1", "angular-ui-router": "0.2.13", "animate.css": "^3.5.2", "bottleneck": "^2.19.5", "core-js": "^2.5.7", "fs-extra": "^9.0.1", + "i18next": "^22.5.0", "install": "^0.13.0", "ionic-datepicker": "1.2.1", "ionic-toast": "^0.4.1", @@ -63,8 +63,10 @@ "leaflet": "^0.7.7", "leaflet-plugins": "^3.0.0", "leaflet.awesome-markers": "^2.0.5", + "messageformat": "^2.3.0", "moment": "^2.29.4", "moment-timezone": "^0.5.43", + "ng-i18next": "^1.0.7", "npm": "^9.6.3", "nvd3": "^1.8.6", "prop-types": "^15.8.1", diff --git a/www/i18n/en.json b/www/i18n/en.json index b368f61c5..79eb3b277 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -243,7 +243,6 @@ "average": "Average for group:", "avoided": "CO₂ avoided (vs. all 'taxi'):", "label-to-squish": "Label trips to collapse the range into a single number", - "equals-phone-charges": "Saved {charges, plural, =0{at least 0 smartphone charges} one {at least 1 smartphone charge} other {at least # smartphone charges}} vs. all 'taxi'", "lastweek": "My last week value:", "us-2030-goal": "US 2030 Goal Estimate:", "us-2050-goal": "US 2050 Goal Estimate:", @@ -251,9 +250,12 @@ "calibrate": "Calibrate", "no-summary-data": "No summary data", "mean-speed": "My Average Speed", - "equals-cookies": "Equals {cookies, plural, =0{at least 0 homemade chocolate chip cookies} one {at least 1 homemade chocolate chip cookie} other {at least # homemade chocolate chip cookies}}", - "equals-icecream": "Equals {icecream, plural, =0{at least 0 half cups vanilla ice cream} one {at least 1 half cup vanilla ice cream} other {at least # half cups vanilla ice cream}}", - "equals-bananas": "Equals {bananas, plural, =0{at least 0 bananas} one {at least 1 banana} other {at least # bananas}}" + "equals-cookies_one": "Equals at least {{count}} homemade chocolate chip cookie", + "equals-cookies_other": "Equals at least {{count}} homemade chocolate chip cookies", + "equals-icecream_one": "Equals at least {{count}} half cup vanilla ice cream", + "equals-icecream_other": "Equals at least {{count}} half cups vanilla ice cream", + "equals-bananas_one": "Equals at least {{count}} banana", + "equals-bananas_other": "Equals at least {{count}} bananas" }, "main-diary" : "Diary", diff --git a/www/js/app.js b/www/js/app.js index 4cf2f6003..b9d305bfb 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -18,6 +18,8 @@ import 'angular-translate-loader-static-files'; import 'moment'; import 'moment-timezone'; +import i18next from 'i18next'; + import 'ionic-toast'; import 'ionic-datepicker'; import 'angular-simple-logger'; @@ -25,7 +27,52 @@ import 'angular-simple-logger'; import '../manual_lib/ionic/js/ionic.js'; import '../manual_lib/ionic/js/ionic-angular.js'; -angular.module('emission', ['ionic', + +import en from '../i18n/en.json'; +import es from '../../locales/es/i18n/es.json'; +import fr from '../../locales/fr/i18n/fr.json'; +import it from '../../locales/it/i18n/it.json'; +const langs = { en, es, fr, it }; + +let resources = {}; +for (const [lang, json] of Object.entries(langs)) { + resources[lang] = { translation: json } +} + +const locales = !navigator?.length ? [navigator.language] : navigator.languages; + +let detectedLang; +locales.forEach(locale => { + const lang = locale.trim().split(/-|_/)[0]; + if (Object.keys(langs).includes(lang)) { + detectedLang = lang; + } +}); +console.debug(`Detected language: ${detectedLang}`); +console.debug('Resources:', resources); +i18next.init({ + debug: true, + resources, + lng: detectedLang, + fallbackLng: 'en' +}); + +console.debug('currlang:', i18next.resolvedLanguage); + +i18next.changeLanguage(detectedLang, (err, t) => { + if (err) { + console.error(err); + } else { + console.debug('i18next language changed to:', i18next.resolvedLanguage); + console.debug('t diary.draft:', t('diary.draft')); + console.debug('i18next.t diary.draft:', i18next.t('diary.draft')); + } +}); + +window.i18next = i18next; +import 'ng-i18next'; + +angular.module('emission', ['ionic', 'jm.i18next', 'emission.controllers','emission.services', 'emission.plugin.logger', 'emission.splash.customURLScheme', 'emission.splash.referral', 'emission.services.email', @@ -75,7 +122,7 @@ angular.module('emission', ['ionic', console.log("Ending run"); }) -.config(function($stateProvider, $urlRouterProvider, $translateProvider, $compileProvider) { +.config(function($stateProvider, $urlRouterProvider, $compileProvider) { console.log("Starting config"); // alert("config"); @@ -119,27 +166,6 @@ 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') - // .useSanitizeValueStrategy('escape'); - - - // Define where we can find the .json and the fallback language - $translateProvider - .fallbackLanguage('en') - .registerAvailableLanguageKeys(['en', 'fr', 'it', 'es'], { - 'en_*': 'en', - 'fr_*': 'fr', - 'it_*': 'it', - 'es_*': 'es', - '*': 'en' - }) - .determinePreferredLanguage() - .useStaticFilesLoader({ - prefix: 'i18n/', - suffix: '.json' - }); console.log("Ending config"); }); diff --git a/www/js/appstatus/permissioncheck.js b/www/js/appstatus/permissioncheck.js index c7fe88271..0e1c81a64 100644 --- a/www/js/appstatus/permissioncheck.js +++ b/www/js/appstatus/permissioncheck.js @@ -16,7 +16,7 @@ angular.module('emission.appstatus.permissioncheck', }; }). controller("PermissionCheckControl", function($scope, $element, $attrs, - $ionicPlatform, $ionicPopup, $window, $translate) { + $ionicPlatform, $ionicPopup, $window) { console.log("PermissionCheckControl initialized with status "+$scope.overallstatus); $scope.setupLocChecks = function(platform, version) { @@ -197,15 +197,15 @@ controller("PermissionCheckControl", function($scope, $element, $attrs, console.log("description tags are "+androidSettingsDescTag+" "+androidPermDescTag); // location settings let locSettingsCheck = { - name: $translate.instant("intro.appstatus.locsettings.name"), - desc: $translate.instant(androidSettingsDescTag), + name: i18next.t("intro.appstatus.locsettings.name"), + desc: i18next.t(androidSettingsDescTag), statusState: false, fix: fixSettings, refresh: checkSettings } let locPermissionsCheck = { - name: $translate.instant("intro.appstatus.locperms.name"), - desc: $translate.instant(androidPermDescTag), + name: i18next.t("intro.appstatus.locperms.name"), + desc: i18next.t(androidPermDescTag), statusState: false, fix: fixPerms, refresh: checkPerms @@ -243,15 +243,15 @@ controller("PermissionCheckControl", function($scope, $element, $attrs, console.log("description tags are "+iOSSettingsDescTag+" "+iOSPermDescTag); // location settings let locSettingsCheck = { - name: $translate.instant("intro.appstatus.locsettings.name"), - desc: $translate.instant(iOSSettingsDescTag), + name: i18next.t("intro.appstatus.locsettings.name"), + desc: i18next.t(iOSSettingsDescTag), statusState: false, fix: fixSettings, refresh: checkSettings } let locPermissionsCheck = { - name: $translate.instant("intro.appstatus.locperms.name"), - desc: $translate.instant(iOSPermDescTag), + name: i18next.t("intro.appstatus.locperms.name"), + desc: i18next.t(iOSPermDescTag), statusState: false, fix: fixPerms, refresh: checkPerms @@ -275,12 +275,12 @@ controller("PermissionCheckControl", function($scope, $element, $attrs, }; let fitnessPermissionsCheck = { - name: $translate.instant("intro.appstatus.fitnessperms.name"), - desc: $translate.instant("intro.appstatus.fitnessperms.description.android"), + name: i18next.t("intro.appstatus.fitnessperms.name"), + desc: i18next.t("intro.appstatus.fitnessperms.description.android"), fix: fixPerms, refresh: checkPerms } - $scope.overallFitnessName = $translate.instant("intro.appstatus.overall-fitness-name-android"); + $scope.overallFitnessName = i18next.t("intro.appstatus.overall-fitness-name-android"); $scope.fitnessChecks = [fitnessPermissionsCheck]; refreshChecks($scope.fitnessChecks, $scope.recomputeFitnessStatus); } @@ -300,12 +300,12 @@ controller("PermissionCheckControl", function($scope, $element, $attrs, }; let fitnessPermissionsCheck = { - name: $translate.instant("intro.appstatus.fitnessperms.name"), - desc: $translate.instant("intro.appstatus.fitnessperms.description.ios"), + name: i18next.t("intro.appstatus.fitnessperms.name"), + desc: i18next.t("intro.appstatus.fitnessperms.description.ios"), fix: fixPerms, refresh: checkPerms } - $scope.overallFitnessName = $translate.instant("intro.appstatus.overall-fitness-name-ios"); + $scope.overallFitnessName = i18next.t("intro.appstatus.overall-fitness-name-ios"); $scope.fitnessChecks = [fitnessPermissionsCheck]; refreshChecks($scope.fitnessChecks, $scope.recomputeFitnessStatus); } @@ -322,8 +322,8 @@ controller("PermissionCheckControl", function($scope, $element, $attrs, $scope.recomputeNotificationStatus, false); }; let appAndChannelNotificationsCheck = { - name: $translate.instant("intro.appstatus.notificationperms.app-enabled-name"), - desc: $translate.instant("intro.appstatus.notificationperms.description.android-enable"), + name: i18next.t("intro.appstatus.notificationperms.app-enabled-name"), + desc: i18next.t("intro.appstatus.notificationperms.description.android-enable"), fix: fixPerms, refresh: checkPerms } @@ -357,14 +357,14 @@ controller("PermissionCheckControl", function($scope, $element, $attrs, androidUnusedDescTag= "intro.appstatus.unusedapprestrict.description.android-disable-lt-12"; } let unusedAppsUnrestrictedCheck = { - name: $translate.instant("intro.appstatus.unusedapprestrict.name"), - desc: $translate.instant(androidUnusedDescTag), + name: i18next.t("intro.appstatus.unusedapprestrict.name"), + desc: i18next.t(androidUnusedDescTag), fix: fixPerms, refresh: checkPerms } let ignoreBatteryOptCheck = { - name: $translate.instant("intro.appstatus.ignorebatteryopt.name"), - desc: $translate.instant("intro.appstatus.ignorebatteryopt.description.android-disable"), + name: i18next.t("intro.appstatus.ignorebatteryopt.name"), + desc: i18next.t("intro.appstatus.ignorebatteryopt.description.android-disable"), fix: fixBatteryOpt, refresh: checkBatteryOpt } @@ -375,16 +375,16 @@ controller("PermissionCheckControl", function($scope, $element, $attrs, $scope.setupPermissionText = function() { if($scope.platform.toLowerCase() == "ios") { if($scope.osver < 13) { - $scope.locationPermExplanation = $translate.instant("intro.permissions.locationPermExplanation-ios-lt-13"); + $scope.locationPermExplanation = i18next.t("intro.permissions.locationPermExplanation-ios-lt-13"); } else { - $scope.locationPermExplanation = $translate.instant("intro.permissions.locationPermExplanation-ios-gte-13"); + $scope.locationPermExplanation = i18next.t("intro.permissions.locationPermExplanation-ios-gte-13"); } } $scope.backgroundRestricted = false; if($window.device.manufacturer.toLowerCase() == "samsung") { $scope.backgroundRestricted = true; - $scope.allowBackgroundInstructions = $translate.instant("intro.allow_background.samsung"); + $scope.allowBackgroundInstructions = i18next.t("intro.allow_background.samsung"); } console.log("Explanation = "+$scope.locationPermExplanation); diff --git a/www/js/config/dynamic_config.js b/www/js/config/dynamic_config.js index ba470f9bb..e8bb63895 100644 --- a/www/js/config/dynamic_config.js +++ b/www/js/config/dynamic_config.js @@ -4,7 +4,7 @@ import angular from 'angular'; angular.module('emission.config.dynamic', ['emission.plugin.logger']) .factory('DynamicConfig', function($http, $ionicPlatform, - $window, $state, $rootScope, $timeout, Logger, $translate) { + $window, $state, $rootScope, $timeout, Logger) { // also used in the startprefs class // but without importing this const CONFIG_PHONE_UI="config/app_ui_config"; @@ -73,7 +73,7 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger']) return savedConfig; } }) - .catch((err) => Logger.displayError($translate.instant('config.unable-read-saved-config'), err)); + .catch((err) => Logger.displayError(i18next.t('config.unable-read-saved-config'), err)); } /** @@ -107,7 +107,7 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger']) if (thenGoToIntro) $state.go("root.intro") }) .then(() => true) - .catch((storeError) => Logger.displayError($translate.instant('config.unable-to-store-config'), storeError)); + .catch((storeError) => Logger.displayError(i18next.t('config.unable-to-store-config'), storeError)); }); } @@ -181,10 +181,10 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger']) const tokenParts = token.split("_"); if (tokenParts.length < 3) { // all tokens must have at least nrelop_[study name]_... - throw new Error($translate.instant('config.not-enough-parts-old-style', {"token": token})); + throw new Error(i18next.t('config.not-enough-parts-old-style', {"token": token})); } if (tokenParts[0] != "nrelop") { - throw new Error($translate.instant('config.no-nrelop-start', {token: token})); + throw new Error(i18next.t('config.no-nrelop-start', {token: token})); } return tokenParts[1]; } @@ -194,12 +194,12 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger']) // new style study, expects token with sub-group const tokenParts = token.split("_"); if (tokenParts.length <= 3) { // no subpart defined - throw new Error($translate.instant('config.not-enough-parts', {token: token})); + throw new Error(i18next.t('config.not-enough-parts', {token: token})); } if (config.opcode.subgroups) { if (config.opcode.subgroups.indexOf(tokenParts[2]) == -1) { // subpart not in config list - throw new Error($translate.instant('config.invalid-subgroup', {token: token, subgroup: tokenParts[2], config_subgroups: config.opcode.subgroups})); + throw new Error(i18next.t('config.invalid-subgroup', {token: token, subgroup: tokenParts[2], config_subgroups: config.opcode.subgroups})); } else { console.log("subgroup "+tokenParts[2]+" found in list "+config.opcode.subgroups); return tokenParts[2]; @@ -207,7 +207,7 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger']) } else { if (tokenParts[2] != "default") { // subpart not in config list - throw new Error($translate.instant('config.invalid-subgroup', {token: token})); + throw new Error(i18next.t('config.invalid-subgroup', {token: token})); } else { console.log("no subgroups in config, 'default' subgroup found in token "); return tokenParts[2]; @@ -246,10 +246,10 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger']) // on successful download, cache the token in the rootScope .then((wasUpdated) => {$rootScope.scannedToken = dc.scannedToken}) .catch((fetchErr) => { - Logger.displayError($translate.instant('config.unable-download-config'), fetchErr); + Logger.displayError(i18next.t('config.unable-download-config'), fetchErr); }); } catch (error) { - Logger.displayError($translate.instant('config.invalid-opcode-format'), error); + Logger.displayError(i18next.t('config.invalid-opcode-format'), error); return Promise.reject(error); } }); @@ -277,7 +277,7 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger']) $rootScope.$apply(() => dc.saveAndNotifyConfigReady(existingConfig)); } }).catch((err) => { - Logger.displayError($translate('config.error-loading-config-app-start'), err) + Logger.displayError('Error loading config on app start', err) }); }; $ionicPlatform.ready().then(function() { diff --git a/www/js/control/emailService.js b/www/js/control/emailService.js index ade76dc6f..0374adf5a 100644 --- a/www/js/control/emailService.js +++ b/www/js/control/emailService.js @@ -4,7 +4,7 @@ import angular from 'angular'; angular.module('emission.services.email', ['emission.plugin.logger']) - .service('EmailHelper', function ($window, $translate, $http, Logger) { + .service('EmailHelper', function ($window, $http, Logger) { const getEmailConfig = function () { return new Promise(function (resolve, reject) { @@ -55,7 +55,7 @@ angular.module('emission.services.email', ['emission.plugin.logger']) // generate a reasonably relevant error message if (ionic.Platform.isIOS() && !hasAct) { - alert($translate.instant('email-service.email-account-not-configured')); + alert(i18next.t('email-service.email-account-not-configured')); return; } @@ -63,7 +63,7 @@ angular.module('emission.services.email', ['emission.plugin.logger']) parentDir = "app://databases"; } if (ionic.Platform.isIOS()) { - alert($translate.instant('email-service.email-account-mail-app')); + alert(i18next.t('email-service.email-account-mail-app')); parentDir = cordova.file.dataDirectory + "../LocalDatabase"; } @@ -77,19 +77,19 @@ angular.module('emission.services.email', ['emission.plugin.logger']) window.Logger.log(window.Logger.LEVEL_INFO, "Going to export logs to "+parentDir); */ - alert($translate.instant('email-service.going-to-email', { parentDir: parentDir })); + alert(i18next.t('email-service.going-to-email', { parentDir: parentDir })); var email = { to: address, attachments: [ parentDir ], - subject: $translate.instant('email-service.email-log.subject-logs'), - body: $translate.instant('email-service.email-log.body-please-fill-in-what-is-wrong') + subject: i18next.t('email-service.email-log.subject-logs'), + body: i18next.t('email-service.email-log.body-please-fill-in-what-is-wrong') } $window.cordova.plugins.email.open(email, function () { Logger.log("email app closed while sending, "+JSON.stringify(email)+" not sure if we should do anything"); - // alert($translate.instant('email-service.no-email-address-configured') + err); + // alert(i18next.t('email-service.no-email-address-configured') + err); return; }); }); diff --git a/www/js/control/general-settings.js b/www/js/control/general-settings.js index b58f74484..6bdc13c1c 100644 --- a/www/js/control/general-settings.js +++ b/www/js/control/general-settings.js @@ -27,18 +27,17 @@ angular.module('emission.main.control',['emission.services', ControlCollectionHelper, ControlSyncHelper, CarbonDatasetHelper, NotificationScheduler, LocalNotify, i18nUtils, - CalorieCal, ClientStats, CommHelper, Logger, DynamicConfig, - $translate) { + CalorieCal, ClientStats, CommHelper, Logger, DynamicConfig) { console.log("controller ControlCtrl called without params"); var datepickerObject = { - todayLabel: $translate.instant('list-datepicker-today'), //Optional - closeLabel: $translate.instant('list-datepicker-close'), //Optional - setLabel: $translate.instant('list-datepicker-set'), //Optional + todayLabel: i18next.t('list-datepicker-today'), //Optional + closeLabel: i18next.t('list-datepicker-close'), //Optional + setLabel: i18next.t('list-datepicker-set'), //Optional monthsList: moment.monthsShort(), weeksList: moment.weekdaysMin(), - titleLabel: $translate.instant('general-settings.choose-date'), + titleLabel: i18next.t('general-settings.choose-date'), setButtonType : 'button-positive', //Optional todayButtonType : 'button-stable', //Optional closeButtonType : 'button-stable', //Optional @@ -71,7 +70,7 @@ angular.module('emission.main.control',['emission.services', ionicDatePicker.openDatePicker(datepickerObject); }; - $scope.carbonDatasetString = $translate.instant('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); + $scope.carbonDatasetString = i18next.t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); $scope.uploadLog = function () { UploadHelper.uploadFile("loggerDB") @@ -155,7 +154,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? $translate.instant('gender-male') : $translate.instant('gender-female') + gender: userDataFromStorage.gender == 1? i18next.t('gender-male') : i18next.t('gender-female') } for (var i in temp) { $scope.userData.push({key: i, value: temp[i]}); @@ -270,16 +269,16 @@ angular.module('emission.main.control',['emission.services', } $scope.nukeUserCache = function() { - var nukeChoiceActions = [{text: $translate.instant('general-settings.nuke-ui-state-only'), + var nukeChoiceActions = [{text: i18next.t('general-settings.nuke-ui-state-only'), action: KVStore.clearOnlyLocal}, - {text: $translate.instant('general-settings.nuke-native-cache-only'), + {text: i18next.t('general-settings.nuke-native-cache-only'), action: KVStore.clearOnlyNative}, - {text: $translate.instant('general-settings.nuke-everything'), + {text: i18next.t('general-settings.nuke-everything'), action: KVStore.clearAll}]; $ionicActionSheet.show({ - titleText: $translate.instant('general-settings.clear-data'), - cancelText: $translate.instant('general-settings.cancel'), + titleText: i18next.t('general-settings.clear-data'), + cancelText: i18next.t('general-settings.cancel'), buttons: nukeChoiceActions, buttonClicked: function(index, button) { button.action(); @@ -520,7 +519,7 @@ angular.module('emission.main.control',['emission.services', } $scope.eraseUserData = function() { CalorieCal.delete().then(function() { - $ionicPopup.alert({template: $translate.instant('general-settings.user-data-erased')}); + $ionicPopup.alert({template: i18next.t('general-settings.user-data-erased')}); }); } $scope.parseState = function(state) { @@ -535,12 +534,12 @@ angular.module('emission.main.control',['emission.services', $scope.changeCarbonDataset = function() { $ionicActionSheet.show({ buttons: CarbonDatasetHelper.getCarbonDatasetOptions(), - titleText: $translate.instant('general-settings.choose-dataset'), - cancelText: $translate.instant('general-settings.cancel'), + titleText: i18next.t('general-settings.choose-dataset'), + cancelText: i18next.t('general-settings.cancel'), buttonClicked: function(index, button) { console.log("changeCarbonDataset(): chose locale " + button.value); CarbonDatasetHelper.saveCurrentCarbonDatasetLocale(button.value); - $scope.carbonDatasetString = $translate.instant('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); + $scope.carbonDatasetString = i18next.t('general-settings.carbon-dataset') + ": " + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); return true; } }); @@ -572,13 +571,13 @@ angular.module('emission.main.control',['emission.services', } var handleNoConsent = function(resultDoc) { - $ionicPopup.confirm({template: $translate.instant('general-settings.consent-not-found')}) + $ionicPopup.confirm({template: i18next.t('general-settings.consent-not-found')}) .then(function(res){ if (res) { $state.go("root.reconsent"); } else { $ionicPopup.alert({ - template: $translate.instant('general-settings.no-consent-message')}); + template: i18next.t('general-settings.no-consent-message')}); } }); } @@ -586,13 +585,13 @@ angular.module('emission.main.control',['emission.services', var handleConsent = function(resultDoc) { $scope.consentDoc = resultDoc; $ionicPopup.confirm({ - template: $translate.instant('general-settings.consented-to',{protocol_id: $scope.consentDoc.protocol_id,approval_date: $scope.consentDoc.approval_date}), + template: i18next.t('general-settings.consented-to',{protocol_id: $scope.consentDoc.protocol_id,approval_date: $scope.consentDoc.approval_date}), scope: $scope, - title: $translate.instant('general-settings.consent-found'), + title: i18next.t('general-settings.consent-found'), buttons: [ // {text: "View", // type: 'button-calm'}, - {text: ""+ $translate.instant('general-settings.consented-ok') +"", + {text: ""+ i18next.t('general-settings.consented-ok') +"", type: 'button-positive'} ] }).finally(function(res) { $scope.consentDoc = null; @@ -612,9 +611,9 @@ angular.module('emission.main.control',['emission.services', } var prepopulateMessage = { - 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') + message: i18next.t('general-settings.share-message'), // not supported on some apps (Facebook, Instagram) + subject: i18next.t('general-settings.share-subject'), // fi. for email + url: i18next.t('general-settings.share-url') } $scope.share = function() { diff --git a/www/js/control/uploadService.js b/www/js/control/uploadService.js index 5d8b0b204..6f95503c1 100644 --- a/www/js/control/uploadService.js +++ b/www/js/control/uploadService.js @@ -4,7 +4,7 @@ import angular from 'angular'; angular.module('emission.services.upload', ['emission.plugin.logger']) - .service('UploadHelper', function ($window, $translate, $http, $rootScope, $ionicPopup, Logger) { + .service('UploadHelper', function ($window, $http, $rootScope, $ionicPopup, Logger) { const getUploadConfig = function () { return new Promise(function (resolve, reject) { Logger.log(Logger.LEVEL_INFO, "About to get email config"); @@ -93,13 +93,13 @@ angular.module('emission.services.upload', ['emission.plugin.logger']) const newScope = $rootScope.$new(); newScope.data = {}; - newScope.fromDirText = $translate.instant('upload-service.upload-from-dir', {parentDir: parentDir}); - newScope.toServerText = $translate.instant('upload-service.upload-to-server', {serverURL: uploadConfig}); + newScope.fromDirText = i18next.t('upload-service.upload-from-dir', {parentDir: parentDir}); + newScope.toServerText = i18next.t('upload-service.upload-to-server', {serverURL: uploadConfig}); var didCancel = true; const detailsPopup = $ionicPopup.show({ - title: $translate.instant("upload-service.upload-database", { db: database }), + title: i18next.t("upload-service.upload-database", { db: database }), template: newScope.toServerText + '', @@ -142,9 +142,9 @@ angular.module('emission.services.upload', ['emission.plugin.logger']) } uploadConfig.forEach((url) => { const progressPopup = $ionicPopup.show({ - title: $translate.instant("upload-service.upload-database", + title: i18next.t("upload-service.upload-database", {db: database}), - template: $translate.instant("upload-service.upload-progress", + template: i18next.t("upload-service.upload-progress", {filesizemb: binString.byteLength / (1000 * 1000), serverURL: uploadConfig}) + '
', @@ -157,8 +157,8 @@ angular.module('emission.services.upload', ['emission.plugin.logger']) console.log(response); progressPopup.close(); const successPopup = $ionicPopup.alert({ - title: $translate.instant("upload-service.upload-success"), - template: $translate.instant("upload-service.upload-details", + title: i18next.t("upload-service.upload-success"), + template: i18next.t("upload-service.upload-details", {filesizemb: binString.byteLength / (1000 * 1000), serverURL: uploadConfig}) }); diff --git a/www/js/diary/infinite_scroll_detail.js b/www/js/diary/infinite_scroll_detail.js index aa4ce39e0..8aa01fbbf 100644 --- a/www/js/diary/infinite_scroll_detail.js +++ b/www/js/diary/infinite_scroll_detail.js @@ -14,7 +14,7 @@ angular.module('emission.main.diary.infscrolldetail', [ .controller("InfiniteDiaryDetailCtrl", function($scope, $rootScope, $injector, $window, $ionicPlatform, $state, $stateParams, ClientStats, $ionicActionSheet, KVStore, Logger, Timeline, DiaryHelper, SurveyOptions, Config, ImperialConfig, - DynamicConfig, CommHelper, $translate) { + DynamicConfig, CommHelper) { console.log("controller InfiniteDiaryDetailCtrl called with params = "+ JSON.stringify($stateParams)); @@ -41,7 +41,7 @@ angular.module('emission.main.diary.infscrolldetail', [ }); var dataset = { values: data, - key: $translate.instant('details.speed'), + key: i18next.t('details.speed'), color: '#7777ff', } var chart = nv.models.lineChart() @@ -53,10 +53,10 @@ angular.module('emission.main.diary.infscrolldetail', [ .showXAxis(true); //Show the x-axis chart.xAxis .tickFormat(d3.format(".1f")) - .axisLabel($translate.instant('details.time') + ' (mins)'); + .axisLabel(i18next.t('details.time') + ' (mins)'); chart.yAxis //Chart y-axis settings - .axisLabel($translate.instant('details.speed') + ' (m/s)') + .axisLabel(i18next.t('details.speed') + ' (m/s)') .tickFormat(d3.format('.1f')); d3.selectAll('#chart svg') //Select the element you want to render the chart in. diff --git a/www/js/diary/infinite_scroll_list.js b/www/js/diary/infinite_scroll_list.js index 8bb1e4539..8dacf1f52 100644 --- a/www/js/diary/infinite_scroll_list.js +++ b/www/js/diary/infinite_scroll_list.js @@ -39,7 +39,7 @@ angular.module('emission.main.diary.infscrolllist',[ Config, ImperialConfig, DynamicConfig, KVStore, Logger, UnifiedDataLoader, InputMatcher, - $ionicModal, $translate) { + $ionicModal) { // TODO: load only a subset of entries instead of everything @@ -88,11 +88,11 @@ angular.module('emission.main.diary.infscrolllist',[ $scope.$broadcast("recomputeAppStatus", (status) => { if (!status) { $ionicPopup.show({ - title: $translate.instant('control.incorrect-app-status'), - template: $translate.instant('control.fix-app-status'), + title: i18next.t('control.incorrect-app-status'), + template: i18next.t('control.fix-app-status'), scope: $scope, buttons: [{ - text: $translate.instant('control.fix'), + text: i18next.t('control.fix'), type: 'button-assertive', onTap: function(e) { $state.go('root.main.control', {launchAppStatusModal: 1}); @@ -247,7 +247,7 @@ angular.module('emission.main.diary.infscrolllist',[ } Logger.log("Turning on the ionic loading overlay in setupInfScroll"); $ionicLoading.show({ - template: $translate.instant('service.reading-server') + template: i18next.t('service.reading-server') }); Logger.log("Requesting trips from server from "+moment(startTs*1000).format("YYYY-MM-DD HH:mm:ss")+" to "+moment(endTs*1000).format("YYYY-MM-DD HH:mm:ss")); @@ -307,7 +307,7 @@ angular.module('emission.main.diary.infscrolllist',[ $scope.data.listEntries = []; Logger.log("Turning on the ionic loading overlay in setupInfScroll"); $ionicLoading.show({ - template: $translate.instant('service.reading-server') + template: i18next.t('service.reading-server') }); Timeline.getUnprocessedLabels($scope.labelPopulateFactory, $scope.enbs).then(([pipelineRange, manualResultMap, enbsResultMap]) => { if (pipelineRange.end_ts) { diff --git a/www/js/diary/infinite_scroll_trip_item.js b/www/js/diary/infinite_scroll_trip_item.js index 5bd312650..1fc4260f2 100644 --- a/www/js/diary/infinite_scroll_trip_item.js +++ b/www/js/diary/infinite_scroll_trip_item.js @@ -36,8 +36,7 @@ angular.module('emission.main.diary.infscrolltripitem', }) .controller("TripItemCtrl", function($scope, $injector, $ionicPlatform, $ionicPopup, - $state, $translate, - Timeline, DiaryHelper, SurveyOptions, + $state, Timeline, DiaryHelper, SurveyOptions, Config, DynamicConfig, $ionicScrollDelegate ){ console.log("Trip Item Controller called"); @@ -68,7 +67,7 @@ angular.module('emission.main.diary.infscrolltripitem', $scope.explainDraft = function($event) { $event.stopPropagation(); $ionicPopup.alert({ - template: $translate.instant('list-explainDraft-alert') + template: i18next.t('list-explainDraft-alert') }); // don't want to go to the detail screen } diff --git a/www/js/diary/services.js b/www/js/diary/services.js index f7a6418ea..19efb6c9d 100644 --- a/www/js/diary/services.js +++ b/www/js/diary/services.js @@ -18,7 +18,7 @@ import angular from 'angular'; angular.module('emission.main.diary.services', ['emission.plugin.logger', 'emission.services']) -.factory('DiaryHelper', function($http, $translate){ +.factory('DiaryHelper', function($http){ var dh = {}; dh.isMultiDay = function(beginTs, endTs) { @@ -53,7 +53,7 @@ angular.module('emission.main.diary.services', ['emission.plugin.logger', if (typeof t == 'number') t = t*1000; // if timestamp, convert to ms if (!t._isAMomentObject) t = moment(t); const opts = { weekday: 'short', month: 'short', day: 'numeric' }; - return Intl.DateTimeFormat($translate.use(), opts) + return Intl.DateTimeFormat(i18next.resolvedLanguage, opts) .format(new Date(t.format('LLL'))); } @@ -171,7 +171,7 @@ angular.module('emission.main.diary.services', ['emission.plugin.logger', return dh; }) .factory('Timeline', function(CommHelper, SurveyOptions, DynamicConfig, $http, $ionicLoading, $ionicPlatform, $window, - $rootScope, UnifiedDataLoader, Logger, $injector, $translate) { + $rootScope, UnifiedDataLoader, Logger, $injector) { var timeline = {}; // corresponds to the old $scope.data. Contains all state for the current // day, including the indication of the current day @@ -233,7 +233,7 @@ angular.module('emission.main.diary.services', ['emission.plugin.logger', timeline.readAllCompositeTrips = function(startTs, endTs) { $ionicLoading.show({ - template: $translate.instant('service.reading-server') + template: i18next.t('service.reading-server') }); const readPromises = [ CommHelper.getRawEntries(["analysis/composite_trip"], @@ -547,7 +547,7 @@ angular.module('emission.main.diary.services', ['emission.plugin.logger', timeline.readUnprocessedTrips = function(startTs, endTs, processedTripList) { $ionicLoading.show({ - template: $translate.instant('service.reading-unprocessed-data') + template: i18next.t('service.reading-unprocessed-data') }); var tq = {key: "write_ts", diff --git a/www/js/i18n-utils.js b/www/js/i18n-utils.js index d5c2c3ab8..45cca7043 100644 --- a/www/js/i18n-utils.js +++ b/www/js/i18n-utils.js @@ -3,7 +3,7 @@ import angular from 'angular'; angular.module('emission.i18n.utils', []) -.factory("i18nUtils", function($http, $translate, Logger) { +.factory("i18nUtils", function($http, Logger) { var iu = {}; // copy-pasted from ngCordova, and updated to promises iu.checkFile = function(fn) { @@ -19,7 +19,7 @@ angular.module('emission.i18n.utils', []) // The language comes in between the first and second part // the default path should end with a "/" iu.geti18nFileName = function (defaultPath, fpFirstPart, fpSecondPart) { - const lang = $translate.use(); + const lang = i18next.resolvedLanguage; const i18nPath = "i18n/"; var defaultVal = defaultPath + fpFirstPart + fpSecondPart; if (lang != 'en') { diff --git a/www/js/intro.js b/www/js/intro.js index aa1c7b2f5..e96570699 100644 --- a/www/js/intro.js +++ b/www/js/intro.js @@ -26,7 +26,7 @@ angular.module('emission.intro', ['emission.splash.startprefs', .controller('IntroCtrl', function($scope, $rootScope, $state, $window, $ionicPlatform, $ionicSlideBoxDelegate, - $ionicPopup, $ionicHistory, ionicToast, $timeout, CommHelper, StartPrefs, SurveyLaunch, DynamicConfig, i18nUtils, $translate) { + $ionicPopup, $ionicHistory, ionicToast, $timeout, CommHelper, StartPrefs, SurveyLaunch, DynamicConfig, i18nUtils) { /* * Move all the state that is currently in the controller body into the init @@ -187,9 +187,9 @@ angular.module('emission.intro', ['emission.splash.startprefs', $ionicPlatform.ready().then(() => { DynamicConfig.configReady().then((newConfig) => { Logger.log("Resolved UI_CONFIG_READY promise in intro.js, filling in templates"); - $scope.lang = $translate.use(); + $scope.lang = i18next.resolvedLanguage; $scope.ui_config = newConfig; - // TODO: we should be able to use $translate for this, right? + // TODO: we should be able to use i18n for this, right? $scope.template_text = newConfig.intro.translated_text[$scope.lang]; if (!$scope.template_text) { $scope.template_text = newConfig.intro.translated_text["en"] diff --git a/www/js/join/join-ctrl.js b/www/js/join/join-ctrl.js index e879c2cd6..3884ae7fd 100644 --- a/www/js/join/join-ctrl.js +++ b/www/js/join/join-ctrl.js @@ -7,7 +7,7 @@ angular.module('emission.join.ctrl', ['emission.splash.startprefs', 'emission.splash.remotenotify', 'emission.stats.clientstats']) .controller('JoinCtrl', function($scope, $state, $interval, $rootScope, - $ionicPlatform, $ionicPopup, $ionicPopover, $translate) { + $ionicPlatform, $ionicPopup, $ionicPopover) { console.log('JoinCtrl invoked'); // alert("attach debugger!"); // PushNotify.startupInit(); @@ -35,6 +35,13 @@ angular.module('emission.join.ctrl', ['emission.splash.startprefs', $scope.popover.hide($event) } + function handleOpenURL(url) { + console.log("onLaunch method from external function called"); + var c = document.querySelectorAll("[ng-app]")[0]; + var scope = angular.element(c).scope(); + scope.$broadcast("CUSTOM_URL_LAUNCH", url); + }; + $scope.scanCode = function() { if (!$scope.scanEnabled) { $ionicPopup.alert({template: "plugins not yet initialized, please retry later"}); @@ -59,11 +66,11 @@ angular.module('emission.join.ctrl', ['emission.splash.startprefs', $scope.data = {}; const tokenPopup = $ionicPopup.show({ template: '
', + title: i18next.t('login.enter-existing-token') + '
', scope: $scope, buttons: [ { - text: '' + $translate.instant('login.button-accept') + '', + text: '' + i18next.t('login.button-accept') + '', type: 'button-positive', onTap: function(e) { if (!$scope.data.existing_token) { @@ -75,7 +82,7 @@ angular.module('emission.join.ctrl', ['emission.splash.startprefs', } } },{ - text: '' + $translate.instant('login.button-decline') + '', + text: '' + i18next.t('login.button-decline') + '', type: 'button-stable', onTap: function(e) { return null; diff --git a/www/js/main.js b/www/js/main.js index f48a466fc..a12ef4a77 100644 --- a/www/js/main.js +++ b/www/js/main.js @@ -69,12 +69,12 @@ angular.module('emission.main', ['emission.main.diary', } }) -.controller('MainCtrl', function($scope, $state, $rootScope, $translate, $ionicPlatform, DynamicConfig) { +.controller('MainCtrl', function($scope, $state, $rootScope, $ionicPlatform, DynamicConfig) { // Currently this is blank since it is basically a placeholder for the // three screens. But we can totally add hooks here if we want. It is the // controller for all the screens because none of them do anything for now. - moment.locale($translate.use()); + moment.locale(i18next.resolvedLanguage); $scope.tabsCustomClass = function() { return "tabs-icon-top tabs-custom"; diff --git a/www/js/metrics.js b/www/js/metrics.js index 634118216..eb939a411 100644 --- a/www/js/metrics.js +++ b/www/js/metrics.js @@ -18,8 +18,7 @@ angular.module('emission.main.metrics',['emission.services', ClientStats, CommHelper, $window, $ionicPopup, ionicDatePicker, $ionicPlatform, FootprintHelper, CalorieCal, ImperialConfig, $ionicModal, $timeout, KVStore, CarbonDatasetHelper, - $rootScope, $location, $state, ReferHelper, Logger, - $translate) { + $rootScope, $location, $state, ReferHelper, Logger) { var lastTwoWeeksQuery = true; $scope.defaultTwoWeekUserCall = true; @@ -175,7 +174,7 @@ angular.module('emission.main.metrics',['emission.services', showVis: true, showResult: true, current: true, - currentString: $translate.instant('metrics.last-week'), + currentString: i18next.t('metrics.last-week'), showChart: false, showSummary: true, showMe: true, @@ -345,7 +344,7 @@ angular.module('emission.main.metrics',['emission.services', bottom: 40, left: 55 }, - noData: $translate.instant('metrics.chart-no-data'), + noData: i18next.t('metrics.chart-no-data'), showControls: false, showValues: true, stacked: false, @@ -367,7 +366,7 @@ angular.module('emission.main.metrics',['emission.services', xAxis: { axisLabelDistance: 3, - axisLabel: $translate.instant('metrics.chart-xaxis-date'), + axisLabel: i18next.t('metrics.chart-xaxis-date'), tickFormat: function(d) { var day = new Date(d * 1000) day.setDate(day.getDate()+1) // Had to add a day to match date with data @@ -377,7 +376,7 @@ angular.module('emission.main.metrics',['emission.services', staggerLabels: true }, yAxis: { - axisLabel: $translate.instant('metrics.trips-yaxis-number'), + axisLabel: i18next.t('metrics.trips-yaxis-number'), axisLabelDistance: -10 }, callback: function(chart) { @@ -505,10 +504,10 @@ angular.module('emission.main.metrics',['emission.services', var getMetrics = function() { $ionicLoading.show({ - template: $translate.instant('loading') + template: i18next.t('loading') }); if(!$scope.defaultTwoWeekUserCall){ - $scope.uictrl.currentString = $translate.instant('metrics.custom'); + $scope.uictrl.currentString = i18next.t('metrics.custom'); $scope.uictrl.current = false; } //$scope.uictrl.showRange = false; @@ -588,8 +587,8 @@ angular.module('emission.main.metrics',['emission.services', }) .catch(function(error) { $ionicLoading.hide(); - $scope.carbonData.aggrCarbon = $translate.instant('metrics.carbon-data-unknown'); - $scope.caloriesData.aggrCalories = $translate.instant('metrics.calorie-data-unknown'); + $scope.carbonData.aggrCarbon = i18next.t('metrics.carbon-data-unknown'); + $scope.caloriesData.aggrCalories = i18next.t('metrics.calorie-data-unknown'); Logger.displayError("Error loading aggregate data, averages not available", error); }); @@ -897,25 +896,25 @@ angular.module('emission.main.metrics',['emission.services', $scope.showCharts = function(agg_metrics) { $scope.data = agg_metrics; $scope.countOptions = angular.copy($scope.options) - $scope.countOptions.chart.yAxis.axisLabel = $translate.instant('metrics.trips-yaxis-number'); + $scope.countOptions.chart.yAxis.axisLabel = i18next.t('metrics.trips-yaxis-number'); $scope.distanceOptions = angular.copy($scope.options) $scope.distanceOptions.chart.yAxis.axisLabel = ImperialConfig.getDistanceSuffix; $scope.durationOptions = angular.copy($scope.options) - $scope.durationOptions.chart.yAxis.axisLabel = $translate.instant('metrics.hours'); + $scope.durationOptions.chart.yAxis.axisLabel = i18next.t('metrics.hours'); $scope.speedOptions = angular.copy($scope.options) $scope.speedOptions.chart.yAxis.axisLabel = ImperialConfig.getSpeedSuffix; }; $scope.pandaFreqOptions = [ - {text: $translate.instant('metrics.pandafreqoptions-daily'), value: 'D'}, - {text: $translate.instant('metrics.pandafreqoptions-weekly'), value: 'W'}, - {text: $translate.instant('metrics.pandafreqoptions-biweekly'), value: '2W'}, - {text: $translate.instant('metrics.pandafreqoptions-monthly'), value: 'M'}, - {text: $translate.instant('metrics.pandafreqoptions-yearly'), value: 'A'} + {text: i18next.t('metrics.pandafreqoptions-daily'), value: 'D'}, + {text: i18next.t('metrics.pandafreqoptions-weekly'), value: 'W'}, + {text: i18next.t('metrics.pandafreqoptions-biweekly'), value: '2W'}, + {text: i18next.t('metrics.pandafreqoptions-monthly'), value: 'M'}, + {text: i18next.t('metrics.pandafreqoptions-yearly'), value: 'A'} ]; $scope.freqOptions = [ - {text: $translate.instant('metrics.freqoptions-daily'), value:'DAILY'}, - {text: $translate.instant('metrics.freqoptions-monthly'), value: 'MONTHLY'}, - {text: $translate.instant('metrics.freqoptions-yearly'), value: 'YEARLY'} + {text: i18next.t('metrics.freqoptions-daily'), value:'DAILY'}, + {text: i18next.t('metrics.freqoptions-monthly'), value: 'MONTHLY'}, + {text: i18next.t('metrics.freqoptions-yearly'), value: 'YEARLY'} ]; /* @@ -1044,7 +1043,7 @@ angular.module('emission.main.metrics',['emission.services', $scope.formatCount = function(value) { const formatVal = Math.round(value); - const unit = $translate.instant('metrics.trips'); + const unit = i18next.t('metrics.trips'); const stringRep = formatVal + " " + unit; return [formatVal, unit, stringRep]; } @@ -1059,7 +1058,7 @@ angular.module('emission.main.metrics',['emission.services', $scope.formatDuration = function(value) { const durM = moment.duration(value * 1000); const formatVal = durM.asHours(); - const unit = $translate.instant('metrics.hours'); + const unit = i18next.t('metrics.hours'); const stringRep = durM.humanize(); return [formatVal, unit, stringRep]; } @@ -1115,7 +1114,7 @@ angular.module('emission.main.metrics',['emission.services', $scope.changeWeekday = function(stringSetFunction, target) { var weekdayOptions = [ - {text: $translate.instant('weekdays-all'), value: null}, + {text: i18next.t('weekdays-all'), value: null}, {text: moment.weekdays(1), value: 0}, {text: moment.weekdays(2), value: 1}, {text: moment.weekdays(3), value: 2}, @@ -1126,8 +1125,8 @@ angular.module('emission.main.metrics',['emission.services', ]; $ionicActionSheet.show({ buttons: weekdayOptions, - titleText: $translate.instant('weekdays-select'), - cancelText: $translate.instant('metrics.cancel'), + titleText: i18next.t('weekdays-select'), + cancelText: i18next.t('metrics.cancel'), buttonClicked: function(index, button) { stringSetFunction(button.text); if (target === 'from') { @@ -1144,8 +1143,8 @@ angular.module('emission.main.metrics',['emission.services', $scope.changeFreq = function() { $ionicActionSheet.show({ buttons: $scope.freqOptions, - titleText: $translate.instant('metrics.select-frequency'), - cancelText: $translate.instant('metrics.cancel'), + titleText: i18next.t('metrics.select-frequency'), + cancelText: i18next.t('metrics.cancel'), buttonClicked: function(index, button) { $scope.selectCtrl.freqString = button.text; $scope.selectCtrl.freq = button.value; @@ -1157,8 +1156,8 @@ angular.module('emission.main.metrics',['emission.services', $scope.changePandaFreq = function() { $ionicActionSheet.show({ buttons: $scope.pandaFreqOptions, - titleText: $translate.instant('metrics.select-pandafrequency'), - cancelText: $translate.instant('metrics.cancel'), + titleText: i18next.t('metrics.select-pandafrequency'), + cancelText: i18next.t('metrics.cancel'), buttonClicked: function(index, button) { $scope.selectCtrl.pandaFreqString = button.text; $scope.selectCtrl.pandaFreq = button.value; @@ -1181,9 +1180,9 @@ angular.module('emission.main.metrics',['emission.services', var now = moment().utc(); var weekAgoFromNow = moment().utc().subtract(7, 'd'); $scope.selectCtrl.freq = 'DAILY'; - $scope.selectCtrl.freqString = $translate.instant('metrics.freqoptions-daily'); + $scope.selectCtrl.freqString = i18next.t('metrics.freqoptions-daily'); $scope.selectCtrl.pandaFreq = 'D'; - $scope.selectCtrl.pandaFreqString = $translate.instant('metrics.pandafreqoptions-daily'); + $scope.selectCtrl.pandaFreqString = i18next.t('metrics.pandafreqoptions-daily'); // local_date saved as localdate $scope.selectCtrl.fromDateLocalDate = moment2Localdate(weekAgoFromNow); $scope.selectCtrl.toDateLocalDate = moment2Localdate(now); @@ -1191,8 +1190,8 @@ angular.module('emission.main.metrics',['emission.services', $scope.selectCtrl.fromDateTimestamp= weekAgoFromNow; $scope.selectCtrl.toDateTimestamp = now; - $scope.selectCtrl.fromDateWeekdayString = $translate.instant('weekdays-all'); - $scope.selectCtrl.toDateWeekdayString = $translate.instant('weekdays-all'); + $scope.selectCtrl.fromDateWeekdayString = i18next.t('weekdays-all'); + $scope.selectCtrl.toDateWeekdayString = i18next.t('weekdays-all'); $scope.selectCtrl.fromDateWeekdayValue = null; $scope.selectCtrl.toDateWeekdayValue = null; @@ -1328,9 +1327,9 @@ angular.module('emission.main.metrics',['emission.services', title: '', scope: $scope, buttons: [ - { text: $translate.instant('metrics.cancel') }, + { text: i18next.t('metrics.cancel') }, { - text: ''+ $translate.instant('metrics.confirm') +'', + text: ''+ i18next.t('metrics.confirm') +'', type: 'button-positive', onTap: function(e) { if (!($scope.userData.gender != -1 && $scope.userData.age && $scope.userData.weight && $scope.userData.height)) { @@ -1346,10 +1345,10 @@ angular.module('emission.main.metrics',['emission.services', } $scope.datepickerObjBase = { - todayLabel: $translate.instant('list-datepicker-today'), //Optional - closeLabel: $translate.instant('list-datepicker-close'), //Optional - setLabel: $translate.instant('list-datepicker-set'), //Optional - titleLabel: $translate.instant('metrics.pick-a-date'), + todayLabel: i18next.t('list-datepicker-today'), //Optional + closeLabel: i18next.t('list-datepicker-close'), //Optional + setLabel: i18next.t('list-datepicker-set'), //Optional + titleLabel: i18next.t('metrics.pick-a-date'), mondayFirst: false, weeksList: moment.weekdaysMin(), monthsList: moment.monthsShort(), diff --git a/www/js/services.js b/www/js/services.js index c963b7d41..4d8afd9f2 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -382,7 +382,6 @@ angular.module('emission.services', ['emission.plugin.logger', }) .service('ControlHelper', function($window, $ionicPopup, - $translate, CommHelper, Logger) { @@ -454,14 +453,14 @@ angular.module('emission.services', ['emission.plugin.logger', attachFile = "app://cache/"+dumpFile; } if (ionic.Platform.isIOS()) { - alert($translate.instant('email-service.email-account-mail-app')); + alert(i18next.t('email-service.email-account-mail-app')); } var email = { attachments: [ attachFile ], - subject: $translate.instant('email-service.email-data.subject-data-dump-from-to', {start: startMoment.format(fmt),end: endMoment.format(fmt)}), - body: $translate.instant('email-service.email-data.body-data-consists-of-list-of-entries') + subject: i18next.t('email-service.email-data.subject-data-dump-from-to', {start: startMoment.format(fmt),end: endMoment.format(fmt)}), + body: i18next.t('email-service.email-data.body-data-consists-of-list-of-entries') } $window.cordova.plugins.email.open(email).then(resolve()); } diff --git a/www/js/splash/customURL.js b/www/js/splash/customURL.js index 1e9babab3..521244bc0 100644 --- a/www/js/splash/customURL.js +++ b/www/js/splash/customURL.js @@ -38,11 +38,3 @@ angular.module('emission.splash.customURLScheme', []) return cus; }); - -function handleOpenURL(url) { - console.log("onLaunch method from external function called"); - var c = document.querySelectorAll("[ng-app]")[0]; - var scope = angular.element(c).scope(); - scope.$broadcast("CUSTOM_URL_LAUNCH", url); -}; - diff --git a/www/js/splash/notifScheduler.js b/www/js/splash/notifScheduler.js index 9f34712e9..349248cf5 100644 --- a/www/js/splash/notifScheduler.js +++ b/www/js/splash/notifScheduler.js @@ -8,7 +8,7 @@ angular.module('emission.splash.notifscheduler', 'emission.stats.clientstats', 'emission.config.dynamic']) -.factory('NotificationScheduler', function($http, $window, $ionicPlatform, $translate, +.factory('NotificationScheduler', function($http, $window, $ionicPlatform, ClientStats, DynamicConfig, CommHelper, Logger) { const scheduler = {}; @@ -73,7 +73,7 @@ angular.module('emission.splash.notifscheduler', const scheduleNotifs = (scheme, notifTimes) => { return new Promise((rs) => { isScheduling = true; - const localeCode = $translate.use(); + const localeCode = i18next.resolvedLanguage; const nots = notifTimes.map((n) => { const nDate = n.toDate(); const seconds = nDate.getTime() / 1000; diff --git a/www/js/splash/storedevicesettings.js b/www/js/splash/storedevicesettings.js index 2da609af2..aaaf82c6b 100644 --- a/www/js/splash/storedevicesettings.js +++ b/www/js/splash/storedevicesettings.js @@ -4,12 +4,12 @@ angular.module('emission.splash.storedevicesettings', ['emission.plugin.logger', 'emission.services', 'emission.splash.startprefs']) .factory('StoreDeviceSettings', function($window, $state, $rootScope, $ionicPlatform, - $ionicPopup, $translate, Logger, CommHelper, StartPrefs) { + $ionicPopup, Logger, CommHelper, StartPrefs) { var storedevicesettings = {}; storedevicesettings.storeDeviceSettings = function() { - var lang = $translate.use(); + var lang = i18next.resolvedLanguage; var manufacturer = $window.device.manufacturer; var osver = $window.device.version; return $window.cordova.getAppVersion.getVersionNumber().then(function(appver) { diff --git a/www/js/survey/enketo/answer.js b/www/js/survey/enketo/answer.js index 80042aa58..d060d4843 100644 --- a/www/js/survey/enketo/answer.js +++ b/www/js/survey/enketo/answer.js @@ -1,13 +1,11 @@ import angular from 'angular'; +import MessageFormat from 'messageformat'; angular.module('emission.survey.enketo.answer', [ 'ionic', 'emission.config.dynamic', ]) -.factory('EnketoSurveyAnswer', function( - $http, DynamicConfig, $translate, - // $translateMessageFormatInterpolation -) { +.factory('EnketoSurveyAnswer', function($http, DynamicConfig,) { /** * @typedef EnketoAnswerData * @type {object} @@ -45,7 +43,7 @@ angular.module('emission.survey.enketo.answer', [ return _lazyLoadConfig().then(configSurveys => { const config = configSurveys[name]; // config for this survey - const lang = $translate.use(); + const lang = i18next.resolvedLanguage; const labelTemplate = config.labelTemplate?.[lang]; if (!labelTemplate) return "Answered"; // no template given in config @@ -66,8 +64,9 @@ angular.module('emission.survey.enketo.answer', [ } } - // const label = $translateMessageFormatInterpolation.interpolate(labelTemplate, labelVars); - const label = labelTemplate; + // use MessageFormat interpolate the label template with the label vars + const mf = new MessageFormat(lang); + const label = mf.compile(labelTemplate)(labelVars); return label.replace(/^[ ,]+|[ ,]+$/g, ''); // trim leading and trailing spaces and commas }) } diff --git a/www/js/survey/enketo/enketo-add-note-button.js b/www/js/survey/enketo/enketo-add-note-button.js index 4fbe59d3e..18fe4168f 100644 --- a/www/js/survey/enketo/enketo-add-note-button.js +++ b/www/js/survey/enketo/enketo-add-note-button.js @@ -25,14 +25,14 @@ angular.module('emission.survey.enketo.add-note-button', templateUrl: 'templates/survey/enketo/add-note-button.html' }; }) -.controller("EnketoAddNoteButtonCtrl", function($scope, $element, $attrs, $translate, +.controller("EnketoAddNoteButtonCtrl", function($scope, $element, $attrs, EnketoSurveyLaunch, $ionicPopover, ClientStats, DynamicConfig, EnketoNotesButtonService) { console.log("Invoked enketo directive controller for add-note-button"); $scope.notes = []; const updateLabel = () => { - const localeCode = $translate.use(); + const localeCode = i18next.resolvedLanguage; if ($scope.notesConfig?.['filled-in-label'] && timelineEntry.additionsList?.length > 0) { $scope.displayLabel = $scope.notesConfig?.['filled-in-label']?.[localeCode]; } else { diff --git a/www/js/survey/enketo/enketo-demographics.js b/www/js/survey/enketo/enketo-demographics.js index 6f22ff967..01a7131fa 100644 --- a/www/js/survey/enketo/enketo-demographics.js +++ b/www/js/survey/enketo/enketo-demographics.js @@ -47,7 +47,7 @@ angular.module('emission.survey.enketo.demographics', $scope.init(); }) .controller("EnketoDemographicsInlineCtrl", function($scope, $window, $element, $attrs, - $http, EnketoSurveyLaunch, EnketoSurvey, $ionicPopover, ClientStats, $translate, + $http, EnketoSurveyLaunch, EnketoSurvey, $ionicPopover, ClientStats, EnketoDemographicsService, $ionicPlatform, $timeout) { console.log("Invoked enketo inline directive controller for demographics "); @@ -55,7 +55,7 @@ angular.module('emission.survey.enketo.demographics', return EnketoSurvey.validateAndSave() .then(result => { if (!result) { - $ionicPopup.alert({template: $translate.instant('survey.enketo-form-errors')}); + $ionicPopup.alert({template: i18next.t('survey.enketo-form-errors')}); } else { $scope.ngDone(); } @@ -122,7 +122,7 @@ angular.module('emission.survey.enketo.demographics', $ionicPlatform.ready(() => $scope.init()); }) -.factory("EnketoDemographicsService", function(UnifiedDataLoader, $window, $ionicLoading, $translate) { +.factory("EnketoDemographicsService", function(UnifiedDataLoader, $window, $ionicLoading) { var eds = {}; console.log("Creating EnketoDemographicsService"); eds.key = "manual/demographic_survey"; diff --git a/www/js/survey/enketo/infinite_scroll_filters.js b/www/js/survey/enketo/infinite_scroll_filters.js index 01a0c5b50..8e45db8e4 100644 --- a/www/js/survey/enketo/infinite_scroll_filters.js +++ b/www/js/survey/enketo/infinite_scroll_filters.js @@ -14,7 +14,7 @@ angular.module('emission.survey.enketo.trip.infscrollfilters',[ 'emission.survey.enketo.trip.button', 'emission.plugin.logger' ]) -.factory('EnketoTripInfScrollFilters', function(Logger, EnketoTripButtonService, $translate){ +.factory('EnketoTripInfScrollFilters', function(Logger, EnketoTripButtonService){ var sf = {}; var unlabeledCheck = function(t) { return !angular.isDefined(t.userInput[EnketoTripButtonService.SINGLE_KEY]); @@ -22,13 +22,13 @@ angular.module('emission.survey.enketo.trip.infscrollfilters',[ sf.UNLABELED = { key: "unlabeled", - text: $translate.instant("diary.unlabeled"), + text: i18next.t("diary.unlabeled"), filter: unlabeledCheck } sf.TO_LABEL = { key: "to_label", - text: $translate.instant("diary.to-label"), + text: i18next.t("diary.to-label"), filter: unlabeledCheck } diff --git a/www/js/survey/enketo/launch.js b/www/js/survey/enketo/launch.js index 0efe9889c..ca4b359fe 100644 --- a/www/js/survey/enketo/launch.js +++ b/www/js/survey/enketo/launch.js @@ -7,9 +7,7 @@ angular.module('emission.survey.enketo.launch', [ 'emission.survey.enketo.service', 'emission.plugin.logger', ]) -.factory('EnketoSurveyLaunch', function( - $ionicPopup, EnketoSurvey, $ionicModal, $translate -) { +.factory('EnketoSurveyLaunch', function($ionicPopup, EnketoSurvey, $ionicModal) { /** * @typedef EnketoSurveyLaunchState * @type {{ @@ -134,7 +132,7 @@ angular.module('emission.survey.enketo.launch', [ return EnketoSurvey.validateAndSave() .then(result => { if (!result) { - $ionicPopup.alert({template: $translate.instant('survey.enketo-form-errors')}); + $ionicPopup.alert({template: i18next.t('survey.enketo-form-errors')}); } else if (result instanceof Error) { $ionicPopup.alert({template: result.message}); console.error(result); diff --git a/www/js/survey/enketo/service.js b/www/js/survey/enketo/service.js index 4a8725dc3..0fe0ff9ff 100644 --- a/www/js/survey/enketo/service.js +++ b/www/js/survey/enketo/service.js @@ -7,8 +7,7 @@ angular.module('emission.survey.enketo.service', [ 'emission.survey.inputmatcher', 'emission.survey.enketo.answer' ]) -.factory('EnketoSurvey', function( - $window, $http, $translate, UnifiedDataLoader, +.factory('EnketoSurvey', function($window, $http, UnifiedDataLoader, InputMatcher, EnketoSurveyAnswer, DynamicConfig) { /** * @typedef EnketoSurveyConfig @@ -80,7 +79,7 @@ angular.module('emission.survey.enketo.service', [ external: opts.external || [], session: opts.session || {} }; - const currLang = $translate.use(); + const currLang = i18next.resolvedLanguage; _state.form = new $window.FormModule(formSelector, data, {language: currLang}); return _state.form.init(); @@ -159,7 +158,7 @@ angular.module('emission.survey.enketo.service', [ let timestamps = EnketoSurveyAnswer.resolveTimestamps(xmlDoc, _state.opts.timelineEntry); if (timestamps === undefined) { // timestamps were resolved, but they are invalid - return new Error($translate.instant('survey.enketo-timestamps-invalid')); //"Timestamps are invalid. Please ensure that the start time is before the end time."); + return new Error(i18next.t('survey.enketo-timestamps-invalid')); //"Timestamps are invalid. Please ensure that the start time is before the end time."); } // if timestamps were not resolved from the survey, we will use the trip or place timestamps timestamps ||= _state.opts.timelineEntry.data.properties; diff --git a/www/js/survey/input-matcher.js b/www/js/survey/input-matcher.js index 4aa655fc8..57a47e880 100644 --- a/www/js/survey/input-matcher.js +++ b/www/js/survey/input-matcher.js @@ -3,7 +3,7 @@ import angular from 'angular'; angular.module('emission.survey.inputmatcher', ['emission.plugin.logger']) -.factory('InputMatcher', function($translate, Logger){ +.factory('InputMatcher', function(Logger){ var im = {}; const EPOCH_MAXIMUM = 2**31 - 1; diff --git a/www/js/survey/multilabel/infinite_scroll_filters.js b/www/js/survey/multilabel/infinite_scroll_filters.js index 8233819c2..12ab3e366 100644 --- a/www/js/survey/multilabel/infinite_scroll_filters.js +++ b/www/js/survey/multilabel/infinite_scroll_filters.js @@ -14,7 +14,7 @@ angular.module('emission.survey.multilabel.infscrollfilters',[ 'emission.survey.multilabel.services', 'emission.plugin.logger' ]) -.factory('MultiLabelInfScrollFilters', function(Logger, ConfirmHelper, $translate){ +.factory('MultiLabelInfScrollFilters', function(Logger, ConfirmHelper){ var sf = {}; var unlabeledCheck = function(t) { return t.INPUTS @@ -42,20 +42,20 @@ angular.module('emission.survey.multilabel.infscrollfilters',[ sf.UNLABELED = { key: "unlabeled", - text: $translate.instant("diary.unlabeled"), + text: i18next.t("diary.unlabeled"), filter: unlabeledCheck, width: "col-50" } sf.INVALID_EBIKE = { key: "invalid_ebike", - text: $translate.instant("diary.invalid-ebike"), + text: i18next.t("diary.invalid-ebike"), filter: invalidCheck } sf.TO_LABEL = { key: "to_label", - text: $translate.instant("diary.to-label"), + text: i18next.t("diary.to-label"), filter: toLabelCheck, width: "col-50" } diff --git a/www/js/survey/multilabel/trip-confirm-services.js b/www/js/survey/multilabel/trip-confirm-services.js index 0ffa94d50..62e25c9af 100644 --- a/www/js/survey/multilabel/trip-confirm-services.js +++ b/www/js/survey/multilabel/trip-confirm-services.js @@ -2,7 +2,7 @@ import angular from 'angular'; angular.module('emission.survey.multilabel.services', ['ionic', 'emission.i18n.utils', "emission.plugin.logger", "emission.config.dynamic"]) -.factory("ConfirmHelper", function($http, $ionicPopup, $ionicPlatform, $translate, i18nUtils, DynamicConfig, Logger) { +.factory("ConfirmHelper", function($http, $ionicPopup, $ionicPlatform, i18nUtils, DynamicConfig, Logger) { var ch = {}; ch.init = function(ui_config) { Logger.log("About to start initializing the confirm helper for " + ui_config.intro.program_or_study); @@ -12,8 +12,8 @@ angular.module('emission.survey.multilabel.services', ['ionic', 'emission.i18n.u ch.inputDetails = { "MODE": { name: "MODE", - labeltext: $translate.instant(".mode"), - choosetext: $translate.instant(".choose-mode"), + labeltext: "diary.mode", + choosetext: "diary.choose-mode", width: labelWidth["base"], btnWidth: btnWidth["base"], key: "manual/mode_confirm", @@ -21,8 +21,8 @@ angular.module('emission.survey.multilabel.services', ['ionic', 'emission.i18n.u }, "PURPOSE": { name: "PURPOSE", - labeltext: $translate.instant(".purpose"), - choosetext: $translate.instant(".choose-purpose"), + labeltext: "diary.purpose", + choosetext: "diary.choose-purpose", width: labelWidth["base"], btnWidth: btnWidth["base"], key: "manual/purpose_confirm", @@ -46,8 +46,8 @@ angular.module('emission.survey.multilabel.services', ['ionic', 'emission.i18n.u console.log("Finished resetting label widths ",ch.inputDetails); ch.inputDetails["REPLACED_MODE"] = { name: "REPLACED_MODE", - labeltext: $translate.instant(".replaces"), - choosetext: $translate.instant(".choose-replaced-mode"), + labeltext: "diary.replaces", + choosetext: "diary.choose-replaced-mode", width: labelWidth["intervention"], btnWidth: btnWidth["intervention"], key: "manual/replaced_mode", @@ -137,7 +137,7 @@ angular.module('emission.survey.multilabel.services', ['ionic', 'emission.i18n.u */ ch.getOptions = function(inputType) { if (!angular.isDefined(ch.inputDetails[inputType].options)) { - var lang = $translate.use(); + var lang = i18next.resolvedLanguage; return loadAndPopulateOptions() .then(function () { return ch.inputDetails[inputType].options; @@ -148,18 +148,18 @@ angular.module('emission.survey.multilabel.services', ['ionic', 'emission.i18n.u } ch.checkOtherOption = function(inputType, onTapFn, $scope) { - $ionicPopup.show({title: $translate.instant("trip-confirm.services-please-fill-in",{text: inputType.toLowerCase()}), + $ionicPopup.show({title: i18next.t("trip-confirm.services-please-fill-in",{text: inputType.toLowerCase()}), scope: $scope, template: '', buttons: [ - { text: $translate.instant('trip-confirm.services-cancel'), + { text: i18next.t('trip-confirm.services-cancel'), onTap: function(e) { ch.INPUTS.forEach(function(item) { $scope.selected[item] = {value: ''}; }); } }, { - text: '' + $translate.instant('trip-confirm.services-save') + '', + text: '' + i18next.t('trip-confirm.services-save') + '', type: 'button-positive', onTap: onTapFn($scope, inputType) } diff --git a/www/templates/appstatus/permissioncheck.html b/www/templates/appstatus/permissioncheck.html index b2099bb81..5418dfdb8 100644 --- a/www/templates/appstatus/permissioncheck.html +++ b/www/templates/appstatus/permissioncheck.html @@ -1,8 +1,8 @@ -{{'intro.appstatus.overall-description' | translate }} +{{'intro.appstatus.overall-description' | i18next }}
-

{{ 'intro.appstatus.overall-loc-name' | translate }} {{overallLocStatusIcon}}

-{{'intro.appstatus.overall-loc-description' | translate }} +

{{ 'intro.appstatus.overall-loc-name' | i18next }} {{overallLocStatusIcon}}

+{{'intro.appstatus.overall-loc-description' | i18next }} @@ -22,7 +22,7 @@

{{ overallFitnessName }} {{overallFitnessStatusIcon}}

-{{'intro.appstatus.overall-fitness-description' | translate }} +{{'intro.appstatus.overall-fitness-description' | i18next }} @@ -41,8 +41,8 @@
-

{{ 'intro.appstatus.overall-notification-name' | translate }} {{overallNotificationStatusIcon}}

-{{'intro.appstatus.overall-notification-description' | translate }} +

{{ 'intro.appstatus.overall-notification-name' | i18next }} {{overallNotificationStatusIcon}}

+{{'intro.appstatus.overall-notification-description' | i18next }}
@@ -60,8 +60,8 @@
-

{{ 'intro.appstatus.overall-background-restrictions-name' | translate }} {{overallBackgroundRestrictionStatusIcon}}

-{{'intro.appstatus.overall-background-restrictions-description' | translate }} +

{{ 'intro.appstatus.overall-background-restrictions-name' | i18next }} {{overallBackgroundRestrictionStatusIcon}}

+{{'intro.appstatus.overall-background-restrictions-description' | i18next }} diff --git a/www/templates/caloriePopup.html b/www/templates/caloriePopup.html index c35e839fa..be568f81f 100644 --- a/www/templates/caloriePopup.html +++ b/www/templates/caloriePopup.html @@ -1,10 +1,10 @@ -
{{'user-gender'}}
+
{{'user-gender'}}
-
{{'gender-male'}}
-
{{'gender-female'}}
+
{{'gender-male'}}
+
{{'gender-female'}}
-
{{'user-height'}}
+
{{'user-height'}}
@@ -12,7 +12,7 @@
ft
-
{{'user-weight'}}
+
{{'user-weight'}}
@@ -20,5 +20,5 @@
lb
-
{{'user-age'}}
+
{{'user-age'}}
diff --git a/www/templates/control/app-status-modal.html b/www/templates/control/app-status-modal.html index 1a32d2e6d..965b2857d 100644 --- a/www/templates/control/app-status-modal.html +++ b/www/templates/control/app-status-modal.html @@ -1,4 +1,4 @@ - +

Permissions

@@ -6,7 +6,9 @@

Permissions

diff --git a/www/templates/control/main-control.html b/www/templates/control/main-control.html index 67eda9352..ee6c1bd03 100644 --- a/www/templates/control/main-control.html +++ b/www/templates/control/main-control.html @@ -1,4 +1,4 @@ - +
{{settings.auth.opcode}}
@@ -6,15 +6,17 @@
-
{{'.view-privacy'}}
+
{{'control.view-privacy'}}
-
{{'.view-qrc'}}
+
{{'control.view-qrc'}}
-
+
+ {{'control.reminders-time-of-day' | i18next: {time: settings.notification.prefReminderTime} }} +
-
{{'.tracking'}}
+
{{'control.tracking'}}