In today's web-based applications internationalization is an important feature. For fulfilling this requirement we need to first understand the two terms localization and internationalization.

Localization

The definition of Localization differs from person to person, but the main concept remains same. That is, it refers to the adaptation of a product, application or document, depending on the required language and culture setting (that is normally called the locale). Except for that, localization consists of the following topics depending on the language setting.
  1. Numeric value format.
  2. Date and time format.
  3. Currency format.
  4. Symbols, icons and colors.

Internationalization

Just as for Localization, the definition of Internationalization always varies. The actual concept is to design a product, application or document in such a way that it can be used for the localization settings depending on culture, region or language. Sometimes it is also called globalization.
Internationalization is often written i18n, where 18 is the number of letters between i and n in the English word.
This article explains how to use this concept in AngularJs. Likely, AngularJs directly supports internationalization and it also provides various locale setting files for various locales.
For downloading an AngulaJS Locale file, please visit the URL link and download your required locale setting file.
For doing this, we will first create a blank web site project and then 3 folders within the project with the following name:
  1. HTML
  2. Scripts
  3. UserScripts
Now add a HTML file named index.html with the following code:
  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <title></title>
  5. <script src="../Scripts/angular.min.js"></script>
  6. <script src="../Scripts/angular-route.js"></script>
  7. <script src="../Scripts/angular-translate.js"></script>
  8. <script src="../UserScript/MyApp.js"></script>
  9. <script src="../UserScript/Index.js"></script>
  10. </head>
  11. <body ng-app="MyApp" ng-controller="TranslateController">
  12. <h1>Localization</h1>
  13. <div>
  14. <button ng-click="changeLanguage('en-de')" translate="BUTTON_TEXT_DE"></button>
  15. <button ng-click="changeLanguage('en')" translate="BUTTON_TEXT_EN"></button>
  16. <button ng-click="changeLanguage('en-ar')" translate="BUTTON_TEXT_AE"></button>
  17. </div>
  18. <div>
  19. <h2>{{ 'HEADLINE' | translate }}</h2>
  20. <p>{{ 'INTRO_TEXT' | translate }}</p>
  21. </div>
  22. <div>
  23. <input type="date" />
  24. </div>
  25. </body>
  26. </html>
Now in the preceding file, we use the reference of Angular.js and angular-route file. Since an Angular route file is required, we need to provide the localization code within the config section. We will explain it a little later. The third file that we called is the angular-translate file. Actually this file is responsible for providing the translate service for Angular. The following is the code for the angular-translate file:
  1. /*!
  2. * angular-translate - v2.7.2 - 2015-06-01
  3. * http://github.com/angular-translate/angular-translate
  4. * Copyright (c) 2015 ; Licensed MIT
  5. */
  6. (function (root, factory) {
  7. if (typeof define === 'function' && define.amd) {
  8. // AMD. Register as an anonymous module unless amdModuleId is set
  9. define([], function () {
  10. return (factory());
  11. });
  12. } else if (typeof exports === 'object') {
  13. // Node. Does not work with strict CommonJS, but
  14. // only CommonJS-like environments that support module.exports,
  15. // like Node.
  16. module.exports = factory();
  17. } else {
  18. factory();
  19. }
  20. }(this, function () {
  21. /**
  22. * @ngdoc overview
  23. * @name translate
  24. *
  25. * @description
  26. * The main module which holds everything together.
  27. */
  28. angular.module('translate', ['ng'])
  29. .run(runTranslate);
  30. function runTranslate($translate) {
  31. 'use strict';
  32. var key = $translate.storageKey(),
  33. storage = $translate.storage();
  34. var fallbackFromIncorrectStorageValue = function () {
  35. var preferred = $translate.preferredLanguage();
  36. if (angular.isString(preferred)) {
  37. $translate.use(preferred);
  38. // $translate.use() will also remember the language.
  39. // So, we don't need to call storage.put() here.
  40. } else {
  41. storage.put(key, $translate.use());
  42. }
  43. };
  44. fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue';
  45. if (storage) {
  46. if (!storage.get(key)) {
  47. fallbackFromIncorrectStorageValue();
  48. } else {
  49. $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue);
  50. }
  51. } else if (angular.isString($translate.preferredLanguage())) {
  52. $translate.use($translate.preferredLanguage());
  53. }
  54. }
  55. runTranslate.$inject = ['$translate'];
  56. runTranslate.displayName = 'runTranslate';
  57. /**
  58. * @ngdoc object
  59. * @name translate.$translateSanitizationProvider
  60. *
  61. * @description
  62. *
  63. * Configurations for $translateSanitization
  64. */
  65. angular.module('translate').provider('$translateSanitization', $translateSanitizationProvider);
  66. function $translateSanitizationProvider () {
  67. 'use strict';
  68. var $sanitize,
  69. currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0.
  70. hasConfiguredStrategy = false,
  71. hasShownNoStrategyConfiguredWarning = false,
  72. strategies;
  73. /**
  74. * Definition of a sanitization strategy function
  75. * @callback StrategyFunction
  76. * @param {string|object} value - value to be sanitized (either a string or an interpolated value map)
  77. * @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params
  78. * @return {string|object}
  79. */
  80. /**
  81. * @ngdoc property
  82. * @name strategies
  83. * @propertyOf translate.$translateSanitizationProvider
  84. *
  85. * @description
  86. * Following strategies are built-in:
  87. * <dl>
  88. * <dt>sanitize</dt>
  89. * <dd>Sanitizes HTML in the translation text using $sanitize</dd>
  90. * <dt>escape</dt>
  91. * <dd>Escapes HTML in the translation</dd>
  92. * <dt>sanitizeParameters</dt>
  93. * <dd>Sanitizes HTML in the values of the interpolation parameters using $sanitize</dd>
  94. * <dt>escapeParameters</dt>
  95. * <dd>Escapes HTML in the values of the interpolation parameters</dd>
  96. * <dt>escaped</dt>
  97. * <dd>Support legacy strategy name 'escaped' for backwards compatibility (will be removed in 3.0)</dd>
  98. * </dl>
  99. *
  100. */
  101. strategies = {
  102. sanitize: function (value, mode) {
  103. if (mode === 'text') {
  104. value = htmlSanitizeValue(value);
  105. }
  106. return value;
  107. },
  108. escape: function (value, mode) {
  109. if (mode === 'text') {
  110. value = htmlEscapeValue(value);
  111. }
  112. return value;
  113. },
  114. sanitizeParameters: function (value, mode) {
  115. if (mode === 'params') {
  116. value = mapInterpolationParameters(value, htmlSanitizeValue);
  117. }
  118. return value;
  119. },
  120. escapeParameters: function (value, mode) {
  121. if (mode === 'params') {
  122. value = mapInterpolationParameters(value, htmlEscapeValue);
  123. }
  124. return value;
  125. }
  126. };
  127. // Support legacy strategy name 'escaped' for backwards compatibility.
  128. // TODO should be removed in 3.0
  129. strategies.escaped = strategies.escapeParameters;
  130. /**
  131. * @ngdoc function
  132. * @name translate.$translateSanitizationProvider#addStrategy
  133. * @methodOf translate.$translateSanitizationProvider
  134. *
  135. * @description
  136. * Adds a sanitization strategy to the list of known strategies.
  137. *
  138. * @param {string} strategyName - unique key for a strategy
  139. * @param {StrategyFunction} strategyFunction - strategy function
  140. * @returns {object} this
  141. */
  142. this.addStrategy = function (strategyName, strategyFunction) {
  143. strategies[strategyName] = strategyFunction;
  144. return this;
  145. };
  146. /**
  147. * @ngdoc function
  148. * @name translate.$translateSanitizationProvider#removeStrategy
  149. * @methodOf translate.$translateSanitizationProvider
  150. *
  151. * @description
  152. * Removes a sanitization strategy from the list of known strategies.
  153. *
  154. * @param {string} strategyName - unique key for a strategy
  155. * @returns {object} this
  156. */
  157. this.removeStrategy = function (strategyName) {
  158. delete strategies[strategyName];
  159. return this;
  160. };
  161. /**
  162. * @ngdoc function
  163. * @name translate.$translateSanitizationProvider#useStrategy
  164. * @methodOf translate.$translateSanitizationProvider
  165. *
  166. * @description
  167. * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
  168. *
  169. * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions.
  170. * @returns {object} this
  171. */
  172. this.useStrategy = function (strategy) {
  173. hasConfiguredStrategy = true;
  174. currentStrategy = strategy;
  175. return this;
  176. };
  177. /**
  178. * @ngdoc object
  179. * @name translate.$translateSanitization
  180. * @requires $injector
  181. * @requires $log
  182. *
  183. * @description
  184. * Sanitizes interpolation parameters and translated texts.
  185. *
  186. */
  187. this.$get = ['$injector', '$log', function ($injector, $log) {
  188. var applyStrategies = function (value, mode, selectedStrategies) {
  189. angular.forEach(selectedStrategies, function (selectedStrategy) {
  190. if (angular.isFunction(selectedStrategy)) {
  191. value = selectedStrategy(value, mode);
  192. } else if (angular.isFunction(strategies[selectedStrategy])) {
  193. value = strategies[selectedStrategy](value, mode);
  194. } else {
  195. throw new Error('translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
  196. }
  197. });
  198. return value;
  199. };
  200. // TODO: should be removed in 3.0
  201. var showNoStrategyConfiguredWarning = function () {
  202. if (!hasConfiguredStrategy && !hasShownNoStrategyConfiguredWarning) {
  203. $log.warn('translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details.');
  204. hasShownNoStrategyConfiguredWarning = true;
  205. }
  206. };
  207. if ($injector.has('$sanitize')) {
  208. $sanitize = $injector.get('$sanitize');
  209. }
  210. return {
  211. /**
  212. * @ngdoc function
  213. * @name translate.$translateSanitization#useStrategy
  214. * @methodOf translate.$translateSanitization
  215. *
  216. * @description
  217. * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
  218. *
  219. * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions.
  220. */
  221. useStrategy: (function (self) {
  222. return function (strategy) {
  223. self.useStrategy(strategy);
  224. };
  225. })(this),
  226. /**
  227. * @ngdoc function
  228. * @name translate.$translateSanitization#sanitize
  229. * @methodOf translate.$translateSanitization
  230. *
  231. * @description
  232. * Sanitizes a value.
  233. *
  234. * @param {string|object} value The value which should be sanitized.
  235. * @param {string} mode The current sanitization mode, either 'params' or 'text'.
  236. * @param {string|StrategyFunction|array} [strategy] Optional custom strategy which should be used instead of the currently selected strategy.
  237. * @returns {string|object} sanitized value
  238. */
  239. sanitize: function (value, mode, strategy) {
  240. if (!currentStrategy) {
  241. showNoStrategyConfiguredWarning();
  242. }
  243. if (arguments.length < 3) {
  244. strategy = currentStrategy;
  245. }
  246. if (!strategy) {
  247. return value;
  248. }
  249. var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy];
  250. return applyStrategies(value, mode, selectedStrategies);
  251. }
  252. };
  253. }];
  254. var htmlEscapeValue = function (value) {
  255. var element = angular.element('<div></div>');
  256. element.text(value); // not chainable, see #1044
  257. return element.html();
  258. };
  259. var htmlSanitizeValue = function (value) {
  260. if (!$sanitize) {
  261. throw new Error('translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as \'escape\'.');
  262. }
  263. return $sanitize(value);
  264. };
  265. var mapInterpolationParameters = function (value, iteratee) {
  266. if (angular.isObject(value)) {
  267. var result = angular.isArray(value) ? [] : {};
  268. angular.forEach(value, function (propertyValue, propertyKey) {
  269. result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee);
  270. });
  271. return result;
  272. } else if (angular.isNumber(value)) {
  273. return value;
  274. } else {
  275. return iteratee(value);
  276. }
  277. };
  278. }
  279. /**
  280. * @ngdoc object
  281. * @name translate.$translateProvider
  282. * @description
  283. *
  284. * $translateProvider allows developers to register translation-tables, asynchronous loaders
  285. * and similar to configure translation behavior directly inside of a module.
  286. *
  287. */
  288. angular.module('translate')
  289. .constant('pascalprechtTranslateOverrider', {})
  290. .provider('$translate', $translate);
  291. function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) {
  292. 'use strict';
  293. var $translationTable = {},
  294. $preferredLanguage,
  295. $availableLanguageKeys = [],
  296. $languageKeyAliases,
  297. $fallbackLanguage,
  298. $fallbackWasString,
  299. $uses,
  300. $nextLang,
  301. $storageFactory,
  302. $storageKey = $STORAGE_KEY,
  303. $storagePrefix,
  304. $missingTranslationHandlerFactory,
  305. $interpolationFactory,
  306. $interpolatorFactories = [],
  307. $loaderFactory,
  308. $cloakClassName = 'translate-cloak',
  309. $loaderOptions,
  310. $notFoundIndicatorLeft,
  311. $notFoundIndicatorRight,
  312. $postCompilingEnabled = false,
  313. $forceAsyncReloadEnabled = false,
  314. NESTED_OBJECT_DELIMITER = '.',
  315. loaderCache,
  316. directivePriority = 0,
  317. statefulFilter = true,
  318. uniformLanguageTagResolver = 'default',
  319. languageTagResolver = {
  320. 'default': function (tag) {
  321. return (tag || '').split('-').join('_');
  322. },
  323. java: function (tag) {
  324. var temp = (tag || '').split('-').join('_');
  325. var parts = temp.split('_');
  326. return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp;
  327. },
  328. bcp47: function (tag) {
  329. var temp = (tag || '').split('_').join('-');
  330. var parts = temp.split('-');
  331. return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp;
  332. }
  333. };
  334. var version = '2.7.2';
  335. // tries to determine the browsers language
  336. var getFirstBrowserLanguage = function () {
  337. // internal purpose only
  338. if (angular.isFunction(pascalprechtTranslateOverrider.getLocale)) {
  339. return pascalprechtTranslateOverrider.getLocale();
  340. }
  341. var nav = $windowProvider.$get().navigator,
  342. browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'],
  343. i,
  344. language;
  345. // support for HTML 5.1 "navigator.languages"
  346. if (angular.isArray(nav.languages)) {
  347. for (i = 0; i < nav.languages.length; i++) {
  348. language = nav.languages[i];
  349. if (language && language.length) {
  350. return language;
  351. }
  352. }
  353. }
  354. // support for other well known properties in browsers
  355. for (i = 0; i < browserLanguagePropertyKeys.length; i++) {
  356. language = nav[browserLanguagePropertyKeys[i]];
  357. if (language && language.length) {
  358. return language;
  359. }
  360. }
  361. return null;
  362. };
  363. getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage';
  364. // tries to determine the browsers locale
  365. var getLocale = function () {
  366. var locale = getFirstBrowserLanguage() || '';
  367. if (languageTagResolver[uniformLanguageTagResolver]) {
  368. locale = languageTagResolver[uniformLanguageTagResolver](locale);
  369. }
  370. return locale;
  371. };
  372. getLocale.displayName = 'angular-translate/service: getLocale';
  373. /**
  374. * @name indexOf
  375. * @private
  376. *
  377. * @description
  378. * indexOf polyfill. Kinda sorta.
  379. *
  380. * @param {array} array Array to search in.
  381. * @param {string} searchElement Element to search for.
  382. *
  383. * @returns {int} Index of search element.
  384. */
  385. var indexOf = function(array, searchElement) {
  386. for (var i = 0, len = array.length; i < len; i++) {
  387. if (array[i] === searchElement) {
  388. return i;
  389. }
  390. }
  391. return -1;
  392. };
  393. /**
  394. * @name trim
  395. * @private
  396. *
  397. * @description
  398. * trim polyfill
  399. *
  400. * @returns {string} The string stripped of whitespace from both ends
  401. */
  402. var trim = function() {
  403. return this.toString().replace(/^\s+|\s+$/g, '');
  404. };
  405. var negotiateLocale = function (preferred) {
  406. var avail = [],
  407. locale = angular.lowercase(preferred),
  408. i = 0,
  409. n = $availableLanguageKeys.length;
  410. for (; i < n; i++) {
  411. avail.push(angular.lowercase($availableLanguageKeys[i]));
  412. }
  413. if (indexOf(avail, locale) > -1) {
  414. return preferred;
  415. }
  416. if ($languageKeyAliases) {
  417. var alias;
  418. for (var langKeyAlias in $languageKeyAliases) {
  419. var hasWildcardKey = false;
  420. var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) &&
  421. angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
  422. if (langKeyAlias.slice(-1) === '*') {
  423. hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length-1);
  424. }
  425. if (hasExactKey || hasWildcardKey) {
  426. alias = $languageKeyAliases[langKeyAlias];
  427. if (indexOf(avail, angular.lowercase(alias)) > -1) {
  428. return alias;
  429. }
  430. }
  431. }
  432. }
  433. if (preferred) {
  434. var parts = preferred.split('_');
  435. if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) {
  436. return parts[0];
  437. }
  438. }
  439. // If everything fails, just return the preferred, unchanged.
  440. return preferred;
  441. };
  442. /**
  443. * @ngdoc function
  444. * @name translate.$translateProvider#translations
  445. * @methodOf translate.$translateProvider
  446. *
  447. * @description
  448. * Registers a new translation table for specific language key.
  449. *
  450. * To register a translation table for specific language, a defined language
  451. * key as first parameter.
  452. *
  453. * <pre>
  454. * // register translation table for language: 'de_DE'
  455. * $translateProvider.translations('de_DE', {
  456. * 'GREETING': 'Hallo Welt!'
  457. * });
  458. *
  459. * // register another one
  460. * $translateProvider.translations('en_US', {
  461. * 'GREETING': 'Hello world!'
  462. * });
  463. * </pre>
  464. *
  465. * When registering multiple translation tables for for the same language key,
  466. * the actual translation table gets extended. This allows you to define module
  467. * specific translation which only get added, once a specific module is loaded in
  468. * your app.
  469. *
  470. * Invoking this method with no arguments returns the translation table which was
  471. * registered with no language key. Invoking it with a language key returns the
  472. * related translation table.
  473. *
  474. * @param {string} key A language key.
  475. * @param {object} translationTable A plain old JavaScript object that represents a translation table.
  476. *
  477. */
  478. var translations = function (langKey, translationTable) {
  479. if (!langKey && !translationTable) {
  480. return $translationTable;
  481. }
  482. if (langKey && !translationTable) {
  483. if (angular.isString(langKey)) {
  484. return $translationTable[langKey];
  485. }
  486. } else {
  487. if (!angular.isObject($translationTable[langKey])) {
  488. $translationTable[langKey] = {};
  489. }
  490. angular.extend($translationTable[langKey], flatObject(translationTable));
  491. }
  492. return this;
  493. };
  494. this.translations = translations;
  495. /**
  496. * @ngdoc function
  497. * @name translate.$translateProvider#cloakClassName
  498. * @methodOf translate.$translateProvider
  499. *
  500. * @description
  501. *
  502. * Let's you change the class name for `translate-cloak` directive.
  503. * Default class name is `translate-cloak`.
  504. *
  505. * @param {string} name translate-cloak class name
  506. */
  507. this.cloakClassName = function (name) {
  508. if (!name) {
  509. return $cloakClassName;
  510. }
  511. $cloakClassName = name;
  512. return this;
  513. };
  514. /**
  515. * @name flatObject
  516. * @private
  517. *
  518. * @description
  519. * Flats an object. This function is used to flatten given translation data with
  520. * namespaces, so they are later accessible via dot notation.
  521. */
  522. var flatObject = function (data, path, result, prevKey) {
  523. var key, keyWithPath, keyWithShortPath, val;
  524. if (!path) {
  525. path = [];
  526. }
  527. if (!result) {
  528. result = {};
  529. }
  530. for (key in data) {
  531. if (!Object.prototype.hasOwnProperty.call(data, key)) {
  532. continue;
  533. }
  534. val = data[key];
  535. if (angular.isObject(val)) {
  536. flatObject(val, path.concat(key), result, key);
  537. } else {
  538. keyWithPath = path.length ? ('' + path.join(NESTED_OBJECT_DELIMITER) + NESTED_OBJECT_DELIMITER + key) : key;
  539. if(path.length && key === prevKey){
  540. // Create shortcut path (foo.bar == foo.bar.bar)
  541. keyWithShortPath = '' + path.join(NESTED_OBJECT_DELIMITER);
  542. // Link it to original path
  543. result[keyWithShortPath] = '@:' + keyWithPath;
  544. }
  545. result[keyWithPath] = val;
  546. }
  547. }
  548. return result;
  549. };
  550. flatObject.displayName = 'flatObject';
  551. /**
  552. * @ngdoc function
  553. * @name translate.$translateProvider#addInterpolation
  554. * @methodOf translate.$translateProvider
  555. *
  556. * @description
  557. * Adds interpolation services to angular-translate, so it can manage them.
  558. *
  559. * @param {object} factory Interpolation service factory
  560. */
  561. this.addInterpolation = function (factory) {
  562. $interpolatorFactories.push(factory);
  563. return this;
  564. };
  565. /**
  566. * @ngdoc function
  567. * @name translate.$translateProvider#useMessageFormatInterpolation
  568. * @methodOf translate.$translateProvider
  569. *
  570. * @description
  571. * Tells angular-translate to use interpolation functionality of messageformat.js.
  572. * This is useful when having high level pluralization and gender selection.
  573. */
  574. this.useMessageFormatInterpolation = function () {
  575. return this.useInterpolation('$translateMessageFormatInterpolation');
  576. };
  577. /**
  578. * @ngdoc function
  579. * @name translate.$translateProvider#useInterpolation
  580. * @methodOf translate.$translateProvider
  581. *
  582. * @description
  583. * Tells angular-translate which interpolation style to use as default, application-wide.
  584. * Simply a factory/service name. The interpolation service has to implement
  585. * the correct interface.
  586. *
  587. * @param {string} factory Interpolation service name.
  588. */
  589. this.useInterpolation = function (factory) {
  590. $interpolationFactory = factory;
  591. return this;
  592. };
  593. /**
  594. * @ngdoc function
  595. * @name translate.$translateProvider#useSanitizeStrategy
  596. * @methodOf translate.$translateProvider
  597. *
  598. * @description
  599. * Simply sets a sanitation strategy type.
  600. *
  601. * @param {string} value Strategy type.
  602. */
  603. this.useSanitizeValueStrategy = function (value) {
  604. $translateSanitizationProvider.useStrategy(value);
  605. return this;
  606. };
  607. /**
  608. * @ngdoc function
  609. * @name translate.$translateProvider#preferredLanguage
  610. * @methodOf translate.$translateProvider
  611. *
  612. * @description
  613. * Tells the module which of the registered translation tables to use for translation
  614. * at initial startup by ing a language key. Similar to `$translateProvider#use`
  615. * only that it says which language to **prefer**.
  616. *
  617. * @param {string} langKey A language key.
  618. *
  619. */
  620. this.preferredLanguage = function(langKey) {
  621. setupPreferredLanguage(langKey);
  622. return this;
  623. };
  624. var setupPreferredLanguage = function (langKey) {
  625. if (langKey) {
  626. $preferredLanguage = langKey;
  627. }
  628. return $preferredLanguage;
  629. };
  630. /**
  631. * @ngdoc function
  632. * @name translate.$translateProvider#translationNotFoundIndicator
  633. * @methodOf translate.$translateProvider
  634. *
  635. * @description
  636. * Sets an indicator which is used when a translation isn't found. E.g. when
  637. * setting the indicator as 'X' and one tries to translate a translation id
  638. * called `NOT_FOUND`, this will result in `X NOT_FOUND X`.
  639. *
  640. * Internally this methods sets a left indicator and a right indicator using
  641. * `$translateProvider.translationNotFoundIndicatorLeft()` and
  642. * `$translateProvider.translationNotFoundIndicatorRight()`.
  643. *
  644. * **Note**: These methods automatically add a whitespace between the indicators
  645. * and the translation id.
  646. *
  647. * @param {string} indicator An indicator, could be any string.
  648. */
  649. this.translationNotFoundIndicator = function (indicator) {
  650. this.translationNotFoundIndicatorLeft(indicator);
  651. this.translationNotFoundIndicatorRight(indicator);
  652. return this;
  653. };
  654. /**
  655. * ngdoc function
  656. * @name translate.$translateProvider#translationNotFoundIndicatorLeft
  657. * @methodOf translate.$translateProvider
  658. *
  659. * @description
  660. * Sets an indicator which is used when a translation isn't found left to the
  661. * translation id.
  662. *
  663. * @param {string} indicator An indicator.
  664. */
  665. this.translationNotFoundIndicatorLeft = function (indicator) {
  666. if (!indicator) {
  667. return $notFoundIndicatorLeft;
  668. }
  669. $notFoundIndicatorLeft = indicator;
  670. return this;
  671. };
  672. /**
  673. * ngdoc function
  674. * @name translate.$translateProvider#translationNotFoundIndicatorLeft
  675. * @methodOf translate.$translateProvider
  676. *
  677. * @description
  678. * Sets an indicator which is used when a translation isn't found right to the
  679. * translation id.
  680. *
  681. * @param {string} indicator An indicator.
  682. */
  683. this.translationNotFoundIndicatorRight = function (indicator) {
  684. if (!indicator) {
  685. return $notFoundIndicatorRight;
  686. }
  687. $notFoundIndicatorRight = indicator;
  688. return this;
  689. };
  690. /**
  691. * @ngdoc function
  692. * @name translate.$translateProvider#fallbackLanguage
  693. * @methodOf translate.$translateProvider
  694. *
  695. * @description
  696. * Tells the module which of the registered translation tables to use when missing translations
  697. * at initial startup by ing a language key. Similar to `$translateProvider#use`
  698. * only that it says which language to **fallback**.
  699. *
  700. * @param {string||array} langKey A language key.
  701. *
  702. */
  703. this.fallbackLanguage = function (langKey) {
  704. fallbackStack(langKey);
  705. return this;
  706. };
  707. var fallbackStack = function (langKey) {
  708. if (langKey) {
  709. if (angular.isString(langKey)) {
  710. $fallbackWasString = true;
  711. $fallbackLanguage = [ langKey ];
  712. } else if (angular.isArray(langKey)) {
  713. $fallbackWasString = false;
  714. $fallbackLanguage = langKey;
  715. }
  716. if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) {
  717. $fallbackLanguage.push($preferredLanguage);
  718. }
  719. return this;
  720. } else {
  721. if ($fallbackWasString) {
  722. return $fallbackLanguage[0];
  723. } else {
  724. return $fallbackLanguage;
  725. }
  726. }
  727. };
  728. /**
  729. * @ngdoc function
  730. * @name translate.$translateProvider#use
  731. * @methodOf translate.$translateProvider
  732. *
  733. * @description
  734. * Set which translation table to use for translation by given language key. When
  735. * trying to 'use' a language which isn't provided, it'll throw an error.
  736. *
  737. * You actually don't have to use this method since `$translateProvider#preferredLanguage`
  738. * does the job too.
  739. *
  740. * @param {string} langKey A language key.
  741. */
  742. this.use = function (langKey) {
  743. if (langKey) {
  744. if (!$translationTable[langKey] && (!$loaderFactory)) {
  745. // only throw an error, when not loading translation data asynchronously
  746. throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\'');
  747. }
  748. $uses = langKey;
  749. return this;
  750. }
  751. return $uses;
  752. };
  753. /**
  754. * @ngdoc function
  755. * @name translate.$translateProvider#storageKey
  756. * @methodOf translate.$translateProvider
  757. *
  758. * @description
  759. * Tells the module which key must represent the choosed language by a user in the storage.
  760. *
  761. * @param {string} key A key for the storage.
  762. */
  763. var storageKey = function(key) {
  764. if (!key) {
  765. if ($storagePrefix) {
  766. return $storagePrefix + $storageKey;
  767. }
  768. return $storageKey;
  769. }
  770. $storageKey = key;
  771. return this;
  772. };
  773. this.storageKey = storageKey;
  774. /**
  775. * @ngdoc function
  776. * @name translate.$translateProvider#useUrlLoader
  777. * @methodOf translate.$translateProvider
  778. *
  779. * @description
  780. * Tells angular-translate to use `$translateUrlLoader` extension service as loader.
  781. *
  782. * @param {string} url Url
  783. * @param {Object=} options Optional configuration object
  784. */
  785. this.useUrlLoader = function (url, options) {
  786. return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options));
  787. };
  788. /**
  789. * @ngdoc function
  790. * @name translate.$translateProvider#useStaticFilesLoader
  791. * @methodOf translate.$translateProvider
  792. *
  793. * @description
  794. * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader.
  795. *
  796. * @param {Object=} options Optional configuration object
  797. */
  798. this.useStaticFilesLoader = function (options) {
  799. return this.useLoader('$translateStaticFilesLoader', options);
  800. };
  801. /**
  802. * @ngdoc function
  803. * @name translate.$translateProvider#useLoader
  804. * @methodOf translate.$translateProvider
  805. *
  806. * @description
  807. * Tells angular-translate to use any other service as loader.
  808. *
  809. * @param {string} loaderFactory Factory name to use
  810. * @param {Object=} options Optional configuration object
  811. */
  812. this.useLoader = function (loaderFactory, options) {
  813. $loaderFactory = loaderFactory;
  814. $loaderOptions = options || {};
  815. return this;
  816. };
  817. /**
  818. * @ngdoc function
  819. * @name translate.$translateProvider#useLocalStorage
  820. * @methodOf translate.$translateProvider
  821. *
  822. * @description
  823. * Tells angular-translate to use `$translateLocalStorage` service as storage layer.
  824. *
  825. */
  826. this.useLocalStorage = function () {
  827. return this.useStorage('$translateLocalStorage');
  828. };
  829. /**
  830. * @ngdoc function
  831. * @name translate.$translateProvider#useCookieStorage
  832. * @methodOf translate.$translateProvider
  833. *
  834. * @description
  835. * Tells angular-translate to use `$translateCookieStorage` service as storage layer.
  836. */
  837. this.useCookieStorage = function () {
  838. return this.useStorage('$translateCookieStorage');
  839. };
  840. /**
  841. * @ngdoc function
  842. * @name translate.$translateProvider#useStorage
  843. * @methodOf translate.$translateProvider
  844. *
  845. * @description
  846. * Tells angular-translate to use custom service as storage layer.
  847. */
  848. this.useStorage = function (storageFactory) {
  849. $storageFactory = storageFactory;
  850. return this;
  851. };
  852. /**
  853. * @ngdoc function
  854. * @name translate.$translateProvider#storagePrefix
  855. * @methodOf translate.$translateProvider
  856. *
  857. * @description
  858. * Sets prefix for storage key.
  859. *
  860. * @param {string} prefix Storage key prefix
  861. */
  862. this.storagePrefix = function (prefix) {
  863. if (!prefix) {
  864. return prefix;
  865. }
  866. $storagePrefix = prefix;
  867. return this;
  868. };
  869. /**
  870. * @ngdoc function
  871. * @name translate.$translateProvider#useMissingTranslationHandlerLog
  872. * @methodOf translate.$translateProvider
  873. *
  874. * @description
  875. * Tells angular-translate to use built-in log handler when trying to translate
  876. * a translation Id which doesn't exist.
  877. *
  878. * This is actually a shortcut method for `useMissingTranslationHandler()`.
  879. *
  880. */
  881. this.useMissingTranslationHandlerLog = function () {
  882. return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
  883. };
  884. /**
  885. * @ngdoc function
  886. * @name translate.$translateProvider#useMissingTranslationHandler
  887. * @methodOf translate.$translateProvider
  888. *
  889. * @description
  890. * Expects a factory name which later gets instantiated with `$injector`.
  891. * This method can be used to tell angular-translate to use a custom
  892. * missingTranslationHandler. Just build a factory which returns a function
  893. * and expects a translation id as argument.
  894. *
  895. * Example:
  896. * <pre>
  897. * app.config(function ($translateProvider) {
  898. * $translateProvider.useMissingTranslationHandler('customHandler');
  899. * });
  900. *
  901. * app.factory('customHandler', function (dep1, dep2) {
  902. * return function (translationId) {
  903. * // something with translationId and dep1 and dep2
  904. * };
  905. * });
  906. * </pre>
  907. *
  908. * @param {string} factory Factory name
  909. */
  910. this.useMissingTranslationHandler = function (factory) {
  911. $missingTranslationHandlerFactory = factory;
  912. return this;
  913. };
  914. /**
  915. * @ngdoc function
  916. * @name translate.$translateProvider#usePostCompiling
  917. * @methodOf translate.$translateProvider
  918. *
  919. * @description
  920. * If post compiling is enabled, all translated values will be processed
  921. * again with AngularJS' $compile.
  922. *
  923. * Example:
  924. * <pre>
  925. * app.config(function ($translateProvider) {
  926. * $translateProvider.usePostCompiling(true);
  927. * });
  928. * </pre>
  929. *
  930. * @param {string} factory Factory name
  931. */
  932. this.usePostCompiling = function (value) {
  933. $postCompilingEnabled = !(!value);
  934. return this;
  935. };
  936. /**
  937. * @ngdoc function
  938. * @name translate.$translateProvider#forceAsyncReload
  939. * @methodOf translate.$translateProvider
  940. *
  941. * @description
  942. * If force async reload is enabled, async loader will always be called
  943. * even if $translationTable already contains the language key, adding
  944. * possible new entries to the $translationTable.
  945. *
  946. * Example:
  947. * <pre>
  948. * app.config(function ($translateProvider) {
  949. * $translateProvider.forceAsyncReload(true);
  950. * });
  951. * </pre>
  952. *
  953. * @param {boolean} value - valid values are true or false
  954. */
  955. this.forceAsyncReload = function (value) {
  956. $forceAsyncReloadEnabled = !(!value);
  957. return this;
  958. };
  959. /**
  960. * @ngdoc function
  961. * @name translate.$translateProvider#uniformLanguageTag
  962. * @methodOf translate.$translateProvider
  963. *
  964. * @description
  965. * Tells angular-translate which language tag should be used as a result when determining
  966. * the current browser language.
  967. *
  968. * This setting must be set before invoking {@link translate.$translateProvider#methods_determinePreferredLanguage determinePreferredLanguage()}.
  969. *
  970. * <pre>
  971. * $translateProvider
  972. * .uniformLanguageTag('bcp47')
  973. * .determinePreferredLanguage()
  974. * </pre>
  975. *
  976. * The resolver currently supports:
  977. * * default
  978. * (traditionally: hyphens will be converted into underscores, i.e. en-US => en_US)
  979. * en-US => en_US
  980. * en_US => en_US
  981. * en-us => en_us
  982. * * java
  983. * like default, but the second part will be always in uppercase
  984. * en-US => en_US
  985. * en_US => en_US
  986. * en-us => en_US
  987. * * BCP 47 (RFC 4646 & 4647)
  988. * en-US => en-US
  989. * en_US => en-US
  990. * en-us => en-US
  991. *
  992. * See also:
  993. * * http://en.wikipedia.org/wiki/IETF_language_tag
  994. * * http://www.w3.org/International/core/langtags/
  995. * * http://tools.ietf.org/html/bcp47
  996. *
  997. * @param {string|object} options - options (or standard)
  998. * @param {string} options.standard - valid values are 'default', 'bcp47', 'java'
  999. */
  1000. this.uniformLanguageTag = function (options) {
  1001. if (!options) {
  1002. options = {};
  1003. } else if (angular.isString(options)) {
  1004. options = {
  1005. standard: options
  1006. };
  1007. }
  1008. uniformLanguageTagResolver = options.standard;
  1009. return this;
  1010. };
  1011. /**
  1012. * @ngdoc function
  1013. * @name translate.$translateProvider#determinePreferredLanguage
  1014. * @methodOf translate.$translateProvider
  1015. *
  1016. * @description
  1017. * Tells angular-translate to try to determine on its own which language key
  1018. * to set as preferred language. When `fn` is given, angular-translate uses it
  1019. * to determine a language key, otherwise it uses the built-in `getLocale()`
  1020. * method.
  1021. *
  1022. * The `getLocale()` returns a language key in the format `[lang]_[country]` or
  1023. * `[lang]` depending on what the browser provides.
  1024. *
  1025. * Use this method at your own risk, since not all browsers return a valid
  1026. * locale (see {@link translate.$translateProvider#methods_uniformLanguageTag uniformLanguageTag()}).
  1027. *
  1028. * @param {Function=} fn Function to determine a browser's locale
  1029. */
  1030. this.determinePreferredLanguage = function (fn) {
  1031. var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale();
  1032. if (!$availableLanguageKeys.length) {
  1033. $preferredLanguage = locale;
  1034. } else {
  1035. $preferredLanguage = negotiateLocale(locale);
  1036. }
  1037. return this;
  1038. };
  1039. /**
  1040. * @ngdoc function
  1041. * @name translate.$translateProvider#registerAvailableLanguageKeys
  1042. * @methodOf translate.$translateProvider
  1043. *
  1044. * @description
  1045. * Registers a set of language keys the app will work with. Use this method in
  1046. * combination with
  1047. * {@link translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
  1048. * When available languages keys are registered, angular-translate
  1049. * tries to find the best fitting language key depending on the browsers locale,
  1050. * considering your language key convention.
  1051. *
  1052. * @param {object} languageKeys Array of language keys the your app will use
  1053. * @param {object=} aliases Alias map.
  1054. */
  1055. this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
  1056. if (languageKeys) {
  1057. $availableLanguageKeys = languageKeys;
  1058. if (aliases) {
  1059. $languageKeyAliases = aliases;
  1060. }
  1061. return this;
  1062. }
  1063. return $availableLanguageKeys;
  1064. };
  1065. /**
  1066. * @ngdoc function
  1067. * @name translate.$translateProvider#useLoaderCache
  1068. * @methodOf translate.$translateProvider
  1069. *
  1070. * @description
  1071. * Registers a cache for internal $http based loaders.
  1072. * {@link translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
  1073. * When false the cache will be disabled (default). When true or undefined
  1074. * the cache will be a default (see $cacheFactory). When an object it will
  1075. * be treat as a cache object itself: the usage is $http({cache: cache})
  1076. *
  1077. * @param {object} cache boolean, string or cache-object
  1078. */
  1079. this.useLoaderCache = function (cache) {
  1080. if (cache === false) {
  1081. // disable cache
  1082. loaderCache = undefined;
  1083. } else if (cache === true) {
  1084. // enable cache using AJS defaults
  1085. loaderCache = true;
  1086. } else if (typeof(cache) === 'undefined') {
  1087. // enable cache using default
  1088. loaderCache = '$translationCache';
  1089. } else if (cache) {
  1090. // enable cache using given one (see $cacheFactory)
  1091. loaderCache = cache;
  1092. }
  1093. return this;
  1094. };
  1095. /**
  1096. * @ngdoc function
  1097. * @name translate.$translateProvider#directivePriority
  1098. * @methodOf translate.$translateProvider
  1099. *
  1100. * @description
  1101. * Sets the default priority of the translate directive. The standard value is `0`.
  1102. * Calling this function without an argument will return the current value.
  1103. *
  1104. * @param {number} priority for the translate-directive
  1105. */
  1106. this.directivePriority = function (priority) {
  1107. if (priority === undefined) {
  1108. // getter
  1109. return directivePriority;
  1110. } else {
  1111. // setter with chaining
  1112. directivePriority = priority;
  1113. return this;
  1114. }
  1115. };
  1116. /**
  1117. * @ngdoc function
  1118. * @name translate.$translateProvider#statefulFilter
  1119. * @methodOf translate.$translateProvider
  1120. *
  1121. * @description
  1122. * Since AngularJS 1.3, filters which are not stateless (depending at the scope)
  1123. * have to explicit define this behavior.
  1124. * Sets whether the translate filter should be stateful or stateless. The standard value is `true`
  1125. * meaning being stateful.
  1126. * Calling this function without an argument will return the current value.
  1127. *
  1128. * @param {boolean} state - defines the state of the filter
  1129. */
  1130. this.statefulFilter = function (state) {
  1131. if (state === undefined) {
  1132. // getter
  1133. return statefulFilter;
  1134. } else {
  1135. // setter with chaining
  1136. statefulFilter = state;
  1137. return this;
  1138. }
  1139. };
  1140. /**
  1141. * @ngdoc object
  1142. * @name translate.$translate
  1143. * @requires $interpolate
  1144. * @requires $log
  1145. * @requires $rootScope
  1146. * @requires $q
  1147. *
  1148. * @description
  1149. * The `$translate` service is the actual core of angular-translate. It expects a translation id
  1150. * and optional interpolate parameters to translate contents.
  1151. *
  1152. * <pre>
  1153. * $translate('HEADLINE_TEXT').then(function (translation) {
  1154. * $scope.translatedText = translation;
  1155. * });
  1156. * </pre>
  1157. *
  1158. * @param {string|array} translationId A token which represents a translation id
  1159. * This can be optionally an array of translation ids which
  1160. * results that the function returns an object where each key
  1161. * is the translation id and the value the translation.
  1162. * @param {object=} interpolateParams An object hash for dynamic values
  1163. * @param {string} interpolationId The id of the interpolation to use
  1164. * @returns {object} promise
  1165. */
  1166. this.$get = [
  1167. '$log',
  1168. '$injector',
  1169. '$rootScope',
  1170. '$q',
  1171. function ($log, $injector, $rootScope, $q) {
  1172. var Storage,
  1173. defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
  1174. pendingLoader = false,
  1175. interpolatorHashMap = {},
  1176. langPromises = {},
  1177. fallbackIndex,
  1178. startFallbackIteration;
  1179. var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {
  1180. // Duck detection: If the first argument is an array, a bunch of translations was requested.
  1181. // The result is an object.
  1182. if (angular.isArray(translationId)) {
  1183. // Inspired by Q.allSettled by Kris Kowal
  1184. // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
  1185. // This transforms all promises regardless resolved or rejected
  1186. var translateAll = function (translationIds) {
  1187. var results = {}; // storing the actual results
  1188. var promises = []; // promises to wait for
  1189. // Wraps the promise a) being always resolved and b) storing the link id->value
  1190. var translate = function (translationId) {
  1191. var deferred = $q.defer();
  1192. var regardless = function (value) {
  1193. results[translationId] = value;
  1194. deferred.resolve([translationId, value]);
  1195. };
  1196. // we don't care whether the promise was resolved or rejected; just store the values
  1197. $translate(translationId, interpolateParams, interpolationId, defaultTranslationText).then(regardless, regardless);
  1198. return deferred.promise;
  1199. };
  1200. for (var i = 0, c = translationIds.length; i < c; i++) {
  1201. promises.push(translate(translationIds[i]));
  1202. }
  1203. // wait for all (including storing to results)
  1204. return $q.all(promises).then(function () {
  1205. // return the results
  1206. return results;
  1207. });
  1208. };
  1209. return translateAll(translationId);
  1210. }
  1211. var deferred = $q.defer();
  1212. // trim off any whitespace
  1213. if (translationId) {
  1214. translationId = trim.apply(translationId);
  1215. }
  1216. var promiseToWaitFor = (function () {
  1217. var promise = $preferredLanguage ?
  1218. langPromises[$preferredLanguage] :
  1219. langPromises[$uses];
  1220. fallbackIndex = 0;
  1221. if ($storageFactory && !promise) {
  1222. // looks like there's no pending promise for $preferredLanguage or
  1223. // $uses. Maybe there's one pending for a language that comes from
  1224. // storage.
  1225. var langKey = Storage.get($storageKey);
  1226. promise = langPromises[langKey];
  1227. if ($fallbackLanguage && $fallbackLanguage.length) {
  1228. var index = indexOf($fallbackLanguage, langKey);
  1229. // maybe the language from storage is also defined as fallback language
  1230. // we increase the fallback language index to not search in that language
  1231. // as fallback, since it's probably the first used language
  1232. // in that case the index starts after the first element
  1233. fallbackIndex = (index === 0) ? 1 : 0;
  1234. // but we can make sure to ALWAYS fallback to preferred language at least
  1235. if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
  1236. $fallbackLanguage.push($preferredLanguage);
  1237. }
  1238. }
  1239. }
  1240. return promise;
  1241. }());
  1242. if (!promiseToWaitFor) {
  1243. // no promise to wait for? okay. Then there's no loader registered
  1244. // nor is a one pending for language that comes from storage.
  1245. // We can just translate.
  1246. determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
  1247. } else {
  1248. var promiseResolved = function () {
  1249. determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
  1250. };
  1251. promiseResolved.displayName = 'promiseResolved';
  1252. promiseToWaitFor['finally'](promiseResolved, deferred.reject);
  1253. }
  1254. return deferred.promise;
  1255. };
  1256. /**
  1257. * @name applyNotFoundIndicators
  1258. * @private
  1259. *
  1260. * @description
  1261. * Applies not fount indicators to given translation id, if needed.
  1262. * This function gets only executed, if a translation id doesn't exist,
  1263. * which is why a translation id is expected as argument.
  1264. *
  1265. * @param {string} translationId Translation id.
  1266. * @returns {string} Same as given translation id but applied with not found
  1267. * indicators.
  1268. */
  1269. var applyNotFoundIndicators = function (translationId) {
  1270. // applying notFoundIndicators
  1271. if ($notFoundIndicatorLeft) {
  1272. translationId = [$notFoundIndicatorLeft, translationId].join(' ');
  1273. }
  1274. if ($notFoundIndicatorRight) {
  1275. translationId = [translationId, $notFoundIndicatorRight].join(' ');
  1276. }
  1277. return translationId;
  1278. };
  1279. /**
  1280. * @name useLanguage
  1281. * @private
  1282. *
  1283. * @description
  1284. * Makes actual use of a language by setting a given language key as used
  1285. * language and informs registered interpolators to also use the given
  1286. * key as locale.
  1287. *
  1288. * @param {key} Locale key.
  1289. */
  1290. var useLanguage = function (key) {
  1291. $uses = key;
  1292. $rootScope.$emit('$translateChangeSuccess', {language: key});
  1293. if ($storageFactory) {
  1294. Storage.put($translate.storageKey(), $uses);
  1295. }
  1296. // inform default interpolator
  1297. defaultInterpolator.setLocale($uses);
  1298. var eachInterpolator = function (interpolator, id) {
  1299. interpolatorHashMap[id].setLocale($uses);
  1300. };
  1301. eachInterpolator.displayName = 'eachInterpolatorLocaleSetter';
  1302. // inform all others too!
  1303. angular.forEach(interpolatorHashMap, eachInterpolator);
  1304. $rootScope.$emit('$translateChangeEnd', {language: key});
  1305. };
  1306. /**
  1307. * @name loadAsync
  1308. * @private
  1309. *
  1310. * @description
  1311. * Kicks of registered async loader using `$injector` and applies existing
  1312. * loader options. When resolved, it updates translation tables accordingly
  1313. * or rejects with given language key.
  1314. *
  1315. * @param {string} key Language key.
  1316. * @return {Promise} A promise.
  1317. */
  1318. var loadAsync = function (key) {
  1319. if (!key) {
  1320. throw 'No language key specified for loading.';
  1321. }
  1322. var deferred = $q.defer();
  1323. $rootScope.$emit('$translateLoadingStart', {language: key});
  1324. pendingLoader = true;
  1325. var cache = loaderCache;
  1326. if (typeof(cache) === 'string') {
  1327. // getting on-demand instance of loader
  1328. cache = $injector.get(cache);
  1329. }
  1330. var loaderOptions = angular.extend({}, $loaderOptions, {
  1331. key: key,
  1332. $http: angular.extend({}, {
  1333. cache: cache
  1334. }, $loaderOptions.$http)
  1335. });
  1336. var onLoaderSuccess = function (data) {
  1337. var translationTable = {};
  1338. $rootScope.$emit('$translateLoadingSuccess', {language: key});
  1339. if (angular.isArray(data)) {
  1340. angular.forEach(data, function (table) {
  1341. angular.extend(translationTable, flatObject(table));
  1342. });
  1343. } else {
  1344. angular.extend(translationTable, flatObject(data));
  1345. }
  1346. pendingLoader = false;
  1347. deferred.resolve({
  1348. key: key,
  1349. table: translationTable
  1350. });
  1351. $rootScope.$emit('$translateLoadingEnd', {language: key});
  1352. };
  1353. onLoaderSuccess.displayName = 'onLoaderSuccess';
  1354. var onLoaderError = function (key) {
  1355. $rootScope.$emit('$translateLoadingError', {language: key});
  1356. deferred.reject(key);
  1357. $rootScope.$emit('$translateLoadingEnd', {language: key});
  1358. };
  1359. onLoaderError.displayName = 'onLoaderError';
  1360. $injector.get($loaderFactory)(loaderOptions)
  1361. .then(onLoaderSuccess, onLoaderError);
  1362. return deferred.promise;
  1363. };
  1364. if ($storageFactory) {
  1365. Storage = $injector.get($storageFactory);
  1366. if (!Storage.get || !Storage.put) {
  1367. throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!');
  1368. }
  1369. }
  1370. // if we have additional interpolations that were added via
  1371. // $translateProvider.addInterpolation(), we have to map'em
  1372. if ($interpolatorFactories.length) {
  1373. var eachInterpolationFactory = function (interpolatorFactory) {
  1374. var interpolator = $injector.get(interpolatorFactory);
  1375. // setting initial locale for each interpolation service
  1376. interpolator.setLocale($preferredLanguage || $uses);
  1377. // make'em recognizable through id
  1378. interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
  1379. };
  1380. eachInterpolationFactory.displayName = 'interpolationFactoryAdder';
  1381. angular.forEach($interpolatorFactories, eachInterpolationFactory);
  1382. }
  1383. /**
  1384. * @name getTranslationTable
  1385. * @private
  1386. *
  1387. * @description
  1388. * Returns a promise that resolves to the translation table
  1389. * or is rejected if an error occurred.
  1390. *
  1391. * @param langKey
  1392. * @returns {Q.promise}
  1393. */
  1394. var getTranslationTable = function (langKey) {
  1395. var deferred = $q.defer();
  1396. if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) {
  1397. deferred.resolve($translationTable[langKey]);
  1398. } else if (langPromises[langKey]) {
  1399. var onResolve = function (data) {
  1400. translations(data.key, data.table);
  1401. deferred.resolve(data.table);
  1402. };
  1403. onResolve.displayName = 'translationTableResolver';
  1404. langPromises[langKey].then(onResolve, deferred.reject);
  1405. } else {
  1406. deferred.reject();
  1407. }
  1408. return deferred.promise;
  1409. };
  1410. /**
  1411. * @name getFallbackTranslation
  1412. * @private
  1413. *
  1414. * @description
  1415. * Returns a promise that will resolve to the translation
  1416. * or be rejected if no translation was found for the language.
  1417. * This function is currently only used for fallback language translation.
  1418. *
  1419. * @param langKey The language to translate to.
  1420. * @param translationId
  1421. * @param interpolateParams
  1422. * @param Interpolator
  1423. * @returns {Q.promise}
  1424. */
  1425. var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) {
  1426. var deferred = $q.defer();
  1427. var onResolve = function (translationTable) {
  1428. if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
  1429. Interpolator.setLocale(langKey);
  1430. var translation = translationTable[translationId];
  1431. if (translation.substr(0, 2) === '@:') {
  1432. getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator)
  1433. .then(deferred.resolve, deferred.reject);
  1434. } else {
  1435. deferred.resolve(Interpolator.interpolate(translationTable[translationId], interpolateParams));
  1436. }
  1437. Interpolator.setLocale($uses);
  1438. } else {
  1439. deferred.reject();
  1440. }
  1441. };
  1442. onResolve.displayName = 'fallbackTranslationResolver';
  1443. getTranslationTable(langKey).then(onResolve, deferred.reject);
  1444. return deferred.promise;
  1445. };
  1446. /**
  1447. * @name getFallbackTranslationInstant
  1448. * @private
  1449. *
  1450. * @description
  1451. * Returns a translation
  1452. * This function is currently only used for fallback language translation.
  1453. *
  1454. * @param langKey The language to translate to.
  1455. * @param translationId
  1456. * @param interpolateParams
  1457. * @param Interpolator
  1458. * @returns {string} translation
  1459. */
  1460. var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
  1461. var result, translationTable = $translationTable[langKey];
  1462. if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
  1463. Interpolator.setLocale(langKey);
  1464. result = Interpolator.interpolate(translationTable[translationId], interpolateParams);
  1465. if (result.substr(0, 2) === '@:') {
  1466. return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator);
  1467. }
  1468. Interpolator.setLocale($uses);
  1469. }
  1470. return result;
  1471. };
  1472. /**
  1473. * @name translateByHandler
  1474. * @private
  1475. *
  1476. * Translate by missing translation handler.
  1477. *
  1478. * @param translationId
  1479. * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
  1480. * absent
  1481. */
  1482. var translateByHandler = function (translationId, interpolateParams) {
  1483. // If we have a handler factory - we might also call it here to determine if it provides
  1484. // a default text for a translationid that can't be found anywhere in our tables
  1485. if ($missingTranslationHandlerFactory) {
  1486. var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams);
  1487. if (resultString !== undefined) {
  1488. return resultString;
  1489. } else {
  1490. return translationId;
  1491. }
  1492. } else {
  1493. return translationId;
  1494. }
  1495. };
  1496. /**
  1497. * @name resolveForFallbackLanguage
  1498. * @private
  1499. *
  1500. * Recursive helper function for fallbackTranslation that will sequentially look
  1501. * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
  1502. *
  1503. * @param fallbackLanguageIndex
  1504. * @param translationId
  1505. * @param interpolateParams
  1506. * @param Interpolator
  1507. * @returns {Q.promise} Promise that will resolve to the translation.
  1508. */
  1509. var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) {
  1510. var deferred = $q.defer();
  1511. if (fallbackLanguageIndex < $fallbackLanguage.length) {
  1512. var langKey = $fallbackLanguage[fallbackLanguageIndex];
  1513. getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then(
  1514. deferred.resolve,
  1515. function () {
  1516. // Look in the next fallback language for a translation.
  1517. // It delays the resolving by ing another promise to resolve.
  1518. resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText).then(deferred.resolve);
  1519. }
  1520. );
  1521. } else {
  1522. // No translation found in any fallback language
  1523. // if a default translation text is set in the directive, then return this as a result
  1524. if (defaultTranslationText) {
  1525. deferred.resolve(defaultTranslationText);
  1526. } else {
  1527. // if no default translation is set and an error handler is defined, send it to the handler
  1528. // and then return the result
  1529. deferred.resolve(translateByHandler(translationId, interpolateParams));
  1530. }
  1531. }
  1532. return deferred.promise;
  1533. };
  1534. /**
  1535. * @name resolveForFallbackLanguageInstant
  1536. * @private
  1537. *
  1538. * Recursive helper function for fallbackTranslation that will sequentially look
  1539. * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
  1540. *
  1541. * @param fallbackLanguageIndex
  1542. * @param translationId
  1543. * @param interpolateParams
  1544. * @param Interpolator
  1545. * @returns {string} translation
  1546. */
  1547. var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
  1548. var result;
  1549. if (fallbackLanguageIndex < $fallbackLanguage.length) {
  1550. var langKey = $fallbackLanguage[fallbackLanguageIndex];
  1551. result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator);
  1552. if (!result) {
  1553. result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
  1554. }
  1555. }
  1556. return result;
  1557. };
  1558. /**
  1559. * Translates with the usage of the fallback languages.
  1560. *
  1561. * @param translationId
  1562. * @param interpolateParams
  1563. * @param Interpolator
  1564. * @returns {Q.promise} Promise, that resolves to the translation.
  1565. */
  1566. var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) {
  1567. // Start with the fallbackLanguage with index 0
  1568. return resolveForFallbackLanguage((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText);
  1569. };
  1570. /**
  1571. * Translates with the usage of the fallback languages.
  1572. *
  1573. * @param translationId
  1574. * @param interpolateParams
  1575. * @param Interpolator
  1576. * @returns {String} translation
  1577. */
  1578. var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) {
  1579. // Start with the fallbackLanguage with index 0
  1580. return resolveForFallbackLanguageInstant((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator);
  1581. };
  1582. var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {
  1583. var deferred = $q.defer();
  1584. var table = $uses ? $translationTable[$uses] : $translationTable,
  1585. Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;
  1586. // if the translation id exists, we can just interpolate it
  1587. if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
  1588. var translation = table[translationId];
  1589. // If using link, rerun $translate with linked translationId and return it
  1590. if (translation.substr(0, 2) === '@:') {
  1591. $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText)
  1592. .then(deferred.resolve, deferred.reject);
  1593. } else {
  1594. deferred.resolve(Interpolator.interpolate(translation, interpolateParams));
  1595. }
  1596. } else {
  1597. var missingTranslationHandlerTranslation;
  1598. // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
  1599. if ($missingTranslationHandlerFactory && !pendingLoader) {
  1600. missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams);
  1601. }
  1602. // since we couldn't translate the inital requested translation id,
  1603. // we try it now with one or more fallback languages, if fallback language(s) is
  1604. // configured.
  1605. if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
  1606. fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText)
  1607. .then(function (translation) {
  1608. deferred.resolve(translation);
  1609. }, function (_translationId) {
  1610. deferred.reject(applyNotFoundIndicators(_translationId));
  1611. });
  1612. } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
  1613. // looks like the requested translation id doesn't exists.
  1614. // Now, if there is a registered handler for missing translations and no
  1615. // asyncLoader is pending, we execute the handler
  1616. if (defaultTranslationText) {
  1617. deferred.resolve(defaultTranslationText);
  1618. } else {
  1619. deferred.resolve(missingTranslationHandlerTranslation);
  1620. }
  1621. } else {
  1622. if (defaultTranslationText) {
  1623. deferred.resolve(defaultTranslationText);
  1624. } else {
  1625. deferred.reject(applyNotFoundIndicators(translationId));
  1626. }
  1627. }
  1628. }
  1629. return deferred.promise;
  1630. };
  1631. var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) {
  1632. var result, table = $uses ? $translationTable[$uses] : $translationTable,
  1633. Interpolator = defaultInterpolator;
  1634. // if the interpolation id exists use custom interpolator
  1635. if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
  1636. Interpolator = interpolatorHashMap[interpolationId];
  1637. }
  1638. // if the translation id exists, we can just interpolate it
  1639. if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
  1640. var translation = table[translationId];
  1641. // If using link, rerun $translate with linked translationId and return it
  1642. if (translation.substr(0, 2) === '@:') {
  1643. result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId);
  1644. } else {
  1645. result = Interpolator.interpolate(translation, interpolateParams);
  1646. }
  1647. } else {
  1648. var missingTranslationHandlerTranslation;
  1649. // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
  1650. if ($missingTranslationHandlerFactory && !pendingLoader) {
  1651. missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams);
  1652. }
  1653. // since we couldn't translate the inital requested translation id,
  1654. // we try it now with one or more fallback languages, if fallback language(s) is
  1655. // configured.
  1656. if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
  1657. fallbackIndex = 0;
  1658. result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator);
  1659. } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
  1660. // looks like the requested translation id doesn't exists.
  1661. // Now, if there is a registered handler for missing translations and no
  1662. // asyncLoader is pending, we execute the handler
  1663. result = missingTranslationHandlerTranslation;
  1664. } else {
  1665. result = applyNotFoundIndicators(translationId);
  1666. }
  1667. }
  1668. return result;
  1669. };
  1670. var clearNextLangAndPromise = function(key) {
  1671. if ($nextLang === key) {
  1672. $nextLang = undefined;
  1673. }
  1674. langPromises[key] = undefined;
  1675. };
  1676. /**
  1677. * @ngdoc function
  1678. * @name translate.$translate#preferredLanguage
  1679. * @methodOf translate.$translate
  1680. *
  1681. * @description
  1682. * Returns the language key for the preferred language.
  1683. *
  1684. * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime)
  1685. *
  1686. * @return {string} preferred language key
  1687. */
  1688. $translate.preferredLanguage = function (langKey) {
  1689. if(langKey) {
  1690. setupPreferredLanguage(langKey);
  1691. }
  1692. return $preferredLanguage;
  1693. };
  1694. /**
  1695. * @ngdoc function
  1696. * @name translate.$translate#cloakClassName
  1697. * @methodOf translate.$translate
  1698. *
  1699. * @description
  1700. * Returns the configured class name for `translate-cloak` directive.
  1701. *
  1702. * @return {string} cloakClassName
  1703. */
  1704. $translate.cloakClassName = function () {
  1705. return $cloakClassName;
  1706. };
  1707. /**
  1708. * @ngdoc function
  1709. * @name translate.$translate#fallbackLanguage
  1710. * @methodOf translate.$translate
  1711. *
  1712. * @description
  1713. * Returns the language key for the fallback languages or sets a new fallback stack.
  1714. *
  1715. * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime)
  1716. *
  1717. * @return {string||array} fallback language key
  1718. */
  1719. $translate.fallbackLanguage = function (langKey) {
  1720. if (langKey !== undefined && langKey !== null) {
  1721. fallbackStack(langKey);
  1722. // as we might have an async loader initiated and a new translation language might have been defined
  1723. // we need to add the promise to the stack also. So - iterate.
  1724. if ($loaderFactory) {
  1725. if ($fallbackLanguage && $fallbackLanguage.length) {
  1726. for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
  1727. if (!langPromises[$fallbackLanguage[i]]) {
  1728. langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
  1729. }
  1730. }
  1731. }
  1732. }
  1733. $translate.use($translate.use());
  1734. }
  1735. if ($fallbackWasString) {
  1736. return $fallbackLanguage[0];
  1737. } else {
  1738. return $fallbackLanguage;
  1739. }
  1740. };
  1741. /**
  1742. * @ngdoc function
  1743. * @name translate.$translate#useFallbackLanguage
  1744. * @methodOf translate.$translate
  1745. *
  1746. * @description
  1747. * Sets the first key of the fallback language stack to be used for translation.
  1748. * Therefore all languages in the fallback array BEFORE this key will be skipped!
  1749. *
  1750. * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to
  1751. * get back to the whole stack
  1752. */
  1753. $translate.useFallbackLanguage = function (langKey) {
  1754. if (langKey !== undefined && langKey !== null) {
  1755. if (!langKey) {
  1756. startFallbackIteration = 0;
  1757. } else {
  1758. var langKeyPosition = indexOf($fallbackLanguage, langKey);
  1759. if (langKeyPosition > -1) {
  1760. startFallbackIteration = langKeyPosition;
  1761. }
  1762. }
  1763. }
  1764. };
  1765. /**
  1766. * @ngdoc function
  1767. * @name translate.$translate#proposedLanguage
  1768. * @methodOf translate.$translate
  1769. *
  1770. * @description
  1771. * Returns the language key of language that is currently loaded asynchronously.
  1772. *
  1773. * @return {string} language key
  1774. */
  1775. $translate.proposedLanguage = function () {
  1776. return $nextLang;
  1777. };
  1778. /**
  1779. * @ngdoc function
  1780. * @name translate.$translate#storage
  1781. * @methodOf translate.$translate
  1782. *
  1783. * @description
  1784. * Returns registered storage.
  1785. *
  1786. * @return {object} Storage
  1787. */
  1788. $translate.storage = function () {
  1789. return Storage;
  1790. };
  1791. /**
  1792. * @ngdoc function
  1793. * @name translate.$translate#use
  1794. * @methodOf translate.$translate
  1795. *
  1796. * @description
  1797. * Tells angular-translate which language to use by given language key. This method is
  1798. * used to change language at runtime. It also takes care of storing the language
  1799. * key in a configured store to let your app remember the choosed language.
  1800. *
  1801. * When trying to 'use' a language which isn't available it tries to load it
  1802. * asynchronously with registered loaders.
  1803. *
  1804. * Returns promise object with loaded language file data
  1805. * @example
  1806. * $translate.use("en_US").then(function(data){
  1807. * $scope.text = $translate("HELLO");
  1808. * });
  1809. *
  1810. * @param {string} key Language key
  1811. * @return {string} Language key
  1812. */
  1813. $translate.use = function (key) {
  1814. if (!key) {
  1815. return $uses;
  1816. }
  1817. var deferred = $q.defer();
  1818. $rootScope.$emit('$translateChangeStart', {language: key});
  1819. // Try to get the aliased language key
  1820. var aliasedKey = negotiateLocale(key);
  1821. if (aliasedKey) {
  1822. key = aliasedKey;
  1823. }
  1824. // if there isn't a translation table for the language we've requested,
  1825. // we load it asynchronously
  1826. if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) {
  1827. $nextLang = key;
  1828. langPromises[key] = loadAsync(key).then(function (translation) {
  1829. translations(translation.key, translation.table);
  1830. deferred.resolve(translation.key);
  1831. useLanguage(translation.key);
  1832. return translation;
  1833. }, function (key) {
  1834. $rootScope.$emit('$translateChangeError', {language: key});
  1835. deferred.reject(key);
  1836. $rootScope.$emit('$translateChangeEnd', {language: key});
  1837. return $q.reject(key);
  1838. });
  1839. langPromises[key]['finally'](function () {
  1840. clearNextLangAndPromise(key);
  1841. });
  1842. } else if ($nextLang === key && langPromises[key]) {
  1843. // we are already loading this asynchronously
  1844. // resolve our new deferred when the old langPromise is resolved
  1845. langPromises[key].then(function (translation) {
  1846. deferred.resolve(translation.key);
  1847. return translation;
  1848. }, function (key) {
  1849. deferred.reject(key);
  1850. return $q.reject(key);
  1851. });
  1852. } else {
  1853. deferred.resolve(key);
  1854. useLanguage(key);
  1855. }
  1856. return deferred.promise;
  1857. };
  1858. /**
  1859. * @ngdoc function
  1860. * @name translate.$translate#storageKey
  1861. * @methodOf translate.$translate
  1862. *
  1863. * @description
  1864. * Returns the key for the storage.
  1865. *
  1866. * @return {string} storage key
  1867. */
  1868. $translate.storageKey = function () {
  1869. return storageKey();
  1870. };
  1871. /**
  1872. * @ngdoc function
  1873. * @name translate.$translate#isPostCompilingEnabled
  1874. * @methodOf translate.$translate
  1875. *
  1876. * @description
  1877. * Returns whether post compiling is enabled or not
  1878. *
  1879. * @return {bool} storage key
  1880. */
  1881. $translate.isPostCompilingEnabled = function () {
  1882. return $postCompilingEnabled;
  1883. };
  1884. /**
  1885. * @ngdoc function
  1886. * @name translate.$translate#isForceAsyncReloadEnabled
  1887. * @methodOf translate.$translate
  1888. *
  1889. * @description
  1890. * Returns whether force async reload is enabled or not
  1891. *
  1892. * @return {boolean} forceAsyncReload value
  1893. */
  1894. $translate.isForceAsyncReloadEnabled = function () {
  1895. return $forceAsyncReloadEnabled;
  1896. };
  1897. /**
  1898. * @ngdoc function
  1899. * @name translate.$translate#refresh
  1900. * @methodOf translate.$translate
  1901. *
  1902. * @description
  1903. * Refreshes a translation table pointed by the given langKey. If langKey is not specified,
  1904. * the module will drop all existent translation tables and load new version of those which
  1905. * are currently in use.
  1906. *
  1907. * Refresh means that the module will drop target translation table and try to load it again.
  1908. *
  1909. * In case there are no loaders registered the refresh() method will throw an Error.
  1910. *
  1911. * If the module is able to refresh translation tables refresh() method will broadcast
  1912. * $translateRefreshStart and $translateRefreshEnd events.
  1913. *
  1914. * @example
  1915. * // this will drop all currently existent translation tables and reload those which are
  1916. * // currently in use
  1917. * $translate.refresh();
  1918. * // this will refresh a translation table for the en_US language
  1919. * $translate.refresh('en_US');
  1920. *
  1921. * @param {string} langKey A language key of the table, which has to be refreshed
  1922. *
  1923. * @return {promise} Promise, which will be resolved in case a translation tables refreshing
  1924. * process is finished successfully, and reject if not.
  1925. */
  1926. $translate.refresh = function (langKey) {
  1927. if (!$loaderFactory) {
  1928. throw new Error('Couldn\'t refresh translation table, no loader registered!');
  1929. }
  1930. var deferred = $q.defer();
  1931. function resolve() {
  1932. deferred.resolve();
  1933. $rootScope.$emit('$translateRefreshEnd', {language: langKey});
  1934. }
  1935. function reject() {
  1936. deferred.reject();
  1937. $rootScope.$emit('$translateRefreshEnd', {language: langKey});
  1938. }
  1939. $rootScope.$emit('$translateRefreshStart', {language: langKey});
  1940. if (!langKey) {
  1941. // if there's no language key specified we refresh ALL THE THINGS!
  1942. var tables = [], loadingKeys = {};
  1943. // reload registered fallback languages
  1944. if ($fallbackLanguage && $fallbackLanguage.length) {
  1945. for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
  1946. tables.push(loadAsync($fallbackLanguage[i]));
  1947. loadingKeys[$fallbackLanguage[i]] = true;
  1948. }
  1949. }
  1950. // reload currently used language
  1951. if ($uses && !loadingKeys[$uses]) {
  1952. tables.push(loadAsync($uses));
  1953. }
  1954. var allTranslationsLoaded = function (tableData) {
  1955. $translationTable = {};
  1956. angular.forEach(tableData, function (data) {
  1957. translations(data.key, data.table);
  1958. });
  1959. if ($uses) {
  1960. useLanguage($uses);
  1961. }
  1962. resolve();
  1963. };
  1964. allTranslationsLoaded.displayName = 'refreshPostProcessor';
  1965. $q.all(tables).then(allTranslationsLoaded, reject);
  1966. } else if ($translationTable[langKey]) {
  1967. var oneTranslationsLoaded = function (data) {
  1968. translations(data.key, data.table);
  1969. if (langKey === $uses) {
  1970. useLanguage($uses);
  1971. }
  1972. resolve();
  1973. };
  1974. oneTranslationsLoaded.displayName = 'refreshPostProcessor';
  1975. loadAsync(langKey).then(oneTranslationsLoaded, reject);
  1976. } else {
  1977. reject();
  1978. }
  1979. return deferred.promise;
  1980. };
  1981. /**
  1982. * @ngdoc function
  1983. * @name translate.$translate#instant
  1984. * @methodOf translate.$translate
  1985. *
  1986. * @description
  1987. * Returns a translation instantly from the internal state of loaded translation. All rules
  1988. * regarding the current language, the preferred language of even fallback languages will be
  1989. * used except any promise handling. If a language was not found, an asynchronous loading
  1990. * will be invoked in the background.
  1991. *
  1992. * @param {string|array} translationId A token which represents a translation id
  1993. * This can be optionally an array of translation ids which
  1994. * results that the function's promise returns an object where
  1995. * each key is the translation id and the value the translation.
  1996. * @param {object} interpolateParams Params
  1997. * @param {string} interpolationId The id of the interpolation to use
  1998. *
  1999. * @return {string|object} translation
  2000. */
  2001. $translate.instant = function (translationId, interpolateParams, interpolationId) {
  2002. // Detect undefined and null values to shorten the execution and prevent exceptions
  2003. if (translationId === null || angular.isUndefined(translationId)) {
  2004. return translationId;
  2005. }
  2006. // Duck detection: If the first argument is an array, a bunch of translations was requested.
  2007. // The result is an object.
  2008. if (angular.isArray(translationId)) {
  2009. var results = {};
  2010. for (var i = 0, c = translationId.length; i < c; i++) {
  2011. results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId);
  2012. }
  2013. return results;
  2014. }
  2015. // We discarded unacceptable values. So we just need to verify if translationId is empty String
  2016. if (angular.isString(translationId) && translationId.length < 1) {
  2017. return translationId;
  2018. }
  2019. // trim off any whitespace
  2020. if (translationId) {
  2021. translationId = trim.apply(translationId);
  2022. }
  2023. var result, possibleLangKeys = [];
  2024. if ($preferredLanguage) {
  2025. possibleLangKeys.push($preferredLanguage);
  2026. }
  2027. if ($uses) {
  2028. possibleLangKeys.push($uses);
  2029. }
  2030. if ($fallbackLanguage && $fallbackLanguage.length) {
  2031. possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
  2032. }
  2033. for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
  2034. var possibleLangKey = possibleLangKeys[j];
  2035. if ($translationTable[possibleLangKey]) {
  2036. if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
  2037. result = determineTranslationInstant(translationId, interpolateParams, interpolationId);
  2038. } else if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
  2039. result = applyNotFoundIndicators(translationId);
  2040. }
  2041. }
  2042. if (typeof result !== 'undefined') {
  2043. break;
  2044. }
  2045. }
  2046. if (!result && result !== '') {
  2047. // Return translation of default interpolator if not found anything.
  2048. result = defaultInterpolator.interpolate(translationId, interpolateParams);
  2049. if ($missingTranslationHandlerFactory && !pendingLoader) {
  2050. result = translateByHandler(translationId, interpolateParams);
  2051. }
  2052. }
  2053. return result;
  2054. };
  2055. /**
  2056. * @ngdoc function
  2057. * @name translate.$translate#versionInfo
  2058. * @methodOf translate.$translate
  2059. *
  2060. * @description
  2061. * Returns the current version information for the angular-translate library
  2062. *
  2063. * @return {string} angular-translate version
  2064. */
  2065. $translate.versionInfo = function () {
  2066. return version;
  2067. };
  2068. /**
  2069. * @ngdoc function
  2070. * @name translate.$translate#loaderCache
  2071. * @methodOf translate.$translate
  2072. *
  2073. * @description
  2074. * Returns the defined loaderCache.
  2075. *
  2076. * @return {boolean|string|object} current value of loaderCache
  2077. */
  2078. $translate.loaderCache = function () {
  2079. return loaderCache;
  2080. };
  2081. // internal purpose only
  2082. $translate.directivePriority = function () {
  2083. return directivePriority;
  2084. };
  2085. // internal purpose only
  2086. $translate.statefulFilter = function () {
  2087. return statefulFilter;
  2088. };
  2089. if ($loaderFactory) {
  2090. // If at least one async loader is defined and there are no
  2091. // (default) translations available we should try to load them.
  2092. if (angular.equals($translationTable, {})) {
  2093. $translate.use($translate.use());
  2094. }
  2095. // Also, if there are any fallback language registered, we start
  2096. // loading them asynchronously as soon as we can.
  2097. if ($fallbackLanguage && $fallbackLanguage.length) {
  2098. var processAsyncResult = function (translation) {
  2099. translations(translation.key, translation.table);
  2100. $rootScope.$emit('$translateChangeEnd', { language: translation.key });
  2101. return translation;
  2102. };
  2103. for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
  2104. var fallbackLanguageId = $fallbackLanguage[i];
  2105. if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) {
  2106. langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult);
  2107. }
  2108. }
  2109. }
  2110. }
  2111. return $translate;
  2112. }
  2113. ];
  2114. }
  2115. $translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider'];
  2116. $translate.displayName = 'displayName';
  2117. /**
  2118. * @ngdoc object
  2119. * @name translate.$translateDefaultInterpolation
  2120. * @requires $interpolate
  2121. *
  2122. * @description
  2123. * Uses angular's `$interpolate` services to interpolate strings against some values.
  2124. *
  2125. * Be aware to configure a proper sanitization strategy.
  2126. *
  2127. * See also:
  2128. * * {@link translate.$translateSanitization}
  2129. *
  2130. * @return {object} $translateDefaultInterpolation Interpolator service
  2131. */
  2132. angular.module('translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation);
  2133. function $translateDefaultInterpolation ($interpolate, $translateSanitization) {
  2134. 'use strict';
  2135. var $translateInterpolator = {},
  2136. $locale,
  2137. $identifier = 'default';
  2138. /**
  2139. * @ngdoc function
  2140. * @name translate.$translateDefaultInterpolation#setLocale
  2141. * @methodOf translate.$translateDefaultInterpolation
  2142. *
  2143. * @description
  2144. * Sets current locale (this is currently not use in this interpolation).
  2145. *
  2146. * @param {string} locale Language key or locale.
  2147. */
  2148. $translateInterpolator.setLocale = function (locale) {
  2149. $locale = locale;
  2150. };
  2151. /**
  2152. * @ngdoc function
  2153. * @name translate.$translateDefaultInterpolation#getInterpolationIdentifier
  2154. * @methodOf translate.$translateDefaultInterpolation
  2155. *
  2156. * @description
  2157. * Returns an identifier for this interpolation service.
  2158. *
  2159. * @returns {string} $identifier
  2160. */
  2161. $translateInterpolator.getInterpolationIdentifier = function () {
  2162. return $identifier;
  2163. };
  2164. /**
  2165. * @deprecated will be removed in 3.0
  2166. * @see {@link translate.$translateSanitization}
  2167. */
  2168. $translateInterpolator.useSanitizeValueStrategy = function (value) {
  2169. $translateSanitization.useStrategy(value);
  2170. return this;
  2171. };
  2172. /**
  2173. * @ngdoc function
  2174. * @name translate.$translateDefaultInterpolation#interpolate
  2175. * @methodOf translate.$translateDefaultInterpolation
  2176. *
  2177. * @description
  2178. * Interpolates given string agains given interpolate params using angulars
  2179. * `$interpolate` service.
  2180. *
  2181. * @returns {string} interpolated string.
  2182. */
  2183. $translateInterpolator.interpolate = function (string, interpolationParams) {
  2184. interpolationParams = interpolationParams || {};
  2185. interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params');
  2186. var interpolatedText = $interpolate(string)(interpolationParams);
  2187. interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text');
  2188. return interpolatedText;
  2189. };
  2190. return $translateInterpolator;
  2191. }
  2192. $translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization'];
  2193. $translateDefaultInterpolation.displayName = '$translateDefaultInterpolation';
  2194. angular.module('translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
  2195. angular.module('translate')
  2196. /**
  2197. * @ngdoc directive
  2198. * @name translate.directive:translate
  2199. * @requires $compile
  2200. * @requires $filter
  2201. * @requires $interpolate
  2202. * @restrict A
  2203. *
  2204. * @description
  2205. * Translates given translation id either through attribute or DOM content.
  2206. * Internally it uses `translate` filter to translate translation id. It possible to
  2207. * an optional `translate-values` object literal as string into translation id.
  2208. *
  2209. * @param {string=} translate Translation id which could be either string or interpolated string.
  2210. * @param {string=} translate-values Values to into translation id. Can be ed as object literal string or interpolated object.
  2211. * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
  2212. * @param {string=} translate-default will be used unless translation was successful
  2213. * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link translate.$translateProvider#methods_usePostCompiling}
  2214. *
  2215. * @example
  2216. <example module="ngView">
  2217. <file name="index.html">
  2218. <div ng-controller="TranslateCtrl">
  2219. <pre translate="TRANSLATION_ID"></pre>
  2220. <pre translate>TRANSLATION_ID</pre>
  2221. <pre translate translate-attr-title="TRANSLATION_ID"></pre>
  2222. <pre translate="{{translationId}}"></pre>
  2223. <pre translate>{{translationId}}</pre>
  2224. <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
  2225. <pre translate translate-values="{value: 5}">WITH_VALUES</pre>
  2226. <pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
  2227. <pre translate translate-values="{{values}}">WITH_VALUES</pre>
  2228. <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>
  2229. </div>
  2230. </file>
  2231. <file name="script.js">
  2232. angular.module('ngView', ['translate'])
  2233. .config(function ($translateProvider) {
  2234. $translateProvider.translations('en',{
  2235. 'TRANSLATION_ID': 'Hello there!',
  2236. 'WITH_VALUES': 'The following value is dynamic: {{value}}'
  2237. }).preferredLanguage('en');
  2238. });
  2239. angular.module('ngView').controller('TranslateCtrl', function ($scope) {
  2240. $scope.translationId = 'TRANSLATION_ID';
  2241. $scope.values = {
  2242. value: 78
  2243. };
  2244. });
  2245. </file>
  2246. <file name="scenario.js">
  2247. it('should translate', function () {
  2248. inject(function ($rootScope, $compile) {
  2249. $rootScope.translationId = 'TRANSLATION_ID';
  2250. element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope);
  2251. $rootScope.$digest();
  2252. expect(element.text()).toBe('Hello there!');
  2253. element = $compile('<p translate="{{translationId}}"></p>')($rootScope);
  2254. $rootScope.$digest();
  2255. expect(element.text()).toBe('Hello there!');
  2256. element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope);
  2257. $rootScope.$digest();
  2258. expect(element.text()).toBe('Hello there!');
  2259. element = $compile('<p translate>{{translationId}}</p>')($rootScope);
  2260. $rootScope.$digest();
  2261. expect(element.text()).toBe('Hello there!');
  2262. element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope);
  2263. $rootScope.$digest();
  2264. expect(element.attr('title')).toBe('Hello there!');
  2265. });
  2266. });
  2267. </file>
  2268. </example>
  2269. */
  2270. .directive('translate', translateDirective);
  2271. function translateDirective($translate, $q, $interpolate, $compile, $parse, $rootScope) {
  2272. 'use strict';
  2273. /**
  2274. * @name trim
  2275. * @private
  2276. *
  2277. * @description
  2278. * trim polyfill
  2279. *
  2280. * @returns {string} The string stripped of whitespace from both ends
  2281. */
  2282. var trim = function() {
  2283. return this.toString().replace(/^\s+|\s+$/g, '');
  2284. };
  2285. return {
  2286. restrict: 'AE',
  2287. scope: true,
  2288. priority: $translate.directivePriority(),
  2289. compile: function (tElement, tAttr) {
  2290. var translateValuesExist = (tAttr.translateValues) ?
  2291. tAttr.translateValues : undefined;
  2292. var translateInterpolation = (tAttr.translateInterpolation) ?
  2293. tAttr.translateInterpolation : undefined;
  2294. var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
  2295. var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)',
  2296. watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)';
  2297. return function linkFn(scope, iElement, iAttr) {
  2298. scope.interpolateParams = {};
  2299. scope.preText = '';
  2300. scope.postText = '';
  2301. var translationIds = {};
  2302. var initInterpolationParams = function (interpolateParams, iAttr, tAttr) {
  2303. // initial setup
  2304. if (iAttr.translateValues) {
  2305. angular.extend(interpolateParams, $parse(iAttr.translateValues)(scope.$parent));
  2306. }
  2307. // initially fetch all attributes if existing and fill the params
  2308. if (translateValueExist) {
  2309. for (var attr in tAttr) {
  2310. if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
  2311. var attributeName = angular.lowercase(attr.substr(14, 1)) + attr.substr(15);
  2312. interpolateParams[attributeName] = tAttr[attr];
  2313. }
  2314. }
  2315. }
  2316. };
  2317. // Ensures any change of the attribute "translate" containing the id will
  2318. // be re-stored to the scope's "translationId".
  2319. // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
  2320. var observeElementTranslation = function (translationId) {
  2321. // Remove any old watcher
  2322. if (angular.isFunction(observeElementTranslation._unwatchOld)) {
  2323. observeElementTranslation._unwatchOld();
  2324. observeElementTranslation._unwatchOld = undefined;
  2325. }
  2326. if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
  2327. // Resolve translation id by inner html if required
  2328. var interpolateMatches = trim.apply(iElement.text()).match(interpolateRegExp);
  2329. // Interpolate translation id if required
  2330. if (angular.isArray(interpolateMatches)) {
  2331. scope.preText = interpolateMatches[1];
  2332. scope.postText = interpolateMatches[3];
  2333. translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent);
  2334. var watcherMatches = iElement.text().match(watcherRegExp);
  2335. if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) {
  2336. observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) {
  2337. translationIds.translate = newValue;
  2338. updateTranslations();
  2339. });
  2340. }
  2341. } else {
  2342. translationIds.translate = iElement.text().replace(/^\s+|\s+$/g,'');
  2343. }
  2344. } else {
  2345. translationIds.translate = translationId;
  2346. }
  2347. updateTranslations();
  2348. };
  2349. var observeAttributeTranslation = function (translateAttr) {
  2350. iAttr.$observe(translateAttr, function (translationId) {
  2351. translationIds[translateAttr] = translationId;
  2352. updateTranslations();
  2353. });
  2354. };
  2355. // initial setup with values
  2356. initInterpolationParams(scope.interpolateParams, iAttr, tAttr);
  2357. var firstAttributeChangedEvent = true;
  2358. iAttr.$observe('translate', function (translationId) {
  2359. if (typeof translationId === 'undefined') {
  2360. // case of element "<translate>xyz</translate>"
  2361. observeElementTranslation('');
  2362. } else {
  2363. // case of regular attribute
  2364. if (translationId !== '' || !firstAttributeChangedEvent) {
  2365. translationIds.translate = translationId;
  2366. updateTranslations();
  2367. }
  2368. }
  2369. firstAttributeChangedEvent = false;
  2370. });
  2371. for (var translateAttr in iAttr) {
  2372. if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') {
  2373. observeAttributeTranslation(translateAttr);
  2374. }
  2375. }
  2376. iAttr.$observe('translateDefault', function (value) {
  2377. scope.defaultText = value;
  2378. });
  2379. if (translateValuesExist) {
  2380. iAttr.$observe('translateValues', function (interpolateParams) {
  2381. if (interpolateParams) {
  2382. scope.$parent.$watch(function () {
  2383. angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
  2384. });
  2385. }
  2386. });
  2387. }
  2388. if (translateValueExist) {
  2389. var observeValueAttribute = function (attrName) {
  2390. iAttr.$observe(attrName, function (value) {
  2391. var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
  2392. scope.interpolateParams[attributeName] = value;
  2393. });
  2394. };
  2395. for (var attr in iAttr) {
  2396. if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
  2397. observeValueAttribute(attr);
  2398. }
  2399. }
  2400. }
  2401. // Master update function
  2402. var updateTranslations = function () {
  2403. for (var key in translationIds) {
  2404. if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) {
  2405. updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText);
  2406. }
  2407. }
  2408. };
  2409. // Put translation processing function outside loop
  2410. var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText) {
  2411. if (translationId) {
  2412. $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText)
  2413. .then(function (translation) {
  2414. applyTranslation(translation, scope, true, translateAttr);
  2415. }, function (translationId) {
  2416. applyTranslation(translationId, scope, false, translateAttr);
  2417. });
  2418. } else {
  2419. // as an empty string cannot be translated, we can solve this using successful=false
  2420. applyTranslation(translationId, scope, false, translateAttr);
  2421. }
  2422. };
  2423. var applyTranslation = function (value, scope, successful, translateAttr) {
  2424. if (translateAttr === 'translate') {
  2425. // default translate into innerHTML
  2426. if (!successful && typeof scope.defaultText !== 'undefined') {
  2427. value = scope.defaultText;
  2428. }
  2429. iElement.html(scope.preText + value + scope.postText);
  2430. var globallyEnabled = $translate.isPostCompilingEnabled();
  2431. var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
  2432. var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
  2433. if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
  2434. $compile(iElement.contents())(scope);
  2435. }
  2436. } else {
  2437. // translate attribute
  2438. if (!successful && typeof scope.defaultText !== 'undefined') {
  2439. value = scope.defaultText;
  2440. }
  2441. var attributeName = iAttr.$attr[translateAttr];
  2442. if (attributeName.substr(0, 5) === 'data-') {
  2443. // ensure html5 data prefix is stripped
  2444. attributeName = attributeName.substr(5);
  2445. }
  2446. attributeName = attributeName.substr(15);
  2447. iElement.attr(attributeName, value);
  2448. }
  2449. };
  2450. if (translateValuesExist || translateValueExist || iAttr.translateDefault) {
  2451. scope.$watch('interpolateParams', updateTranslations, true);
  2452. }
  2453. // Ensures the text will be refreshed after the current language was changed
  2454. // w/ $translate.use(...)
  2455. var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
  2456. // ensure translation will be looked up at least one
  2457. if (iElement.text().length) {
  2458. if (iAttr.translate) {
  2459. observeElementTranslation(iAttr.translate);
  2460. } else {
  2461. observeElementTranslation('');
  2462. }
  2463. } else if (iAttr.translate) {
  2464. // ensure attribute will be not skipped
  2465. observeElementTranslation(iAttr.translate);
  2466. }
  2467. updateTranslations();
  2468. scope.$on('$destroy', unbind);
  2469. };
  2470. }
  2471. };
  2472. }
  2473. translateDirective.$inject = ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope'];
  2474. translateDirective.displayName = 'translateDirective';
  2475. angular.module('translate')
  2476. /**
  2477. * @ngdoc directive
  2478. * @name translate.directive:translateCloak
  2479. * @requires $rootScope
  2480. * @requires $translate
  2481. * @restrict A
  2482. *
  2483. * $description
  2484. * Adds a `translate-cloak` class name to the given element where this directive
  2485. * is applied initially and removes it, once a loader has finished loading.
  2486. *
  2487. * This directive can be used to prevent initial flickering when loading translation
  2488. * data asynchronously.
  2489. *
  2490. * The class name is defined in
  2491. * {@link translate.$translateProvider#cloakClassName $translate.cloakClassName()}.
  2492. *
  2493. * @param {string=} translate-cloak If a translationId is provided, it will be used for showing
  2494. * or hiding the cloak. Basically it relies on the translation
  2495. * resolve.
  2496. */
  2497. .directive('translateCloak', translateCloakDirective);
  2498. function translateCloakDirective($rootScope, $translate) {
  2499. 'use strict';
  2500. return {
  2501. compile: function (tElement) {
  2502. var applyCloak = function () {
  2503. tElement.addClass($translate.cloakClassName());
  2504. },
  2505. removeCloak = function () {
  2506. tElement.removeClass($translate.cloakClassName());
  2507. },
  2508. removeListener = $rootScope.$on('$translateChangeEnd', function () {
  2509. removeCloak();
  2510. removeListener();
  2511. removeListener = null;
  2512. });
  2513. applyCloak();
  2514. return function linkFn(scope, iElement, iAttr) {
  2515. // Register a watcher for the defined translation allowing a fine tuned cloak
  2516. if (iAttr.translateCloak && iAttr.translateCloak.length) {
  2517. iAttr.$observe('translateCloak', function (translationId) {
  2518. $translate(translationId).then(removeCloak, applyCloak);
  2519. });
  2520. }
  2521. };
  2522. }
  2523. };
  2524. }
  2525. translateCloakDirective.$inject = ['$rootScope', '$translate'];
  2526. translateCloakDirective.displayName = 'translateCloakDirective';
  2527. angular.module('translate')
  2528. /**
  2529. * @ngdoc filter
  2530. * @name translate.filter:translate
  2531. * @requires $parse
  2532. * @requires translate.$translate
  2533. * @function
  2534. *
  2535. * @description
  2536. * Uses `$translate` service to translate contents. Accepts interpolate parameters
  2537. * to dynamized values though translation.
  2538. *
  2539. * @param {string} translationId A translation id to be translated.
  2540. * @param {*=} interpolateParams Optional object literal (as hash or string) to values into translation.
  2541. *
  2542. * @returns {string} Translated text.
  2543. *
  2544. * @example
  2545. <example module="ngView">
  2546. <file name="index.html">
  2547. <div ng-controller="TranslateCtrl">
  2548. <pre>{{ 'TRANSLATION_ID' | translate }}</pre>
  2549. <pre>{{ translationId | translate }}</pre>
  2550. <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre>
  2551. <pre>{{ 'WITH_VALUES' | translate:values }}</pre>
  2552. </div>
  2553. </file>
  2554. <file name="script.js">
  2555. angular.module('ngView', ['translate'])
  2556. .config(function ($translateProvider) {
  2557. $translateProvider.translations('en', {
  2558. 'TRANSLATION_ID': 'Hello there!',
  2559. 'WITH_VALUES': 'The following value is dynamic: {{value}}'
  2560. });
  2561. $translateProvider.preferredLanguage('en');
  2562. });
  2563. angular.module('ngView').controller('TranslateCtrl', function ($scope) {
  2564. $scope.translationId = 'TRANSLATION_ID';
  2565. $scope.values = {
  2566. value: 78
  2567. };
  2568. });
  2569. </file>
  2570. </example>
  2571. */
  2572. .filter('translate', translateFilterFactory);
  2573. function translateFilterFactory($parse, $translate) {
  2574. 'use strict';
  2575. var translateFilter = function (translationId, interpolateParams, interpolation) {
  2576. if (!angular.isObject(interpolateParams)) {
  2577. interpolateParams = $parse(interpolateParams)(this);
  2578. }
  2579. return $translate.instant(translationId, interpolateParams, interpolation);
  2580. };
  2581. if ($translate.statefulFilter()) {
  2582. translateFilter.$stateful = true;
  2583. }
  2584. return translateFilter;
  2585. }
  2586. translateFilterFactory.$inject = ['$parse', '$translate'];
  2587. translateFilterFactory.displayName = 'translateFilterFactory';
  2588. angular.module('translate')
  2589. /**
  2590. * @ngdoc object
  2591. * @name translate.$translationCache
  2592. * @requires $cacheFactory
  2593. *
  2594. * @description
  2595. * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You
  2596. * can load translation tables directly into the cache by consuming the
  2597. * `$translationCache` service directly.
  2598. *
  2599. * @return {object} $cacheFactory object.
  2600. */
  2601. .factory('$translationCache', $translationCache);
  2602. function $translationCache($cacheFactory) {
  2603. 'use strict';
  2604. return $cacheFactory('translations');
  2605. }
  2606. $translationCache.$inject = ['$cacheFactory'];
  2607. $translationCache.displayName = '$translationCache';
  2608. return 'translate';
  2609. }));
Now the remaining two files, named Index.js and MyApp.Js, are used for defining the Angular app and controller for the page.
The following is the code for MyApp.JS:
  1. var MyApp = angular.module('MyApp', ['translate']);
  2. MyApp.config(function ($translateProvider) {
  3. $translateProvider.translations('en', {
  4. HEADLINE: 'Hello there, This is my awesome app!',
  5. INTRO_TEXT: 'And it has i18n support!',
  6. BUTTON_TEXT_EN: 'english',
  7. BUTTON_TEXT_DE: 'german',
  8. BUTTON_TEXT_AE: 'Arabic'
  9. })
  10. .translations('en-de', {
  11. HEADLINE: 'Hey, das ist meine großartige App!',
  12. INTRO_TEXT: 'Und sie untersützt mehrere Sprachen!',
  13. BUTTON_TEXT_EN: 'englisch',
  14. BUTTON_TEXT_DE: 'deutsch',
  15. BUTTON_TEXT_AE: 'Arabisch'
  16. })
  17. .translations('en-ar', {
  18. HEADLINE: 'هذا هو اختبار التدويل!',
  19. INTRO_TEXT: 'إضفاء الطابع المحلي على القيام به!',
  20. BUTTON_TEXT_EN: 'الإنجليزية',
  21. BUTTON_TEXT_DE: 'ألماني',
  22. BUTTON_TEXT_AE: 'العربية'
  23. },
  24. { rtl: true });
  25. $translateProvider.useSanitizeValueStrategy('escaped');
  26. $translateProvider.preferredLanguage('en');
  27. });
The following is the code of the Index.js file:
  1. MyApp.controller('TranslateController', function ($translate, $scope)
  2. {
  3. $scope.changeLanguage = function (langKey)
  4. {
  5. $translate.use(langKey);
  6. };
  7. });
In the App.Config part, we use the translate provider to translate the language depending on the selected languages.
Now if we run the project then the output looks as in the following:
But the problem is that when we click on the Arabic Button, it translates the text as Arabic, but never change the document alignment to rtl format since Arabic always uses the rtl (right to left) format. For doing this we need to make a change in the MyApp.js file and add the following new code to the files:
  1. MyApp.run(function ($rootScope, Language)
  2. {
  3. $rootScope.Language = Language;
  4. })
  5. // Service definition
  6. MyApp.factory('Language', function ($translate)
  7. {
  8. var rtlLanguages = ['en-ar'];
  9. var isRtl = function ()
  10. {
  11. var languageKey = $translate.proposedLanguage() || $translate.use();
  12. for (var i = 0; i < rtlLanguages.length; i += 1)
  13. {
  14. if (languageKey.indexOf(rtlLanguages[i]) > -1)
  15. return true;
  16. }
  17. return false;
  18. };
  19. return
  20. {
  21. isRtl: isRtl
  22. };
  23. });
Actually, we create a factory service to define the rtl format is on or off depending on the selected language.
Also, we need to change the controller script in the index.js file.
  1. MyApp.controller('TranslateController', function ($translate, $scope, $window)
  2. {
  3. if (sessionStorage.getItem("Locale") == null || sessionStorage.getItem("Locale") == undefined)
  4. {
  5. $scope.Lang = 'en';
  6. sessionStorage.setItem("Locale", JSON.stringify($scope.Lang));
  7. }
  8. else
  9. {
  10. $scope.Lang = JSON.parse(sessionStorage.getItem('Locale'));
  11. }
  12. $scope.selectedLanguage = $translate.use($scope.Lang); //default
  13. $scope.changeLanguage = function (langKey) {
  14. $translate.use(langKey);
  15. sessionStorage.setItem("Locale", JSON.stringify(langKey));
  16. $window.location.reload();
  17. };
  18. });
Here during the load of the Controller (for the first time of the page load) we set the page language to English (en) and store this information in the browser session variables so that we can access this value in the HTML file to load the dynamic Angular locale script file.
Now after doing this we need to make some changes in the HTML file also.
  1. <!DOCTYPE html>
  2. <html ng-app="MyApp" ng-controller="TranslateController" ng-class="{'rtl':Language.isRtl()}" dir="{{(Language.isRtl())?'rtl':'ltr'}}" lang={{Lang}}>
  3. <head>
  4. <title></title>
  5. <script src="../Scripts/angular.min.js"></script>
  6. <script src="../Scripts/angular-route.js"></script>
  7. <script src="../Scripts/angular-translate.js"></script>
  8. <script src="../UserScript/MyApp.js"></script>
  9. <script src="../UserScript/Index.js"></script>
  10. <script>
  11. var locale = JSON.parse(sessionStorage.getItem('Locale'));
  12. if (locale) {
  13. document.write('<script src="../scripts/i18n/angular-locale_' + locale + '.js"><\/script>');
  14. }
  15. </script>
  16. </head>
  17. <body>
  18. <h1>Localization</h1>
  19. <div>
  20. <button ng-click="changeLanguage('en-de')" translate="BUTTON_TEXT_DE"></button>
  21. <button ng-click="changeLanguage('en')" translate="BUTTON_TEXT_EN"></button>
  22. <button ng-click="changeLanguage('en-ar')" translate="BUTTON_TEXT_AE"></button>
  23. </div>
  24. <div>
  25. <h2>{{ 'HEADLINE' | translate }}</h2>
  26. <p>{{ 'INTRO_TEXT' | translate }}</p>
  27. </div>
  28. <div>
  29. <input type="date" />
  30. </div>
  31. </body>
  32. </html>
Execute the project and click on the Arabic button and see the following output.
localization