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'}}