angular-translate.js 81 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359
  1. /*!
  2. * angular-translate - v2.6.1 - 2015-03-01
  3. * http://github.com/angular-translate/angular-translate
  4. * Copyright (c) 2015 ; Licensed MIT
  5. */
  6. /**
  7. * @ngdoc overview
  8. * @name pascalprecht.translate
  9. *
  10. * @description
  11. * The main module which holds everything together.
  12. */
  13. angular.module('pascalprecht.translate', ['ng'])
  14. .run(['$translate', function ($translate) {
  15. var key = $translate.storageKey(),
  16. storage = $translate.storage();
  17. var fallbackFromIncorrectStorageValue = function() {
  18. var preferred = $translate.preferredLanguage();
  19. if (angular.isString(preferred)) {
  20. $translate.use(preferred);
  21. // $translate.use() will also remember the language.
  22. // So, we don't need to call storage.put() here.
  23. } else {
  24. storage.put(key, $translate.use());
  25. }
  26. };
  27. if (storage) {
  28. if (!storage.get(key)) {
  29. fallbackFromIncorrectStorageValue();
  30. } else {
  31. $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue);
  32. }
  33. } else if (angular.isString($translate.preferredLanguage())) {
  34. $translate.use($translate.preferredLanguage());
  35. }
  36. }]);
  37. /**
  38. * @ngdoc object
  39. * @name pascalprecht.translate.$translateProvider
  40. * @description
  41. *
  42. * $translateProvider allows developers to register translation-tables, asynchronous loaders
  43. * and similar to configure translation behavior directly inside of a module.
  44. *
  45. */
  46. angular.module('pascalprecht.translate').provider('$translate', ['$STORAGE_KEY', '$windowProvider', function ($STORAGE_KEY, $windowProvider) {
  47. var $translationTable = {},
  48. $preferredLanguage,
  49. $availableLanguageKeys = [],
  50. $languageKeyAliases,
  51. $fallbackLanguage,
  52. $fallbackWasString,
  53. $uses,
  54. $nextLang,
  55. $storageFactory,
  56. $storageKey = $STORAGE_KEY,
  57. $storagePrefix,
  58. $missingTranslationHandlerFactory,
  59. $interpolationFactory,
  60. $interpolatorFactories = [],
  61. $interpolationSanitizationStrategy = false,
  62. $loaderFactory,
  63. $cloakClassName = 'translate-cloak',
  64. $loaderOptions,
  65. $notFoundIndicatorLeft,
  66. $notFoundIndicatorRight,
  67. $postCompilingEnabled = false,
  68. NESTED_OBJECT_DELIMITER = '.',
  69. loaderCache,
  70. directivePriority = 0;
  71. var version = '2.6.1';
  72. // tries to determine the browsers language
  73. var getFirstBrowserLanguage = function () {
  74. var nav = $windowProvider.$get().navigator,
  75. browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'],
  76. i,
  77. language;
  78. // support for HTML 5.1 "navigator.languages"
  79. if (angular.isArray(nav.languages)) {
  80. for (i = 0; i < nav.languages.length; i++) {
  81. language = nav.languages[i];
  82. if (language && language.length) {
  83. return language;
  84. }
  85. }
  86. }
  87. // support for other well known properties in browsers
  88. for (i = 0; i < browserLanguagePropertyKeys.length; i++) {
  89. language = nav[browserLanguagePropertyKeys[i]];
  90. if (language && language.length) {
  91. return language;
  92. }
  93. }
  94. return null;
  95. };
  96. getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage';
  97. // tries to determine the browsers locale
  98. var getLocale = function () {
  99. return (getFirstBrowserLanguage() || '').split('-').join('_');
  100. };
  101. getLocale.displayName = 'angular-translate/service: getLocale';
  102. /**
  103. * @name indexOf
  104. * @private
  105. *
  106. * @description
  107. * indexOf polyfill. Kinda sorta.
  108. *
  109. * @param {array} array Array to search in.
  110. * @param {string} searchElement Element to search for.
  111. *
  112. * @returns {int} Index of search element.
  113. */
  114. var indexOf = function(array, searchElement) {
  115. for (var i = 0, len = array.length; i < len; i++) {
  116. if (array[i] === searchElement) {
  117. return i;
  118. }
  119. }
  120. return -1;
  121. };
  122. /**
  123. * @name trim
  124. * @private
  125. *
  126. * @description
  127. * trim polyfill
  128. *
  129. * @returns {string} The string stripped of whitespace from both ends
  130. */
  131. var trim = function() {
  132. return this.replace(/^\s+|\s+$/g, '');
  133. };
  134. var negotiateLocale = function (preferred) {
  135. var avail = [],
  136. locale = angular.lowercase(preferred),
  137. i = 0,
  138. n = $availableLanguageKeys.length;
  139. for (; i < n; i++) {
  140. avail.push(angular.lowercase($availableLanguageKeys[i]));
  141. }
  142. if (indexOf(avail, locale) > -1) {
  143. return preferred;
  144. }
  145. if ($languageKeyAliases) {
  146. var alias;
  147. for (var langKeyAlias in $languageKeyAliases) {
  148. var hasWildcardKey = false;
  149. var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) &&
  150. angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
  151. if (langKeyAlias.slice(-1) === '*') {
  152. hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length-1);
  153. }
  154. if (hasExactKey || hasWildcardKey) {
  155. alias = $languageKeyAliases[langKeyAlias];
  156. if (indexOf(avail, angular.lowercase(alias)) > -1) {
  157. return alias;
  158. }
  159. }
  160. }
  161. }
  162. var parts = preferred.split('_');
  163. if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) {
  164. return parts[0];
  165. }
  166. // If everything fails, just return the preferred, unchanged.
  167. return preferred;
  168. };
  169. /**
  170. * @ngdoc function
  171. * @name pascalprecht.translate.$translateProvider#translations
  172. * @methodOf pascalprecht.translate.$translateProvider
  173. *
  174. * @description
  175. * Registers a new translation table for specific language key.
  176. *
  177. * To register a translation table for specific language, pass a defined language
  178. * key as first parameter.
  179. *
  180. * <pre>
  181. * // register translation table for language: 'de_DE'
  182. * $translateProvider.translations('de_DE', {
  183. * 'GREETING': 'Hallo Welt!'
  184. * });
  185. *
  186. * // register another one
  187. * $translateProvider.translations('en_US', {
  188. * 'GREETING': 'Hello world!'
  189. * });
  190. * </pre>
  191. *
  192. * When registering multiple translation tables for for the same language key,
  193. * the actual translation table gets extended. This allows you to define module
  194. * specific translation which only get added, once a specific module is loaded in
  195. * your app.
  196. *
  197. * Invoking this method with no arguments returns the translation table which was
  198. * registered with no language key. Invoking it with a language key returns the
  199. * related translation table.
  200. *
  201. * @param {string} key A language key.
  202. * @param {object} translationTable A plain old JavaScript object that represents a translation table.
  203. *
  204. */
  205. var translations = function (langKey, translationTable) {
  206. if (!langKey && !translationTable) {
  207. return $translationTable;
  208. }
  209. if (langKey && !translationTable) {
  210. if (angular.isString(langKey)) {
  211. return $translationTable[langKey];
  212. }
  213. } else {
  214. if (!angular.isObject($translationTable[langKey])) {
  215. $translationTable[langKey] = {};
  216. }
  217. angular.extend($translationTable[langKey], flatObject(translationTable));
  218. }
  219. return this;
  220. };
  221. this.translations = translations;
  222. /**
  223. * @ngdoc function
  224. * @name pascalprecht.translate.$translateProvider#cloakClassName
  225. * @methodOf pascalprecht.translate.$translateProvider
  226. *
  227. * @description
  228. *
  229. * Let's you change the class name for `translate-cloak` directive.
  230. * Default class name is `translate-cloak`.
  231. *
  232. * @param {string} name translate-cloak class name
  233. */
  234. this.cloakClassName = function (name) {
  235. if (!name) {
  236. return $cloakClassName;
  237. }
  238. $cloakClassName = name;
  239. return this;
  240. };
  241. /**
  242. * @name flatObject
  243. * @private
  244. *
  245. * @description
  246. * Flats an object. This function is used to flatten given translation data with
  247. * namespaces, so they are later accessible via dot notation.
  248. */
  249. var flatObject = function (data, path, result, prevKey) {
  250. var key, keyWithPath, keyWithShortPath, val;
  251. if (!path) {
  252. path = [];
  253. }
  254. if (!result) {
  255. result = {};
  256. }
  257. for (key in data) {
  258. if (!Object.prototype.hasOwnProperty.call(data, key)) {
  259. continue;
  260. }
  261. val = data[key];
  262. if (angular.isObject(val)) {
  263. flatObject(val, path.concat(key), result, key);
  264. } else {
  265. keyWithPath = path.length ? ('' + path.join(NESTED_OBJECT_DELIMITER) + NESTED_OBJECT_DELIMITER + key) : key;
  266. if(path.length && key === prevKey){
  267. // Create shortcut path (foo.bar == foo.bar.bar)
  268. keyWithShortPath = '' + path.join(NESTED_OBJECT_DELIMITER);
  269. // Link it to original path
  270. result[keyWithShortPath] = '@:' + keyWithPath;
  271. }
  272. result[keyWithPath] = val;
  273. }
  274. }
  275. return result;
  276. };
  277. /**
  278. * @ngdoc function
  279. * @name pascalprecht.translate.$translateProvider#addInterpolation
  280. * @methodOf pascalprecht.translate.$translateProvider
  281. *
  282. * @description
  283. * Adds interpolation services to angular-translate, so it can manage them.
  284. *
  285. * @param {object} factory Interpolation service factory
  286. */
  287. this.addInterpolation = function (factory) {
  288. $interpolatorFactories.push(factory);
  289. return this;
  290. };
  291. /**
  292. * @ngdoc function
  293. * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation
  294. * @methodOf pascalprecht.translate.$translateProvider
  295. *
  296. * @description
  297. * Tells angular-translate to use interpolation functionality of messageformat.js.
  298. * This is useful when having high level pluralization and gender selection.
  299. */
  300. this.useMessageFormatInterpolation = function () {
  301. return this.useInterpolation('$translateMessageFormatInterpolation');
  302. };
  303. /**
  304. * @ngdoc function
  305. * @name pascalprecht.translate.$translateProvider#useInterpolation
  306. * @methodOf pascalprecht.translate.$translateProvider
  307. *
  308. * @description
  309. * Tells angular-translate which interpolation style to use as default, application-wide.
  310. * Simply pass a factory/service name. The interpolation service has to implement
  311. * the correct interface.
  312. *
  313. * @param {string} factory Interpolation service name.
  314. */
  315. this.useInterpolation = function (factory) {
  316. $interpolationFactory = factory;
  317. return this;
  318. };
  319. /**
  320. * @ngdoc function
  321. * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy
  322. * @methodOf pascalprecht.translate.$translateProvider
  323. *
  324. * @description
  325. * Simply sets a sanitation strategy type.
  326. *
  327. * @param {string} value Strategy type.
  328. */
  329. this.useSanitizeValueStrategy = function (value) {
  330. $interpolationSanitizationStrategy = value;
  331. return this;
  332. };
  333. /**
  334. * @ngdoc function
  335. * @name pascalprecht.translate.$translateProvider#preferredLanguage
  336. * @methodOf pascalprecht.translate.$translateProvider
  337. *
  338. * @description
  339. * Tells the module which of the registered translation tables to use for translation
  340. * at initial startup by passing a language key. Similar to `$translateProvider#use`
  341. * only that it says which language to **prefer**.
  342. *
  343. * @param {string} langKey A language key.
  344. *
  345. */
  346. this.preferredLanguage = function(langKey) {
  347. setupPreferredLanguage(langKey);
  348. return this;
  349. };
  350. var setupPreferredLanguage = function (langKey) {
  351. if (langKey) {
  352. $preferredLanguage = langKey;
  353. }
  354. return $preferredLanguage;
  355. };
  356. /**
  357. * @ngdoc function
  358. * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator
  359. * @methodOf pascalprecht.translate.$translateProvider
  360. *
  361. * @description
  362. * Sets an indicator which is used when a translation isn't found. E.g. when
  363. * setting the indicator as 'X' and one tries to translate a translation id
  364. * called `NOT_FOUND`, this will result in `X NOT_FOUND X`.
  365. *
  366. * Internally this methods sets a left indicator and a right indicator using
  367. * `$translateProvider.translationNotFoundIndicatorLeft()` and
  368. * `$translateProvider.translationNotFoundIndicatorRight()`.
  369. *
  370. * **Note**: These methods automatically add a whitespace between the indicators
  371. * and the translation id.
  372. *
  373. * @param {string} indicator An indicator, could be any string.
  374. */
  375. this.translationNotFoundIndicator = function (indicator) {
  376. this.translationNotFoundIndicatorLeft(indicator);
  377. this.translationNotFoundIndicatorRight(indicator);
  378. return this;
  379. };
  380. /**
  381. * ngdoc function
  382. * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
  383. * @methodOf pascalprecht.translate.$translateProvider
  384. *
  385. * @description
  386. * Sets an indicator which is used when a translation isn't found left to the
  387. * translation id.
  388. *
  389. * @param {string} indicator An indicator.
  390. */
  391. this.translationNotFoundIndicatorLeft = function (indicator) {
  392. if (!indicator) {
  393. return $notFoundIndicatorLeft;
  394. }
  395. $notFoundIndicatorLeft = indicator;
  396. return this;
  397. };
  398. /**
  399. * ngdoc function
  400. * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
  401. * @methodOf pascalprecht.translate.$translateProvider
  402. *
  403. * @description
  404. * Sets an indicator which is used when a translation isn't found right to the
  405. * translation id.
  406. *
  407. * @param {string} indicator An indicator.
  408. */
  409. this.translationNotFoundIndicatorRight = function (indicator) {
  410. if (!indicator) {
  411. return $notFoundIndicatorRight;
  412. }
  413. $notFoundIndicatorRight = indicator;
  414. return this;
  415. };
  416. /**
  417. * @ngdoc function
  418. * @name pascalprecht.translate.$translateProvider#fallbackLanguage
  419. * @methodOf pascalprecht.translate.$translateProvider
  420. *
  421. * @description
  422. * Tells the module which of the registered translation tables to use when missing translations
  423. * at initial startup by passing a language key. Similar to `$translateProvider#use`
  424. * only that it says which language to **fallback**.
  425. *
  426. * @param {string||array} langKey A language key.
  427. *
  428. */
  429. this.fallbackLanguage = function (langKey) {
  430. fallbackStack(langKey);
  431. return this;
  432. };
  433. var fallbackStack = function (langKey) {
  434. if (langKey) {
  435. if (angular.isString(langKey)) {
  436. $fallbackWasString = true;
  437. $fallbackLanguage = [ langKey ];
  438. } else if (angular.isArray(langKey)) {
  439. $fallbackWasString = false;
  440. $fallbackLanguage = langKey;
  441. }
  442. if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) {
  443. $fallbackLanguage.push($preferredLanguage);
  444. }
  445. return this;
  446. } else {
  447. if ($fallbackWasString) {
  448. return $fallbackLanguage[0];
  449. } else {
  450. return $fallbackLanguage;
  451. }
  452. }
  453. };
  454. /**
  455. * @ngdoc function
  456. * @name pascalprecht.translate.$translateProvider#use
  457. * @methodOf pascalprecht.translate.$translateProvider
  458. *
  459. * @description
  460. * Set which translation table to use for translation by given language key. When
  461. * trying to 'use' a language which isn't provided, it'll throw an error.
  462. *
  463. * You actually don't have to use this method since `$translateProvider#preferredLanguage`
  464. * does the job too.
  465. *
  466. * @param {string} langKey A language key.
  467. */
  468. this.use = function (langKey) {
  469. if (langKey) {
  470. if (!$translationTable[langKey] && (!$loaderFactory)) {
  471. // only throw an error, when not loading translation data asynchronously
  472. throw new Error("$translateProvider couldn't find translationTable for langKey: '" + langKey + "'");
  473. }
  474. $uses = langKey;
  475. return this;
  476. }
  477. return $uses;
  478. };
  479. /**
  480. * @ngdoc function
  481. * @name pascalprecht.translate.$translateProvider#storageKey
  482. * @methodOf pascalprecht.translate.$translateProvider
  483. *
  484. * @description
  485. * Tells the module which key must represent the choosed language by a user in the storage.
  486. *
  487. * @param {string} key A key for the storage.
  488. */
  489. var storageKey = function(key) {
  490. if (!key) {
  491. if ($storagePrefix) {
  492. return $storagePrefix + $storageKey;
  493. }
  494. return $storageKey;
  495. }
  496. $storageKey = key;
  497. };
  498. this.storageKey = storageKey;
  499. /**
  500. * @ngdoc function
  501. * @name pascalprecht.translate.$translateProvider#useUrlLoader
  502. * @methodOf pascalprecht.translate.$translateProvider
  503. *
  504. * @description
  505. * Tells angular-translate to use `$translateUrlLoader` extension service as loader.
  506. *
  507. * @param {string} url Url
  508. * @param {Object=} options Optional configuration object
  509. */
  510. this.useUrlLoader = function (url, options) {
  511. return this.useLoader('$translateUrlLoader', angular.extend({ url: url }, options));
  512. };
  513. /**
  514. * @ngdoc function
  515. * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader
  516. * @methodOf pascalprecht.translate.$translateProvider
  517. *
  518. * @description
  519. * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader.
  520. *
  521. * @param {Object=} options Optional configuration object
  522. */
  523. this.useStaticFilesLoader = function (options) {
  524. return this.useLoader('$translateStaticFilesLoader', options);
  525. };
  526. /**
  527. * @ngdoc function
  528. * @name pascalprecht.translate.$translateProvider#useLoader
  529. * @methodOf pascalprecht.translate.$translateProvider
  530. *
  531. * @description
  532. * Tells angular-translate to use any other service as loader.
  533. *
  534. * @param {string} loaderFactory Factory name to use
  535. * @param {Object=} options Optional configuration object
  536. */
  537. this.useLoader = function (loaderFactory, options) {
  538. $loaderFactory = loaderFactory;
  539. $loaderOptions = options || {};
  540. return this;
  541. };
  542. /**
  543. * @ngdoc function
  544. * @name pascalprecht.translate.$translateProvider#useLocalStorage
  545. * @methodOf pascalprecht.translate.$translateProvider
  546. *
  547. * @description
  548. * Tells angular-translate to use `$translateLocalStorage` service as storage layer.
  549. *
  550. */
  551. this.useLocalStorage = function () {
  552. return this.useStorage('$translateLocalStorage');
  553. };
  554. /**
  555. * @ngdoc function
  556. * @name pascalprecht.translate.$translateProvider#useCookieStorage
  557. * @methodOf pascalprecht.translate.$translateProvider
  558. *
  559. * @description
  560. * Tells angular-translate to use `$translateCookieStorage` service as storage layer.
  561. */
  562. this.useCookieStorage = function () {
  563. return this.useStorage('$translateCookieStorage');
  564. };
  565. /**
  566. * @ngdoc function
  567. * @name pascalprecht.translate.$translateProvider#useStorage
  568. * @methodOf pascalprecht.translate.$translateProvider
  569. *
  570. * @description
  571. * Tells angular-translate to use custom service as storage layer.
  572. */
  573. this.useStorage = function (storageFactory) {
  574. $storageFactory = storageFactory;
  575. return this;
  576. };
  577. /**
  578. * @ngdoc function
  579. * @name pascalprecht.translate.$translateProvider#storagePrefix
  580. * @methodOf pascalprecht.translate.$translateProvider
  581. *
  582. * @description
  583. * Sets prefix for storage key.
  584. *
  585. * @param {string} prefix Storage key prefix
  586. */
  587. this.storagePrefix = function (prefix) {
  588. if (!prefix) {
  589. return prefix;
  590. }
  591. $storagePrefix = prefix;
  592. return this;
  593. };
  594. /**
  595. * @ngdoc function
  596. * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog
  597. * @methodOf pascalprecht.translate.$translateProvider
  598. *
  599. * @description
  600. * Tells angular-translate to use built-in log handler when trying to translate
  601. * a translation Id which doesn't exist.
  602. *
  603. * This is actually a shortcut method for `useMissingTranslationHandler()`.
  604. *
  605. */
  606. this.useMissingTranslationHandlerLog = function () {
  607. return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
  608. };
  609. /**
  610. * @ngdoc function
  611. * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler
  612. * @methodOf pascalprecht.translate.$translateProvider
  613. *
  614. * @description
  615. * Expects a factory name which later gets instantiated with `$injector`.
  616. * This method can be used to tell angular-translate to use a custom
  617. * missingTranslationHandler. Just build a factory which returns a function
  618. * and expects a translation id as argument.
  619. *
  620. * Example:
  621. * <pre>
  622. * app.config(function ($translateProvider) {
  623. * $translateProvider.useMissingTranslationHandler('customHandler');
  624. * });
  625. *
  626. * app.factory('customHandler', function (dep1, dep2) {
  627. * return function (translationId) {
  628. * // something with translationId and dep1 and dep2
  629. * };
  630. * });
  631. * </pre>
  632. *
  633. * @param {string} factory Factory name
  634. */
  635. this.useMissingTranslationHandler = function (factory) {
  636. $missingTranslationHandlerFactory = factory;
  637. return this;
  638. };
  639. /**
  640. * @ngdoc function
  641. * @name pascalprecht.translate.$translateProvider#usePostCompiling
  642. * @methodOf pascalprecht.translate.$translateProvider
  643. *
  644. * @description
  645. * If post compiling is enabled, all translated values will be processed
  646. * again with AngularJS' $compile.
  647. *
  648. * Example:
  649. * <pre>
  650. * app.config(function ($translateProvider) {
  651. * $translateProvider.usePostCompiling(true);
  652. * });
  653. * </pre>
  654. *
  655. * @param {string} factory Factory name
  656. */
  657. this.usePostCompiling = function (value) {
  658. $postCompilingEnabled = !(!value);
  659. return this;
  660. };
  661. /**
  662. * @ngdoc function
  663. * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage
  664. * @methodOf pascalprecht.translate.$translateProvider
  665. *
  666. * @description
  667. * Tells angular-translate to try to determine on its own which language key
  668. * to set as preferred language. When `fn` is given, angular-translate uses it
  669. * to determine a language key, otherwise it uses the built-in `getLocale()`
  670. * method.
  671. *
  672. * The `getLocale()` returns a language key in the format `[lang]_[country]` or
  673. * `[lang]` depending on what the browser provides.
  674. *
  675. * Use this method at your own risk, since not all browsers return a valid
  676. * locale.
  677. *
  678. * @param {object=} fn Function to determine a browser's locale
  679. */
  680. this.determinePreferredLanguage = function (fn) {
  681. var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale();
  682. if (!$availableLanguageKeys.length) {
  683. $preferredLanguage = locale;
  684. } else {
  685. $preferredLanguage = negotiateLocale(locale);
  686. }
  687. return this;
  688. };
  689. /**
  690. * @ngdoc function
  691. * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys
  692. * @methodOf pascalprecht.translate.$translateProvider
  693. *
  694. * @description
  695. * Registers a set of language keys the app will work with. Use this method in
  696. * combination with
  697. * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
  698. * When available languages keys are registered, angular-translate
  699. * tries to find the best fitting language key depending on the browsers locale,
  700. * considering your language key convention.
  701. *
  702. * @param {object} languageKeys Array of language keys the your app will use
  703. * @param {object=} aliases Alias map.
  704. */
  705. this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
  706. if (languageKeys) {
  707. $availableLanguageKeys = languageKeys;
  708. if (aliases) {
  709. $languageKeyAliases = aliases;
  710. }
  711. return this;
  712. }
  713. return $availableLanguageKeys;
  714. };
  715. /**
  716. * @ngdoc function
  717. * @name pascalprecht.translate.$translateProvider#useLoaderCache
  718. * @methodOf pascalprecht.translate.$translateProvider
  719. *
  720. * @description
  721. * Registers a cache for internal $http based loaders.
  722. * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
  723. * When false the cache will be disabled (default). When true or undefined
  724. * the cache will be a default (see $cacheFactory). When an object it will
  725. * be treat as a cache object itself: the usage is $http({cache: cache})
  726. *
  727. * @param {object} cache boolean, string or cache-object
  728. */
  729. this.useLoaderCache = function (cache) {
  730. if (cache === false) {
  731. // disable cache
  732. loaderCache = undefined;
  733. } else if (cache === true) {
  734. // enable cache using AJS defaults
  735. loaderCache = true;
  736. } else if (typeof(cache) === 'undefined') {
  737. // enable cache using default
  738. loaderCache = '$translationCache';
  739. } else if (cache) {
  740. // enable cache using given one (see $cacheFactory)
  741. loaderCache = cache;
  742. }
  743. return this;
  744. };
  745. /**
  746. * @ngdoc function
  747. * @name pascalprecht.translate.$translateProvider#directivePriority
  748. * @methodOf pascalprecht.translate.$translateProvider
  749. *
  750. * @description
  751. * Sets the default priority of the translate directive. The standard value is `0`.
  752. * Calling this function without an argument will return the current value.
  753. *
  754. * @param {number} priority for the translate-directive
  755. */
  756. this.directivePriority = function (priority) {
  757. if (priority === undefined) {
  758. // getter
  759. return directivePriority;
  760. } else {
  761. // setter with chaining
  762. directivePriority = priority;
  763. return this;
  764. }
  765. };
  766. /**
  767. * @ngdoc object
  768. * @name pascalprecht.translate.$translate
  769. * @requires $interpolate
  770. * @requires $log
  771. * @requires $rootScope
  772. * @requires $q
  773. *
  774. * @description
  775. * The `$translate` service is the actual core of angular-translate. It expects a translation id
  776. * and optional interpolate parameters to translate contents.
  777. *
  778. * <pre>
  779. * $translate('HEADLINE_TEXT').then(function (translation) {
  780. * $scope.translatedText = translation;
  781. * });
  782. * </pre>
  783. *
  784. * @param {string|array} translationId A token which represents a translation id
  785. * This can be optionally an array of translation ids which
  786. * results that the function returns an object where each key
  787. * is the translation id and the value the translation.
  788. * @param {object=} interpolateParams An object hash for dynamic values
  789. * @param {string} interpolationId The id of the interpolation to use
  790. * @returns {object} promise
  791. */
  792. this.$get = [
  793. '$log',
  794. '$injector',
  795. '$rootScope',
  796. '$q',
  797. function ($log, $injector, $rootScope, $q) {
  798. var Storage,
  799. defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
  800. pendingLoader = false,
  801. interpolatorHashMap = {},
  802. langPromises = {},
  803. fallbackIndex,
  804. startFallbackIteration;
  805. var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {
  806. // Duck detection: If the first argument is an array, a bunch of translations was requested.
  807. // The result is an object.
  808. if (angular.isArray(translationId)) {
  809. // Inspired by Q.allSettled by Kris Kowal
  810. // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
  811. // This transforms all promises regardless resolved or rejected
  812. var translateAll = function (translationIds) {
  813. var results = {}; // storing the actual results
  814. var promises = []; // promises to wait for
  815. // Wraps the promise a) being always resolved and b) storing the link id->value
  816. var translate = function (translationId) {
  817. var deferred = $q.defer();
  818. var regardless = function (value) {
  819. results[translationId] = value;
  820. deferred.resolve([translationId, value]);
  821. };
  822. // we don't care whether the promise was resolved or rejected; just store the values
  823. $translate(translationId, interpolateParams, interpolationId, defaultTranslationText).then(regardless, regardless);
  824. return deferred.promise;
  825. };
  826. for (var i = 0, c = translationIds.length; i < c; i++) {
  827. promises.push(translate(translationIds[i]));
  828. }
  829. // wait for all (including storing to results)
  830. return $q.all(promises).then(function () {
  831. // return the results
  832. return results;
  833. });
  834. };
  835. return translateAll(translationId);
  836. }
  837. var deferred = $q.defer();
  838. // trim off any whitespace
  839. if (translationId) {
  840. translationId = trim.apply(translationId);
  841. }
  842. var promiseToWaitFor = (function () {
  843. var promise = $preferredLanguage ?
  844. langPromises[$preferredLanguage] :
  845. langPromises[$uses];
  846. fallbackIndex = 0;
  847. if ($storageFactory && !promise) {
  848. // looks like there's no pending promise for $preferredLanguage or
  849. // $uses. Maybe there's one pending for a language that comes from
  850. // storage.
  851. var langKey = Storage.get($storageKey);
  852. promise = langPromises[langKey];
  853. if ($fallbackLanguage && $fallbackLanguage.length) {
  854. var index = indexOf($fallbackLanguage, langKey);
  855. // maybe the language from storage is also defined as fallback language
  856. // we increase the fallback language index to not search in that language
  857. // as fallback, since it's probably the first used language
  858. // in that case the index starts after the first element
  859. fallbackIndex = (index === 0) ? 1 : 0;
  860. // but we can make sure to ALWAYS fallback to preferred language at least
  861. if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
  862. $fallbackLanguage.push($preferredLanguage);
  863. }
  864. }
  865. }
  866. return promise;
  867. }());
  868. if (!promiseToWaitFor) {
  869. // no promise to wait for? okay. Then there's no loader registered
  870. // nor is a one pending for language that comes from storage.
  871. // We can just translate.
  872. determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
  873. } else {
  874. promiseToWaitFor.then(function () {
  875. determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText).then(deferred.resolve, deferred.reject);
  876. }, deferred.reject);
  877. }
  878. return deferred.promise;
  879. };
  880. /**
  881. * @name applyNotFoundIndicators
  882. * @private
  883. *
  884. * @description
  885. * Applies not fount indicators to given translation id, if needed.
  886. * This function gets only executed, if a translation id doesn't exist,
  887. * which is why a translation id is expected as argument.
  888. *
  889. * @param {string} translationId Translation id.
  890. * @returns {string} Same as given translation id but applied with not found
  891. * indicators.
  892. */
  893. var applyNotFoundIndicators = function (translationId) {
  894. // applying notFoundIndicators
  895. if ($notFoundIndicatorLeft) {
  896. translationId = [$notFoundIndicatorLeft, translationId].join(' ');
  897. }
  898. if ($notFoundIndicatorRight) {
  899. translationId = [translationId, $notFoundIndicatorRight].join(' ');
  900. }
  901. return translationId;
  902. };
  903. /**
  904. * @name useLanguage
  905. * @private
  906. *
  907. * @description
  908. * Makes actual use of a language by setting a given language key as used
  909. * language and informs registered interpolators to also use the given
  910. * key as locale.
  911. *
  912. * @param {key} Locale key.
  913. */
  914. var useLanguage = function (key) {
  915. $uses = key;
  916. $rootScope.$emit('$translateChangeSuccess', {language: key});
  917. if ($storageFactory) {
  918. Storage.put($translate.storageKey(), $uses);
  919. }
  920. // inform default interpolator
  921. defaultInterpolator.setLocale($uses);
  922. // inform all others too!
  923. angular.forEach(interpolatorHashMap, function (interpolator, id) {
  924. interpolatorHashMap[id].setLocale($uses);
  925. });
  926. $rootScope.$emit('$translateChangeEnd', {language: key});
  927. };
  928. /**
  929. * @name loadAsync
  930. * @private
  931. *
  932. * @description
  933. * Kicks of registered async loader using `$injector` and applies existing
  934. * loader options. When resolved, it updates translation tables accordingly
  935. * or rejects with given language key.
  936. *
  937. * @param {string} key Language key.
  938. * @return {Promise} A promise.
  939. */
  940. var loadAsync = function (key) {
  941. if (!key) {
  942. throw 'No language key specified for loading.';
  943. }
  944. var deferred = $q.defer();
  945. $rootScope.$emit('$translateLoadingStart', {language: key});
  946. pendingLoader = true;
  947. var cache = loaderCache;
  948. if (typeof(cache) === 'string') {
  949. // getting on-demand instance of loader
  950. cache = $injector.get(cache);
  951. }
  952. var loaderOptions = angular.extend({}, $loaderOptions, {
  953. key: key,
  954. $http: angular.extend({}, {
  955. cache: cache
  956. }, $loaderOptions.$http)
  957. });
  958. $injector.get($loaderFactory)(loaderOptions).then(function (data) {
  959. var translationTable = {};
  960. $rootScope.$emit('$translateLoadingSuccess', {language: key});
  961. if (angular.isArray(data)) {
  962. angular.forEach(data, function (table) {
  963. angular.extend(translationTable, flatObject(table));
  964. });
  965. } else {
  966. angular.extend(translationTable, flatObject(data));
  967. }
  968. pendingLoader = false;
  969. deferred.resolve({
  970. key: key,
  971. table: translationTable
  972. });
  973. $rootScope.$emit('$translateLoadingEnd', {language: key});
  974. }, function (key) {
  975. $rootScope.$emit('$translateLoadingError', {language: key});
  976. deferred.reject(key);
  977. $rootScope.$emit('$translateLoadingEnd', {language: key});
  978. });
  979. return deferred.promise;
  980. };
  981. if ($storageFactory) {
  982. Storage = $injector.get($storageFactory);
  983. if (!Storage.get || !Storage.put) {
  984. throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!');
  985. }
  986. }
  987. // apply additional settings
  988. if (angular.isFunction(defaultInterpolator.useSanitizeValueStrategy)) {
  989. defaultInterpolator.useSanitizeValueStrategy($interpolationSanitizationStrategy);
  990. }
  991. // if we have additional interpolations that were added via
  992. // $translateProvider.addInterpolation(), we have to map'em
  993. if ($interpolatorFactories.length) {
  994. angular.forEach($interpolatorFactories, function (interpolatorFactory) {
  995. var interpolator = $injector.get(interpolatorFactory);
  996. // setting initial locale for each interpolation service
  997. interpolator.setLocale($preferredLanguage || $uses);
  998. // apply additional settings
  999. if (angular.isFunction(interpolator.useSanitizeValueStrategy)) {
  1000. interpolator.useSanitizeValueStrategy($interpolationSanitizationStrategy);
  1001. }
  1002. // make'em recognizable through id
  1003. interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
  1004. });
  1005. }
  1006. /**
  1007. * @name getTranslationTable
  1008. * @private
  1009. *
  1010. * @description
  1011. * Returns a promise that resolves to the translation table
  1012. * or is rejected if an error occurred.
  1013. *
  1014. * @param langKey
  1015. * @returns {Q.promise}
  1016. */
  1017. var getTranslationTable = function (langKey) {
  1018. var deferred = $q.defer();
  1019. if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) {
  1020. deferred.resolve($translationTable[langKey]);
  1021. } else if (langPromises[langKey]) {
  1022. langPromises[langKey].then(function (data) {
  1023. translations(data.key, data.table);
  1024. deferred.resolve(data.table);
  1025. }, deferred.reject);
  1026. } else {
  1027. deferred.reject();
  1028. }
  1029. return deferred.promise;
  1030. };
  1031. /**
  1032. * @name getFallbackTranslation
  1033. * @private
  1034. *
  1035. * @description
  1036. * Returns a promise that will resolve to the translation
  1037. * or be rejected if no translation was found for the language.
  1038. * This function is currently only used for fallback language translation.
  1039. *
  1040. * @param langKey The language to translate to.
  1041. * @param translationId
  1042. * @param interpolateParams
  1043. * @param Interpolator
  1044. * @returns {Q.promise}
  1045. */
  1046. var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) {
  1047. var deferred = $q.defer();
  1048. getTranslationTable(langKey).then(function (translationTable) {
  1049. if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
  1050. Interpolator.setLocale(langKey);
  1051. var translation = translationTable[translationId];
  1052. if (translation.substr(0, 2) === '@:') {
  1053. getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator)
  1054. .then(deferred.resolve, deferred.reject);
  1055. } else {
  1056. deferred.resolve(Interpolator.interpolate(translationTable[translationId], interpolateParams));
  1057. }
  1058. Interpolator.setLocale($uses);
  1059. } else {
  1060. deferred.reject();
  1061. }
  1062. }, deferred.reject);
  1063. return deferred.promise;
  1064. };
  1065. /**
  1066. * @name getFallbackTranslationInstant
  1067. * @private
  1068. *
  1069. * @description
  1070. * Returns a translation
  1071. * This function is currently only used for fallback language translation.
  1072. *
  1073. * @param langKey The language to translate to.
  1074. * @param translationId
  1075. * @param interpolateParams
  1076. * @param Interpolator
  1077. * @returns {string} translation
  1078. */
  1079. var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator) {
  1080. var result, translationTable = $translationTable[langKey];
  1081. if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) {
  1082. Interpolator.setLocale(langKey);
  1083. result = Interpolator.interpolate(translationTable[translationId], interpolateParams);
  1084. if (result.substr(0, 2) === '@:') {
  1085. return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator);
  1086. }
  1087. Interpolator.setLocale($uses);
  1088. }
  1089. return result;
  1090. };
  1091. /**
  1092. * @name translateByHandler
  1093. * @private
  1094. *
  1095. * Translate by missing translation handler.
  1096. *
  1097. * @param translationId
  1098. * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
  1099. * absent
  1100. */
  1101. var translateByHandler = function (translationId) {
  1102. // If we have a handler factory - we might also call it here to determine if it provides
  1103. // a default text for a translationid that can't be found anywhere in our tables
  1104. if ($missingTranslationHandlerFactory) {
  1105. var resultString = $injector.get($missingTranslationHandlerFactory)(translationId, $uses);
  1106. if (resultString !== undefined) {
  1107. return resultString;
  1108. } else {
  1109. return translationId;
  1110. }
  1111. } else {
  1112. return translationId;
  1113. }
  1114. };
  1115. /**
  1116. * @name resolveForFallbackLanguage
  1117. * @private
  1118. *
  1119. * Recursive helper function for fallbackTranslation that will sequentially look
  1120. * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
  1121. *
  1122. * @param fallbackLanguageIndex
  1123. * @param translationId
  1124. * @param interpolateParams
  1125. * @param Interpolator
  1126. * @returns {Q.promise} Promise that will resolve to the translation.
  1127. */
  1128. var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) {
  1129. var deferred = $q.defer();
  1130. if (fallbackLanguageIndex < $fallbackLanguage.length) {
  1131. var langKey = $fallbackLanguage[fallbackLanguageIndex];
  1132. getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).then(
  1133. deferred.resolve,
  1134. function () {
  1135. // Look in the next fallback language for a translation.
  1136. // It delays the resolving by passing another promise to resolve.
  1137. resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText).then(deferred.resolve);
  1138. }
  1139. );
  1140. } else {
  1141. // No translation found in any fallback language
  1142. // if a default translation text is set in the directive, then return this as a result
  1143. if (defaultTranslationText) {
  1144. deferred.resolve(defaultTranslationText);
  1145. } else {
  1146. // if no default translation is set and an error handler is defined, send it to the handler
  1147. // and then return the result
  1148. deferred.resolve(translateByHandler(translationId));
  1149. }
  1150. }
  1151. return deferred.promise;
  1152. };
  1153. /**
  1154. * @name resolveForFallbackLanguageInstant
  1155. * @private
  1156. *
  1157. * Recursive helper function for fallbackTranslation that will sequentially look
  1158. * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
  1159. *
  1160. * @param fallbackLanguageIndex
  1161. * @param translationId
  1162. * @param interpolateParams
  1163. * @param Interpolator
  1164. * @returns {string} translation
  1165. */
  1166. var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator) {
  1167. var result;
  1168. if (fallbackLanguageIndex < $fallbackLanguage.length) {
  1169. var langKey = $fallbackLanguage[fallbackLanguageIndex];
  1170. result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator);
  1171. if (!result) {
  1172. result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
  1173. }
  1174. }
  1175. return result;
  1176. };
  1177. /**
  1178. * Translates with the usage of the fallback languages.
  1179. *
  1180. * @param translationId
  1181. * @param interpolateParams
  1182. * @param Interpolator
  1183. * @returns {Q.promise} Promise, that resolves to the translation.
  1184. */
  1185. var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) {
  1186. // Start with the fallbackLanguage with index 0
  1187. return resolveForFallbackLanguage((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText);
  1188. };
  1189. /**
  1190. * Translates with the usage of the fallback languages.
  1191. *
  1192. * @param translationId
  1193. * @param interpolateParams
  1194. * @param Interpolator
  1195. * @returns {String} translation
  1196. */
  1197. var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator) {
  1198. // Start with the fallbackLanguage with index 0
  1199. return resolveForFallbackLanguageInstant((startFallbackIteration>0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator);
  1200. };
  1201. var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText) {
  1202. var deferred = $q.defer();
  1203. var table = $uses ? $translationTable[$uses] : $translationTable,
  1204. Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;
  1205. // if the translation id exists, we can just interpolate it
  1206. if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
  1207. var translation = table[translationId];
  1208. // If using link, rerun $translate with linked translationId and return it
  1209. if (translation.substr(0, 2) === '@:') {
  1210. $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText)
  1211. .then(deferred.resolve, deferred.reject);
  1212. } else {
  1213. deferred.resolve(Interpolator.interpolate(translation, interpolateParams));
  1214. }
  1215. } else {
  1216. var missingTranslationHandlerTranslation;
  1217. // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
  1218. if ($missingTranslationHandlerFactory && !pendingLoader) {
  1219. missingTranslationHandlerTranslation = translateByHandler(translationId);
  1220. }
  1221. // since we couldn't translate the inital requested translation id,
  1222. // we try it now with one or more fallback languages, if fallback language(s) is
  1223. // configured.
  1224. if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
  1225. fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText)
  1226. .then(function (translation) {
  1227. deferred.resolve(translation);
  1228. }, function (_translationId) {
  1229. deferred.reject(applyNotFoundIndicators(_translationId));
  1230. });
  1231. } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
  1232. // looks like the requested translation id doesn't exists.
  1233. // Now, if there is a registered handler for missing translations and no
  1234. // asyncLoader is pending, we execute the handler
  1235. if (defaultTranslationText) {
  1236. deferred.resolve(defaultTranslationText);
  1237. } else {
  1238. deferred.resolve(missingTranslationHandlerTranslation);
  1239. }
  1240. } else {
  1241. if (defaultTranslationText) {
  1242. deferred.resolve(defaultTranslationText);
  1243. } else {
  1244. deferred.reject(applyNotFoundIndicators(translationId));
  1245. }
  1246. }
  1247. }
  1248. return deferred.promise;
  1249. };
  1250. var determineTranslationInstant = function (translationId, interpolateParams, interpolationId) {
  1251. var result, table = $uses ? $translationTable[$uses] : $translationTable,
  1252. Interpolator = defaultInterpolator;
  1253. // if the interpolation id exists use custom interpolator
  1254. if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
  1255. Interpolator = interpolatorHashMap[interpolationId];
  1256. }
  1257. // if the translation id exists, we can just interpolate it
  1258. if (table && Object.prototype.hasOwnProperty.call(table, translationId)) {
  1259. var translation = table[translationId];
  1260. // If using link, rerun $translate with linked translationId and return it
  1261. if (translation.substr(0, 2) === '@:') {
  1262. result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId);
  1263. } else {
  1264. result = Interpolator.interpolate(translation, interpolateParams);
  1265. }
  1266. } else {
  1267. var missingTranslationHandlerTranslation;
  1268. // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
  1269. if ($missingTranslationHandlerFactory && !pendingLoader) {
  1270. missingTranslationHandlerTranslation = translateByHandler(translationId);
  1271. }
  1272. // since we couldn't translate the inital requested translation id,
  1273. // we try it now with one or more fallback languages, if fallback language(s) is
  1274. // configured.
  1275. if ($uses && $fallbackLanguage && $fallbackLanguage.length) {
  1276. fallbackIndex = 0;
  1277. result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator);
  1278. } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
  1279. // looks like the requested translation id doesn't exists.
  1280. // Now, if there is a registered handler for missing translations and no
  1281. // asyncLoader is pending, we execute the handler
  1282. result = missingTranslationHandlerTranslation;
  1283. } else {
  1284. result = applyNotFoundIndicators(translationId);
  1285. }
  1286. }
  1287. return result;
  1288. };
  1289. /**
  1290. * @ngdoc function
  1291. * @name pascalprecht.translate.$translate#preferredLanguage
  1292. * @methodOf pascalprecht.translate.$translate
  1293. *
  1294. * @description
  1295. * Returns the language key for the preferred language.
  1296. *
  1297. * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime)
  1298. *
  1299. * @return {string} preferred language key
  1300. */
  1301. $translate.preferredLanguage = function (langKey) {
  1302. if(langKey) {
  1303. setupPreferredLanguage(langKey);
  1304. }
  1305. return $preferredLanguage;
  1306. };
  1307. /**
  1308. * @ngdoc function
  1309. * @name pascalprecht.translate.$translate#cloakClassName
  1310. * @methodOf pascalprecht.translate.$translate
  1311. *
  1312. * @description
  1313. * Returns the configured class name for `translate-cloak` directive.
  1314. *
  1315. * @return {string} cloakClassName
  1316. */
  1317. $translate.cloakClassName = function () {
  1318. return $cloakClassName;
  1319. };
  1320. /**
  1321. * @ngdoc function
  1322. * @name pascalprecht.translate.$translate#fallbackLanguage
  1323. * @methodOf pascalprecht.translate.$translate
  1324. *
  1325. * @description
  1326. * Returns the language key for the fallback languages or sets a new fallback stack.
  1327. *
  1328. * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime)
  1329. *
  1330. * @return {string||array} fallback language key
  1331. */
  1332. $translate.fallbackLanguage = function (langKey) {
  1333. if (langKey !== undefined && langKey !== null) {
  1334. fallbackStack(langKey);
  1335. // as we might have an async loader initiated and a new translation language might have been defined
  1336. // we need to add the promise to the stack also. So - iterate.
  1337. if ($loaderFactory) {
  1338. if ($fallbackLanguage && $fallbackLanguage.length) {
  1339. for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
  1340. if (!langPromises[$fallbackLanguage[i]]) {
  1341. langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
  1342. }
  1343. }
  1344. }
  1345. }
  1346. $translate.use($translate.use());
  1347. }
  1348. if ($fallbackWasString) {
  1349. return $fallbackLanguage[0];
  1350. } else {
  1351. return $fallbackLanguage;
  1352. }
  1353. };
  1354. /**
  1355. * @ngdoc function
  1356. * @name pascalprecht.translate.$translate#useFallbackLanguage
  1357. * @methodOf pascalprecht.translate.$translate
  1358. *
  1359. * @description
  1360. * Sets the first key of the fallback language stack to be used for translation.
  1361. * Therefore all languages in the fallback array BEFORE this key will be skipped!
  1362. *
  1363. * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to
  1364. * get back to the whole stack
  1365. */
  1366. $translate.useFallbackLanguage = function (langKey) {
  1367. if (langKey !== undefined && langKey !== null) {
  1368. if (!langKey) {
  1369. startFallbackIteration = 0;
  1370. } else {
  1371. var langKeyPosition = indexOf($fallbackLanguage, langKey);
  1372. if (langKeyPosition > -1) {
  1373. startFallbackIteration = langKeyPosition;
  1374. }
  1375. }
  1376. }
  1377. };
  1378. /**
  1379. * @ngdoc function
  1380. * @name pascalprecht.translate.$translate#proposedLanguage
  1381. * @methodOf pascalprecht.translate.$translate
  1382. *
  1383. * @description
  1384. * Returns the language key of language that is currently loaded asynchronously.
  1385. *
  1386. * @return {string} language key
  1387. */
  1388. $translate.proposedLanguage = function () {
  1389. return $nextLang;
  1390. };
  1391. /**
  1392. * @ngdoc function
  1393. * @name pascalprecht.translate.$translate#storage
  1394. * @methodOf pascalprecht.translate.$translate
  1395. *
  1396. * @description
  1397. * Returns registered storage.
  1398. *
  1399. * @return {object} Storage
  1400. */
  1401. $translate.storage = function () {
  1402. return Storage;
  1403. };
  1404. /**
  1405. * @ngdoc function
  1406. * @name pascalprecht.translate.$translate#use
  1407. * @methodOf pascalprecht.translate.$translate
  1408. *
  1409. * @description
  1410. * Tells angular-translate which language to use by given language key. This method is
  1411. * used to change language at runtime. It also takes care of storing the language
  1412. * key in a configured store to let your app remember the choosed language.
  1413. *
  1414. * When trying to 'use' a language which isn't available it tries to load it
  1415. * asynchronously with registered loaders.
  1416. *
  1417. * Returns promise object with loaded language file data
  1418. * @example
  1419. * $translate.use("en_US").then(function(data){
  1420. * $scope.text = $translate("HELLO");
  1421. * });
  1422. *
  1423. * @param {string} key Language key
  1424. * @return {string} Language key
  1425. */
  1426. $translate.use = function (key) {
  1427. if (!key) {
  1428. return $uses;
  1429. }
  1430. var deferred = $q.defer();
  1431. $rootScope.$emit('$translateChangeStart', {language: key});
  1432. // Try to get the aliased language key
  1433. var aliasedKey = negotiateLocale(key);
  1434. if (aliasedKey) {
  1435. key = aliasedKey;
  1436. }
  1437. // if there isn't a translation table for the language we've requested,
  1438. // we load it asynchronously
  1439. if (!$translationTable[key] && $loaderFactory && !langPromises[key]) {
  1440. $nextLang = key;
  1441. langPromises[key] = loadAsync(key).then(function (translation) {
  1442. translations(translation.key, translation.table);
  1443. deferred.resolve(translation.key);
  1444. useLanguage(translation.key);
  1445. if ($nextLang === key) {
  1446. $nextLang = undefined;
  1447. }
  1448. return translation;
  1449. }, function (key) {
  1450. if ($nextLang === key) {
  1451. $nextLang = undefined;
  1452. }
  1453. $rootScope.$emit('$translateChangeError', {language: key});
  1454. deferred.reject(key);
  1455. $rootScope.$emit('$translateChangeEnd', {language: key});
  1456. });
  1457. } else {
  1458. deferred.resolve(key);
  1459. useLanguage(key);
  1460. }
  1461. return deferred.promise;
  1462. };
  1463. /**
  1464. * @ngdoc function
  1465. * @name pascalprecht.translate.$translate#storageKey
  1466. * @methodOf pascalprecht.translate.$translate
  1467. *
  1468. * @description
  1469. * Returns the key for the storage.
  1470. *
  1471. * @return {string} storage key
  1472. */
  1473. $translate.storageKey = function () {
  1474. return storageKey();
  1475. };
  1476. /**
  1477. * @ngdoc function
  1478. * @name pascalprecht.translate.$translate#isPostCompilingEnabled
  1479. * @methodOf pascalprecht.translate.$translate
  1480. *
  1481. * @description
  1482. * Returns whether post compiling is enabled or not
  1483. *
  1484. * @return {bool} storage key
  1485. */
  1486. $translate.isPostCompilingEnabled = function () {
  1487. return $postCompilingEnabled;
  1488. };
  1489. /**
  1490. * @ngdoc function
  1491. * @name pascalprecht.translate.$translate#refresh
  1492. * @methodOf pascalprecht.translate.$translate
  1493. *
  1494. * @description
  1495. * Refreshes a translation table pointed by the given langKey. If langKey is not specified,
  1496. * the module will drop all existent translation tables and load new version of those which
  1497. * are currently in use.
  1498. *
  1499. * Refresh means that the module will drop target translation table and try to load it again.
  1500. *
  1501. * In case there are no loaders registered the refresh() method will throw an Error.
  1502. *
  1503. * If the module is able to refresh translation tables refresh() method will broadcast
  1504. * $translateRefreshStart and $translateRefreshEnd events.
  1505. *
  1506. * @example
  1507. * // this will drop all currently existent translation tables and reload those which are
  1508. * // currently in use
  1509. * $translate.refresh();
  1510. * // this will refresh a translation table for the en_US language
  1511. * $translate.refresh('en_US');
  1512. *
  1513. * @param {string} langKey A language key of the table, which has to be refreshed
  1514. *
  1515. * @return {promise} Promise, which will be resolved in case a translation tables refreshing
  1516. * process is finished successfully, and reject if not.
  1517. */
  1518. $translate.refresh = function (langKey) {
  1519. if (!$loaderFactory) {
  1520. throw new Error('Couldn\'t refresh translation table, no loader registered!');
  1521. }
  1522. var deferred = $q.defer();
  1523. function resolve() {
  1524. deferred.resolve();
  1525. $rootScope.$emit('$translateRefreshEnd', {language: langKey});
  1526. }
  1527. function reject() {
  1528. deferred.reject();
  1529. $rootScope.$emit('$translateRefreshEnd', {language: langKey});
  1530. }
  1531. $rootScope.$emit('$translateRefreshStart', {language: langKey});
  1532. if (!langKey) {
  1533. // if there's no language key specified we refresh ALL THE THINGS!
  1534. var tables = [], loadingKeys = {};
  1535. // reload registered fallback languages
  1536. if ($fallbackLanguage && $fallbackLanguage.length) {
  1537. for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
  1538. tables.push(loadAsync($fallbackLanguage[i]));
  1539. loadingKeys[$fallbackLanguage[i]] = true;
  1540. }
  1541. }
  1542. // reload currently used language
  1543. if ($uses && !loadingKeys[$uses]) {
  1544. tables.push(loadAsync($uses));
  1545. }
  1546. $q.all(tables).then(function (tableData) {
  1547. angular.forEach(tableData, function (data) {
  1548. if ($translationTable[data.key]) {
  1549. delete $translationTable[data.key];
  1550. }
  1551. translations(data.key, data.table);
  1552. });
  1553. if ($uses) {
  1554. useLanguage($uses);
  1555. }
  1556. resolve();
  1557. });
  1558. } else if ($translationTable[langKey]) {
  1559. loadAsync(langKey).then(function (data) {
  1560. translations(data.key, data.table);
  1561. if (langKey === $uses) {
  1562. useLanguage($uses);
  1563. }
  1564. resolve();
  1565. }, reject);
  1566. } else {
  1567. reject();
  1568. }
  1569. return deferred.promise;
  1570. };
  1571. /**
  1572. * @ngdoc function
  1573. * @name pascalprecht.translate.$translate#instant
  1574. * @methodOf pascalprecht.translate.$translate
  1575. *
  1576. * @description
  1577. * Returns a translation instantly from the internal state of loaded translation. All rules
  1578. * regarding the current language, the preferred language of even fallback languages will be
  1579. * used except any promise handling. If a language was not found, an asynchronous loading
  1580. * will be invoked in the background.
  1581. *
  1582. * @param {string|array} translationId A token which represents a translation id
  1583. * This can be optionally an array of translation ids which
  1584. * results that the function's promise returns an object where
  1585. * each key is the translation id and the value the translation.
  1586. * @param {object} interpolateParams Params
  1587. * @param {string} interpolationId The id of the interpolation to use
  1588. *
  1589. * @return {string} translation
  1590. */
  1591. $translate.instant = function (translationId, interpolateParams, interpolationId) {
  1592. // Detect undefined and null values to shorten the execution and prevent exceptions
  1593. if (translationId === null || angular.isUndefined(translationId)) {
  1594. return translationId;
  1595. }
  1596. // Duck detection: If the first argument is an array, a bunch of translations was requested.
  1597. // The result is an object.
  1598. if (angular.isArray(translationId)) {
  1599. var results = {};
  1600. for (var i = 0, c = translationId.length; i < c; i++) {
  1601. results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId);
  1602. }
  1603. return results;
  1604. }
  1605. // We discarded unacceptable values. So we just need to verify if translationId is empty String
  1606. if (angular.isString(translationId) && translationId.length < 1) {
  1607. return translationId;
  1608. }
  1609. // trim off any whitespace
  1610. if (translationId) {
  1611. translationId = trim.apply(translationId);
  1612. }
  1613. var result, possibleLangKeys = [];
  1614. if ($preferredLanguage) {
  1615. possibleLangKeys.push($preferredLanguage);
  1616. }
  1617. if ($uses) {
  1618. possibleLangKeys.push($uses);
  1619. }
  1620. if ($fallbackLanguage && $fallbackLanguage.length) {
  1621. possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
  1622. }
  1623. for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
  1624. var possibleLangKey = possibleLangKeys[j];
  1625. if ($translationTable[possibleLangKey]) {
  1626. if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
  1627. result = determineTranslationInstant(translationId, interpolateParams, interpolationId);
  1628. } else if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
  1629. result = applyNotFoundIndicators(translationId);
  1630. }
  1631. }
  1632. if (typeof result !== 'undefined') {
  1633. break;
  1634. }
  1635. }
  1636. if (!result && result !== '') {
  1637. // Return translation of default interpolator if not found anything.
  1638. result = defaultInterpolator.interpolate(translationId, interpolateParams);
  1639. if ($missingTranslationHandlerFactory && !pendingLoader) {
  1640. result = translateByHandler(translationId);
  1641. }
  1642. }
  1643. return result;
  1644. };
  1645. /**
  1646. * @ngdoc function
  1647. * @name pascalprecht.translate.$translate#versionInfo
  1648. * @methodOf pascalprecht.translate.$translate
  1649. *
  1650. * @description
  1651. * Returns the current version information for the angular-translate library
  1652. *
  1653. * @return {string} angular-translate version
  1654. */
  1655. $translate.versionInfo = function () {
  1656. return version;
  1657. };
  1658. /**
  1659. * @ngdoc function
  1660. * @name pascalprecht.translate.$translate#loaderCache
  1661. * @methodOf pascalprecht.translate.$translate
  1662. *
  1663. * @description
  1664. * Returns the defined loaderCache.
  1665. *
  1666. * @return {boolean|string|object} current value of loaderCache
  1667. */
  1668. $translate.loaderCache = function () {
  1669. return loaderCache;
  1670. };
  1671. // internal purpose only
  1672. $translate.directivePriority = function () {
  1673. return directivePriority;
  1674. };
  1675. if ($loaderFactory) {
  1676. // If at least one async loader is defined and there are no
  1677. // (default) translations available we should try to load them.
  1678. if (angular.equals($translationTable, {})) {
  1679. $translate.use($translate.use());
  1680. }
  1681. // Also, if there are any fallback language registered, we start
  1682. // loading them asynchronously as soon as we can.
  1683. if ($fallbackLanguage && $fallbackLanguage.length) {
  1684. var processAsyncResult = function (translation) {
  1685. translations(translation.key, translation.table);
  1686. $rootScope.$emit('$translateChangeEnd', { language: translation.key });
  1687. return translation;
  1688. };
  1689. for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
  1690. langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]).then(processAsyncResult);
  1691. }
  1692. }
  1693. }
  1694. return $translate;
  1695. }
  1696. ];
  1697. }]);
  1698. /**
  1699. * @ngdoc object
  1700. * @name pascalprecht.translate.$translateDefaultInterpolation
  1701. * @requires $interpolate
  1702. *
  1703. * @description
  1704. * Uses angular's `$interpolate` services to interpolate strings against some values.
  1705. *
  1706. * @return {object} $translateInterpolator Interpolator service
  1707. */
  1708. angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', ['$interpolate', function ($interpolate) {
  1709. var $translateInterpolator = {},
  1710. $locale,
  1711. $identifier = 'default',
  1712. $sanitizeValueStrategy = null,
  1713. // map of all sanitize strategies
  1714. sanitizeValueStrategies = {
  1715. escaped: function (params) {
  1716. var result = {};
  1717. for (var key in params) {
  1718. if (Object.prototype.hasOwnProperty.call(params, key)) {
  1719. if (angular.isNumber(params[key])) {
  1720. result[key] = params[key];
  1721. } else {
  1722. result[key] = angular.element('<div></div>').text(params[key]).html();
  1723. }
  1724. }
  1725. }
  1726. return result;
  1727. }
  1728. };
  1729. var sanitizeParams = function (params) {
  1730. var result;
  1731. if (angular.isFunction(sanitizeValueStrategies[$sanitizeValueStrategy])) {
  1732. result = sanitizeValueStrategies[$sanitizeValueStrategy](params);
  1733. } else {
  1734. result = params;
  1735. }
  1736. return result;
  1737. };
  1738. /**
  1739. * @ngdoc function
  1740. * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale
  1741. * @methodOf pascalprecht.translate.$translateDefaultInterpolation
  1742. *
  1743. * @description
  1744. * Sets current locale (this is currently not use in this interpolation).
  1745. *
  1746. * @param {string} locale Language key or locale.
  1747. */
  1748. $translateInterpolator.setLocale = function (locale) {
  1749. $locale = locale;
  1750. };
  1751. /**
  1752. * @ngdoc function
  1753. * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier
  1754. * @methodOf pascalprecht.translate.$translateDefaultInterpolation
  1755. *
  1756. * @description
  1757. * Returns an identifier for this interpolation service.
  1758. *
  1759. * @returns {string} $identifier
  1760. */
  1761. $translateInterpolator.getInterpolationIdentifier = function () {
  1762. return $identifier;
  1763. };
  1764. $translateInterpolator.useSanitizeValueStrategy = function (value) {
  1765. $sanitizeValueStrategy = value;
  1766. return this;
  1767. };
  1768. /**
  1769. * @ngdoc function
  1770. * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate
  1771. * @methodOf pascalprecht.translate.$translateDefaultInterpolation
  1772. *
  1773. * @description
  1774. * Interpolates given string agains given interpolate params using angulars
  1775. * `$interpolate` service.
  1776. *
  1777. * @returns {string} interpolated string.
  1778. */
  1779. $translateInterpolator.interpolate = function (string, interpolateParams) {
  1780. if ($sanitizeValueStrategy) {
  1781. interpolateParams = sanitizeParams(interpolateParams);
  1782. }
  1783. return $interpolate(string)(interpolateParams || {});
  1784. };
  1785. return $translateInterpolator;
  1786. }]);
  1787. angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
  1788. angular.module('pascalprecht.translate')
  1789. /**
  1790. * @ngdoc directive
  1791. * @name pascalprecht.translate.directive:translate
  1792. * @requires $compile
  1793. * @requires $filter
  1794. * @requires $interpolate
  1795. * @restrict A
  1796. *
  1797. * @description
  1798. * Translates given translation id either through attribute or DOM content.
  1799. * Internally it uses `translate` filter to translate translation id. It possible to
  1800. * pass an optional `translate-values` object literal as string into translation id.
  1801. *
  1802. * @param {string=} translate Translation id which could be either string or interpolated string.
  1803. * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
  1804. * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
  1805. * @param {string=} translate-default will be used unless translation was successful
  1806. * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translate#usePostCompiling}
  1807. *
  1808. * @example
  1809. <example module="ngView">
  1810. <file name="index.html">
  1811. <div ng-controller="TranslateCtrl">
  1812. <pre translate="TRANSLATION_ID"></pre>
  1813. <pre translate>TRANSLATION_ID</pre>
  1814. <pre translate translate-attr-title="TRANSLATION_ID"></pre>
  1815. <pre translate="{{translationId}}"></pre>
  1816. <pre translate>{{translationId}}</pre>
  1817. <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
  1818. <pre translate translate-values="{value: 5}">WITH_VALUES</pre>
  1819. <pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
  1820. <pre translate translate-values="{{values}}">WITH_VALUES</pre>
  1821. <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>
  1822. </div>
  1823. </file>
  1824. <file name="script.js">
  1825. angular.module('ngView', ['pascalprecht.translate'])
  1826. .config(function ($translateProvider) {
  1827. $translateProvider.translations('en',{
  1828. 'TRANSLATION_ID': 'Hello there!',
  1829. 'WITH_VALUES': 'The following value is dynamic: {{value}}'
  1830. }).preferredLanguage('en');
  1831. });
  1832. angular.module('ngView').controller('TranslateCtrl', function ($scope) {
  1833. $scope.translationId = 'TRANSLATION_ID';
  1834. $scope.values = {
  1835. value: 78
  1836. };
  1837. });
  1838. </file>
  1839. <file name="scenario.js">
  1840. it('should translate', function () {
  1841. inject(function ($rootScope, $compile) {
  1842. $rootScope.translationId = 'TRANSLATION_ID';
  1843. element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope);
  1844. $rootScope.$digest();
  1845. expect(element.text()).toBe('Hello there!');
  1846. element = $compile('<p translate="{{translationId}}"></p>')($rootScope);
  1847. $rootScope.$digest();
  1848. expect(element.text()).toBe('Hello there!');
  1849. element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope);
  1850. $rootScope.$digest();
  1851. expect(element.text()).toBe('Hello there!');
  1852. element = $compile('<p translate>{{translationId}}</p>')($rootScope);
  1853. $rootScope.$digest();
  1854. expect(element.text()).toBe('Hello there!');
  1855. element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope);
  1856. $rootScope.$digest();
  1857. expect(element.attr('title')).toBe('Hello there!');
  1858. });
  1859. });
  1860. </file>
  1861. </example>
  1862. */
  1863. .directive('translate', ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope', function ($translate, $q, $interpolate, $compile, $parse, $rootScope) {
  1864. /**
  1865. * @name trim
  1866. * @private
  1867. *
  1868. * @description
  1869. * trim polyfill
  1870. *
  1871. * @returns {string} The string stripped of whitespace from both ends
  1872. */
  1873. var trim = function() {
  1874. return this.replace(/^\s+|\s+$/g, '');
  1875. };
  1876. return {
  1877. restrict: 'AE',
  1878. scope: true,
  1879. priority: $translate.directivePriority(),
  1880. compile: function (tElement, tAttr) {
  1881. var translateValuesExist = (tAttr.translateValues) ?
  1882. tAttr.translateValues : undefined;
  1883. var translateInterpolation = (tAttr.translateInterpolation) ?
  1884. tAttr.translateInterpolation : undefined;
  1885. var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
  1886. var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)',
  1887. watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)';
  1888. return function linkFn(scope, iElement, iAttr) {
  1889. scope.interpolateParams = {};
  1890. scope.preText = '';
  1891. scope.postText = '';
  1892. var translationIds = {};
  1893. // Ensures any change of the attribute "translate" containing the id will
  1894. // be re-stored to the scope's "translationId".
  1895. // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
  1896. var observeElementTranslation = function (translationId) {
  1897. // Remove any old watcher
  1898. if (angular.isFunction(observeElementTranslation._unwatchOld)) {
  1899. observeElementTranslation._unwatchOld();
  1900. observeElementTranslation._unwatchOld = undefined;
  1901. }
  1902. if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
  1903. // Resolve translation id by inner html if required
  1904. var interpolateMatches = trim.apply(iElement.text()).match(interpolateRegExp);
  1905. // Interpolate translation id if required
  1906. if (angular.isArray(interpolateMatches)) {
  1907. scope.preText = interpolateMatches[1];
  1908. scope.postText = interpolateMatches[3];
  1909. translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent);
  1910. var watcherMatches = iElement.text().match(watcherRegExp);
  1911. if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) {
  1912. observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) {
  1913. translationIds.translate = newValue;
  1914. updateTranslations();
  1915. });
  1916. }
  1917. } else {
  1918. translationIds.translate = iElement.text().replace(/^\s+|\s+$/g,'');
  1919. }
  1920. } else {
  1921. translationIds.translate = translationId;
  1922. }
  1923. updateTranslations();
  1924. };
  1925. var observeAttributeTranslation = function (translateAttr) {
  1926. iAttr.$observe(translateAttr, function (translationId) {
  1927. translationIds[translateAttr] = translationId;
  1928. updateTranslations();
  1929. });
  1930. };
  1931. var firstAttributeChangedEvent = true;
  1932. iAttr.$observe('translate', function (translationId) {
  1933. if (typeof translationId === 'undefined') {
  1934. // case of element "<translate>xyz</translate>"
  1935. observeElementTranslation('');
  1936. } else {
  1937. // case of regular attribute
  1938. if (translationId !== '' || !firstAttributeChangedEvent) {
  1939. translationIds.translate = translationId;
  1940. updateTranslations();
  1941. }
  1942. }
  1943. firstAttributeChangedEvent = false;
  1944. });
  1945. for (var translateAttr in iAttr) {
  1946. if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') {
  1947. observeAttributeTranslation(translateAttr);
  1948. }
  1949. }
  1950. iAttr.$observe('translateDefault', function (value) {
  1951. scope.defaultText = value;
  1952. });
  1953. if (translateValuesExist) {
  1954. iAttr.$observe('translateValues', function (interpolateParams) {
  1955. if (interpolateParams) {
  1956. scope.$parent.$watch(function () {
  1957. angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
  1958. });
  1959. }
  1960. });
  1961. }
  1962. if (translateValueExist) {
  1963. var observeValueAttribute = function (attrName) {
  1964. iAttr.$observe(attrName, function (value) {
  1965. var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
  1966. scope.interpolateParams[attributeName] = value;
  1967. });
  1968. };
  1969. for (var attr in iAttr) {
  1970. if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
  1971. observeValueAttribute(attr);
  1972. }
  1973. }
  1974. }
  1975. // Master update function
  1976. var updateTranslations = function () {
  1977. for (var key in translationIds) {
  1978. if (translationIds.hasOwnProperty(key)) {
  1979. updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText);
  1980. }
  1981. }
  1982. };
  1983. // Put translation processing function outside loop
  1984. var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText) {
  1985. if (translationId) {
  1986. $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText)
  1987. .then(function (translation) {
  1988. applyTranslation(translation, scope, true, translateAttr);
  1989. }, function (translationId) {
  1990. applyTranslation(translationId, scope, false, translateAttr);
  1991. });
  1992. } else {
  1993. // as an empty string cannot be translated, we can solve this using successful=false
  1994. applyTranslation(translationId, scope, false, translateAttr);
  1995. }
  1996. };
  1997. var applyTranslation = function (value, scope, successful, translateAttr) {
  1998. if (translateAttr === 'translate') {
  1999. // default translate into innerHTML
  2000. if (!successful && typeof scope.defaultText !== 'undefined') {
  2001. value = scope.defaultText;
  2002. }
  2003. iElement.html(scope.preText + value + scope.postText);
  2004. var globallyEnabled = $translate.isPostCompilingEnabled();
  2005. var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
  2006. var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
  2007. if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
  2008. $compile(iElement.contents())(scope);
  2009. }
  2010. } else {
  2011. // translate attribute
  2012. if (!successful && typeof scope.defaultText !== 'undefined') {
  2013. value = scope.defaultText;
  2014. }
  2015. var attributeName = iAttr.$attr[translateAttr].substr(15);
  2016. iElement.attr(attributeName, value);
  2017. }
  2018. };
  2019. scope.$watch('interpolateParams', updateTranslations, true);
  2020. // Ensures the text will be refreshed after the current language was changed
  2021. // w/ $translate.use(...)
  2022. var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
  2023. // ensure translation will be looked up at least one
  2024. if (iElement.text().length) {
  2025. observeElementTranslation('');
  2026. }
  2027. updateTranslations();
  2028. scope.$on('$destroy', unbind);
  2029. };
  2030. }
  2031. };
  2032. }]);
  2033. angular.module('pascalprecht.translate')
  2034. /**
  2035. * @ngdoc directive
  2036. * @name pascalprecht.translate.directive:translateCloak
  2037. * @requires $rootScope
  2038. * @requires $translate
  2039. * @restrict A
  2040. *
  2041. * $description
  2042. * Adds a `translate-cloak` class name to the given element where this directive
  2043. * is applied initially and removes it, once a loader has finished loading.
  2044. *
  2045. * This directive can be used to prevent initial flickering when loading translation
  2046. * data asynchronously.
  2047. *
  2048. * The class name is defined in
  2049. * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}.
  2050. *
  2051. * @param {string=} translate-cloak If a translationId is provided, it will be used for showing
  2052. * or hiding the cloak. Basically it relies on the translation
  2053. * resolve.
  2054. */
  2055. .directive('translateCloak', ['$rootScope', '$translate', function ($rootScope, $translate) {
  2056. return {
  2057. compile: function (tElement) {
  2058. var applyCloak = function () {
  2059. tElement.addClass($translate.cloakClassName());
  2060. },
  2061. removeCloak = function () {
  2062. tElement.removeClass($translate.cloakClassName());
  2063. },
  2064. removeListener = $rootScope.$on('$translateChangeEnd', function () {
  2065. removeCloak();
  2066. removeListener();
  2067. removeListener = null;
  2068. });
  2069. applyCloak();
  2070. return function linkFn(scope, iElement, iAttr) {
  2071. // Register a watcher for the defined translation allowing a fine tuned cloak
  2072. if (iAttr.translateCloak && iAttr.translateCloak.length) {
  2073. iAttr.$observe('translateCloak', function (translationId) {
  2074. $translate(translationId).then(removeCloak, applyCloak);
  2075. });
  2076. }
  2077. };
  2078. }
  2079. };
  2080. }]);
  2081. angular.module('pascalprecht.translate')
  2082. /**
  2083. * @ngdoc filter
  2084. * @name pascalprecht.translate.filter:translate
  2085. * @requires $parse
  2086. * @requires pascalprecht.translate.$translate
  2087. * @function
  2088. *
  2089. * @description
  2090. * Uses `$translate` service to translate contents. Accepts interpolate parameters
  2091. * to pass dynamized values though translation.
  2092. *
  2093. * @param {string} translationId A translation id to be translated.
  2094. * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation.
  2095. *
  2096. * @returns {string} Translated text.
  2097. *
  2098. * @example
  2099. <example module="ngView">
  2100. <file name="index.html">
  2101. <div ng-controller="TranslateCtrl">
  2102. <pre>{{ 'TRANSLATION_ID' | translate }}</pre>
  2103. <pre>{{ translationId | translate }}</pre>
  2104. <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre>
  2105. <pre>{{ 'WITH_VALUES' | translate:values }}</pre>
  2106. </div>
  2107. </file>
  2108. <file name="script.js">
  2109. angular.module('ngView', ['pascalprecht.translate'])
  2110. .config(function ($translateProvider) {
  2111. $translateProvider.translations('en', {
  2112. 'TRANSLATION_ID': 'Hello there!',
  2113. 'WITH_VALUES': 'The following value is dynamic: {{value}}'
  2114. });
  2115. $translateProvider.preferredLanguage('en');
  2116. });
  2117. angular.module('ngView').controller('TranslateCtrl', function ($scope) {
  2118. $scope.translationId = 'TRANSLATION_ID';
  2119. $scope.values = {
  2120. value: 78
  2121. };
  2122. });
  2123. </file>
  2124. </example>
  2125. */
  2126. .filter('translate', ['$parse', '$translate', function ($parse, $translate) {
  2127. var translateFilter = function (translationId, interpolateParams, interpolation) {
  2128. if (!angular.isObject(interpolateParams)) {
  2129. interpolateParams = $parse(interpolateParams)(this);
  2130. }
  2131. return $translate.instant(translationId, interpolateParams, interpolation);
  2132. };
  2133. // Since AngularJS 1.3, filters which are not stateless (depending at the scope)
  2134. // have to explicit define this behavior.
  2135. translateFilter.$stateful = true;
  2136. return translateFilter;
  2137. }]);