angular-translate.js 126 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709
  1. /*!
  2. * angular-translate - v2.15.1 - 2017-04-03
  3. *
  4. * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
  5. */
  6. (function (root, factory) {
  7. if (typeof define === 'function' && define.amd) {
  8. // AMD. Register as an anonymous module unless amdModuleId is set
  9. define([], function () {
  10. return (factory());
  11. });
  12. } else if (typeof module === 'object' && module.exports) {
  13. // Node. Does not work with strict CommonJS, but
  14. // only CommonJS-like environments that support module.exports,
  15. // like Node.
  16. module.exports = factory();
  17. } else {
  18. factory();
  19. }
  20. }(this, function () {
  21. /**
  22. * @ngdoc overview
  23. * @name pascalprecht.translate
  24. *
  25. * @description
  26. * The main module which holds everything together.
  27. */
  28. runTranslate.$inject = ['$translate'];
  29. $translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider'];
  30. $translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization'];
  31. translateDirective.$inject = ['$translate', '$interpolate', '$compile', '$parse', '$rootScope'];
  32. translateAttrDirective.$inject = ['$translate', '$rootScope'];
  33. translateCloakDirective.$inject = ['$translate', '$rootScope'];
  34. translateFilterFactory.$inject = ['$parse', '$translate'];
  35. $translationCache.$inject = ['$cacheFactory'];
  36. angular.module('pascalprecht.translate', ['ng'])
  37. .run(runTranslate);
  38. function runTranslate($translate) {
  39. 'use strict';
  40. var key = $translate.storageKey(),
  41. storage = $translate.storage();
  42. var fallbackFromIncorrectStorageValue = function () {
  43. var preferred = $translate.preferredLanguage();
  44. if (angular.isString(preferred)) {
  45. $translate.use(preferred);
  46. // $translate.use() will also remember the language.
  47. // So, we don't need to call storage.put() here.
  48. } else {
  49. storage.put(key, $translate.use());
  50. }
  51. };
  52. fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue';
  53. if (storage) {
  54. if (!storage.get(key)) {
  55. fallbackFromIncorrectStorageValue();
  56. } else {
  57. $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue);
  58. }
  59. } else if (angular.isString($translate.preferredLanguage())) {
  60. $translate.use($translate.preferredLanguage());
  61. }
  62. }
  63. runTranslate.displayName = 'runTranslate';
  64. /**
  65. * @ngdoc object
  66. * @name pascalprecht.translate.$translateSanitizationProvider
  67. *
  68. * @description
  69. *
  70. * Configurations for $translateSanitization
  71. */
  72. angular.module('pascalprecht.translate').provider('$translateSanitization', $translateSanitizationProvider);
  73. function $translateSanitizationProvider () {
  74. 'use strict';
  75. var $sanitize,
  76. $sce,
  77. currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0.
  78. hasConfiguredStrategy = false,
  79. hasShownNoStrategyConfiguredWarning = false,
  80. strategies;
  81. /**
  82. * Definition of a sanitization strategy function
  83. * @callback StrategyFunction
  84. * @param {string|object} value - value to be sanitized (either a string or an interpolated value map)
  85. * @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params
  86. * @return {string|object}
  87. */
  88. /**
  89. * @ngdoc property
  90. * @name strategies
  91. * @propertyOf pascalprecht.translate.$translateSanitizationProvider
  92. *
  93. * @description
  94. * Following strategies are built-in:
  95. * <dl>
  96. * <dt>sanitize</dt>
  97. * <dd>Sanitizes HTML in the translation text using $sanitize</dd>
  98. * <dt>escape</dt>
  99. * <dd>Escapes HTML in the translation</dd>
  100. * <dt>sanitizeParameters</dt>
  101. * <dd>Sanitizes HTML in the values of the interpolation parameters using $sanitize</dd>
  102. * <dt>escapeParameters</dt>
  103. * <dd>Escapes HTML in the values of the interpolation parameters</dd>
  104. * <dt>escaped</dt>
  105. * <dd>Support legacy strategy name 'escaped' for backwards compatibility (will be removed in 3.0)</dd>
  106. * </dl>
  107. *
  108. */
  109. strategies = {
  110. sanitize: function (value, mode/*, context*/) {
  111. if (mode === 'text') {
  112. value = htmlSanitizeValue(value);
  113. }
  114. return value;
  115. },
  116. escape: function (value, mode/*, context*/) {
  117. if (mode === 'text') {
  118. value = htmlEscapeValue(value);
  119. }
  120. return value;
  121. },
  122. sanitizeParameters: function (value, mode/*, context*/) {
  123. if (mode === 'params') {
  124. value = mapInterpolationParameters(value, htmlSanitizeValue);
  125. }
  126. return value;
  127. },
  128. escapeParameters: function (value, mode/*, context*/) {
  129. if (mode === 'params') {
  130. value = mapInterpolationParameters(value, htmlEscapeValue);
  131. }
  132. return value;
  133. },
  134. sce: function (value, mode, context) {
  135. if (mode === 'text') {
  136. value = htmlTrustValue(value);
  137. } else if (mode === 'params') {
  138. if (context !== 'filter') {
  139. // do html escape in filter context #1101
  140. value = mapInterpolationParameters(value, htmlEscapeValue);
  141. }
  142. }
  143. return value;
  144. },
  145. sceParameters: function (value, mode/*, context*/) {
  146. if (mode === 'params') {
  147. value = mapInterpolationParameters(value, htmlTrustValue);
  148. }
  149. return value;
  150. }
  151. };
  152. // Support legacy strategy name 'escaped' for backwards compatibility.
  153. // TODO should be removed in 3.0
  154. strategies.escaped = strategies.escapeParameters;
  155. /**
  156. * @ngdoc function
  157. * @name pascalprecht.translate.$translateSanitizationProvider#addStrategy
  158. * @methodOf pascalprecht.translate.$translateSanitizationProvider
  159. *
  160. * @description
  161. * Adds a sanitization strategy to the list of known strategies.
  162. *
  163. * @param {string} strategyName - unique key for a strategy
  164. * @param {StrategyFunction} strategyFunction - strategy function
  165. * @returns {object} this
  166. */
  167. this.addStrategy = function (strategyName, strategyFunction) {
  168. strategies[strategyName] = strategyFunction;
  169. return this;
  170. };
  171. /**
  172. * @ngdoc function
  173. * @name pascalprecht.translate.$translateSanitizationProvider#removeStrategy
  174. * @methodOf pascalprecht.translate.$translateSanitizationProvider
  175. *
  176. * @description
  177. * Removes a sanitization strategy from the list of known strategies.
  178. *
  179. * @param {string} strategyName - unique key for a strategy
  180. * @returns {object} this
  181. */
  182. this.removeStrategy = function (strategyName) {
  183. delete strategies[strategyName];
  184. return this;
  185. };
  186. /**
  187. * @ngdoc function
  188. * @name pascalprecht.translate.$translateSanitizationProvider#useStrategy
  189. * @methodOf pascalprecht.translate.$translateSanitizationProvider
  190. *
  191. * @description
  192. * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
  193. *
  194. * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions.
  195. * @returns {object} this
  196. */
  197. this.useStrategy = function (strategy) {
  198. hasConfiguredStrategy = true;
  199. currentStrategy = strategy;
  200. return this;
  201. };
  202. /**
  203. * @ngdoc object
  204. * @name pascalprecht.translate.$translateSanitization
  205. * @requires $injector
  206. * @requires $log
  207. *
  208. * @description
  209. * Sanitizes interpolation parameters and translated texts.
  210. *
  211. */
  212. this.$get = ['$injector', '$log', function ($injector, $log) {
  213. var cachedStrategyMap = {};
  214. var applyStrategies = function (value, mode, context, selectedStrategies) {
  215. angular.forEach(selectedStrategies, function (selectedStrategy) {
  216. if (angular.isFunction(selectedStrategy)) {
  217. value = selectedStrategy(value, mode, context);
  218. } else if (angular.isFunction(strategies[selectedStrategy])) {
  219. value = strategies[selectedStrategy](value, mode, context);
  220. } else if (angular.isString(strategies[selectedStrategy])) {
  221. if (!cachedStrategyMap[strategies[selectedStrategy]]) {
  222. try {
  223. cachedStrategyMap[strategies[selectedStrategy]] = $injector.get(strategies[selectedStrategy]);
  224. } catch (e) {
  225. cachedStrategyMap[strategies[selectedStrategy]] = function() {};
  226. throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
  227. }
  228. }
  229. value = cachedStrategyMap[strategies[selectedStrategy]](value, mode, context);
  230. } else {
  231. throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
  232. }
  233. });
  234. return value;
  235. };
  236. // TODO: should be removed in 3.0
  237. var showNoStrategyConfiguredWarning = function () {
  238. if (!hasConfiguredStrategy && !hasShownNoStrategyConfiguredWarning) {
  239. $log.warn('pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details.');
  240. hasShownNoStrategyConfiguredWarning = true;
  241. }
  242. };
  243. if ($injector.has('$sanitize')) {
  244. $sanitize = $injector.get('$sanitize');
  245. }
  246. if ($injector.has('$sce')) {
  247. $sce = $injector.get('$sce');
  248. }
  249. return {
  250. /**
  251. * @ngdoc function
  252. * @name pascalprecht.translate.$translateSanitization#useStrategy
  253. * @methodOf pascalprecht.translate.$translateSanitization
  254. *
  255. * @description
  256. * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
  257. *
  258. * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions.
  259. */
  260. useStrategy: (function (self) {
  261. return function (strategy) {
  262. self.useStrategy(strategy);
  263. };
  264. })(this),
  265. /**
  266. * @ngdoc function
  267. * @name pascalprecht.translate.$translateSanitization#sanitize
  268. * @methodOf pascalprecht.translate.$translateSanitization
  269. *
  270. * @description
  271. * Sanitizes a value.
  272. *
  273. * @param {string|object} value The value which should be sanitized.
  274. * @param {string} mode The current sanitization mode, either 'params' or 'text'.
  275. * @param {string|StrategyFunction|array} [strategy] Optional custom strategy which should be used instead of the currently selected strategy.
  276. * @param {string} [context] The context of this call: filter, service. Default is service
  277. * @returns {string|object} sanitized value
  278. */
  279. sanitize: function (value, mode, strategy, context) {
  280. if (!currentStrategy) {
  281. showNoStrategyConfiguredWarning();
  282. }
  283. if (!strategy && strategy !== null) {
  284. strategy = currentStrategy;
  285. }
  286. if (!strategy) {
  287. return value;
  288. }
  289. if (!context) {
  290. context = 'service';
  291. }
  292. var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy];
  293. return applyStrategies(value, mode, context, selectedStrategies);
  294. }
  295. };
  296. }];
  297. var htmlEscapeValue = function (value) {
  298. var element = angular.element('<div></div>');
  299. element.text(value); // not chainable, see #1044
  300. return element.html();
  301. };
  302. var htmlSanitizeValue = function (value) {
  303. if (!$sanitize) {
  304. throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as \'escape\'.');
  305. }
  306. return $sanitize(value);
  307. };
  308. var htmlTrustValue = function (value) {
  309. if (!$sce) {
  310. throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sce service.');
  311. }
  312. return $sce.trustAsHtml(value);
  313. };
  314. var mapInterpolationParameters = function (value, iteratee, stack) {
  315. if (angular.isDate(value)) {
  316. return value;
  317. } else if (angular.isObject(value)) {
  318. var result = angular.isArray(value) ? [] : {};
  319. if (!stack) {
  320. stack = [];
  321. } else {
  322. if (stack.indexOf(value) > -1) {
  323. throw new Error('pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object');
  324. }
  325. }
  326. stack.push(value);
  327. angular.forEach(value, function (propertyValue, propertyKey) {
  328. /* Skipping function properties. */
  329. if (angular.isFunction(propertyValue)) {
  330. return;
  331. }
  332. result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee, stack);
  333. });
  334. stack.splice(-1, 1); // remove last
  335. return result;
  336. } else if (angular.isNumber(value)) {
  337. return value;
  338. } else if (!angular.isUndefined(value) && value !== null) {
  339. return iteratee(value);
  340. } else {
  341. return value;
  342. }
  343. };
  344. }
  345. /**
  346. * @ngdoc object
  347. * @name pascalprecht.translate.$translateProvider
  348. * @description
  349. *
  350. * $translateProvider allows developers to register translation-tables, asynchronous loaders
  351. * and similar to configure translation behavior directly inside of a module.
  352. *
  353. */
  354. angular.module('pascalprecht.translate')
  355. .constant('pascalprechtTranslateOverrider', {})
  356. .provider('$translate', $translate);
  357. function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) {
  358. 'use strict';
  359. var $translationTable = {},
  360. $preferredLanguage,
  361. $availableLanguageKeys = [],
  362. $languageKeyAliases,
  363. $fallbackLanguage,
  364. $fallbackWasString,
  365. $uses,
  366. $nextLang,
  367. $storageFactory,
  368. $storageKey = $STORAGE_KEY,
  369. $storagePrefix,
  370. $missingTranslationHandlerFactory,
  371. $interpolationFactory,
  372. $interpolatorFactories = [],
  373. $loaderFactory,
  374. $cloakClassName = 'translate-cloak',
  375. $loaderOptions,
  376. $notFoundIndicatorLeft,
  377. $notFoundIndicatorRight,
  378. $postCompilingEnabled = false,
  379. $forceAsyncReloadEnabled = false,
  380. $nestedObjectDelimeter = '.',
  381. $isReady = false,
  382. $keepContent = false,
  383. loaderCache,
  384. directivePriority = 0,
  385. statefulFilter = true,
  386. postProcessFn,
  387. uniformLanguageTagResolver = 'default',
  388. languageTagResolver = {
  389. 'default' : function (tag) {
  390. return (tag || '').split('-').join('_');
  391. },
  392. java : function (tag) {
  393. var temp = (tag || '').split('-').join('_');
  394. var parts = temp.split('_');
  395. return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp;
  396. },
  397. bcp47 : function (tag) {
  398. var temp = (tag || '').split('_').join('-');
  399. var parts = temp.split('-');
  400. return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp;
  401. },
  402. 'iso639-1' : function (tag) {
  403. var temp = (tag || '').split('_').join('-');
  404. var parts = temp.split('-');
  405. return parts[0].toLowerCase();
  406. }
  407. };
  408. var version = '2.15.1';
  409. // tries to determine the browsers language
  410. var getFirstBrowserLanguage = function () {
  411. // internal purpose only
  412. if (angular.isFunction(pascalprechtTranslateOverrider.getLocale)) {
  413. return pascalprechtTranslateOverrider.getLocale();
  414. }
  415. var nav = $windowProvider.$get().navigator,
  416. browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'],
  417. i,
  418. language;
  419. // support for HTML 5.1 "navigator.languages"
  420. if (angular.isArray(nav.languages)) {
  421. for (i = 0; i < nav.languages.length; i++) {
  422. language = nav.languages[i];
  423. if (language && language.length) {
  424. return language;
  425. }
  426. }
  427. }
  428. // support for other well known properties in browsers
  429. for (i = 0; i < browserLanguagePropertyKeys.length; i++) {
  430. language = nav[browserLanguagePropertyKeys[i]];
  431. if (language && language.length) {
  432. return language;
  433. }
  434. }
  435. return null;
  436. };
  437. getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage';
  438. // tries to determine the browsers locale
  439. var getLocale = function () {
  440. var locale = getFirstBrowserLanguage() || '';
  441. if (languageTagResolver[uniformLanguageTagResolver]) {
  442. locale = languageTagResolver[uniformLanguageTagResolver](locale);
  443. }
  444. return locale;
  445. };
  446. getLocale.displayName = 'angular-translate/service: getLocale';
  447. /**
  448. * @name indexOf
  449. * @private
  450. *
  451. * @description
  452. * indexOf polyfill. Kinda sorta.
  453. *
  454. * @param {array} array Array to search in.
  455. * @param {string} searchElement Element to search for.
  456. *
  457. * @returns {int} Index of search element.
  458. */
  459. var indexOf = function (array, searchElement) {
  460. for (var i = 0, len = array.length; i < len; i++) {
  461. if (array[i] === searchElement) {
  462. return i;
  463. }
  464. }
  465. return -1;
  466. };
  467. /**
  468. * @name trim
  469. * @private
  470. *
  471. * @description
  472. * trim polyfill
  473. *
  474. * @returns {string} The string stripped of whitespace from both ends
  475. */
  476. var trim = function () {
  477. return this.toString().replace(/^\s+|\s+$/g, '');
  478. };
  479. var negotiateLocale = function (preferred) {
  480. if (!preferred) {
  481. return;
  482. }
  483. var avail = [],
  484. locale = angular.lowercase(preferred),
  485. i = 0,
  486. n = $availableLanguageKeys.length;
  487. for (; i < n; i++) {
  488. avail.push(angular.lowercase($availableLanguageKeys[i]));
  489. }
  490. // Check for an exact match in our list of available keys
  491. if (indexOf(avail, locale) > -1) {
  492. return preferred;
  493. }
  494. if ($languageKeyAliases) {
  495. var alias;
  496. for (var langKeyAlias in $languageKeyAliases) {
  497. if ($languageKeyAliases.hasOwnProperty(langKeyAlias)) {
  498. var hasWildcardKey = false;
  499. var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) &&
  500. angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
  501. if (langKeyAlias.slice(-1) === '*') {
  502. hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length - 1);
  503. }
  504. if (hasExactKey || hasWildcardKey) {
  505. alias = $languageKeyAliases[langKeyAlias];
  506. if (indexOf(avail, angular.lowercase(alias)) > -1) {
  507. return alias;
  508. }
  509. }
  510. }
  511. }
  512. }
  513. // Check for a language code without region
  514. var parts = preferred.split('_');
  515. if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) {
  516. return parts[0];
  517. }
  518. // If everything fails, return undefined.
  519. return;
  520. };
  521. /**
  522. * @ngdoc function
  523. * @name pascalprecht.translate.$translateProvider#translations
  524. * @methodOf pascalprecht.translate.$translateProvider
  525. *
  526. * @description
  527. * Registers a new translation table for specific language key.
  528. *
  529. * To register a translation table for specific language, pass a defined language
  530. * key as first parameter.
  531. *
  532. * <pre>
  533. * // register translation table for language: 'de_DE'
  534. * $translateProvider.translations('de_DE', {
  535. * 'GREETING': 'Hallo Welt!'
  536. * });
  537. *
  538. * // register another one
  539. * $translateProvider.translations('en_US', {
  540. * 'GREETING': 'Hello world!'
  541. * });
  542. * </pre>
  543. *
  544. * When registering multiple translation tables for for the same language key,
  545. * the actual translation table gets extended. This allows you to define module
  546. * specific translation which only get added, once a specific module is loaded in
  547. * your app.
  548. *
  549. * Invoking this method with no arguments returns the translation table which was
  550. * registered with no language key. Invoking it with a language key returns the
  551. * related translation table.
  552. *
  553. * @param {string} langKey A language key.
  554. * @param {object} translationTable A plain old JavaScript object that represents a translation table.
  555. *
  556. */
  557. var translations = function (langKey, translationTable) {
  558. if (!langKey && !translationTable) {
  559. return $translationTable;
  560. }
  561. if (langKey && !translationTable) {
  562. if (angular.isString(langKey)) {
  563. return $translationTable[langKey];
  564. }
  565. } else {
  566. if (!angular.isObject($translationTable[langKey])) {
  567. $translationTable[langKey] = {};
  568. }
  569. angular.extend($translationTable[langKey], flatObject(translationTable));
  570. }
  571. return this;
  572. };
  573. this.translations = translations;
  574. /**
  575. * @ngdoc function
  576. * @name pascalprecht.translate.$translateProvider#cloakClassName
  577. * @methodOf pascalprecht.translate.$translateProvider
  578. *
  579. * @description
  580. *
  581. * Let's you change the class name for `translate-cloak` directive.
  582. * Default class name is `translate-cloak`.
  583. *
  584. * @param {string} name translate-cloak class name
  585. */
  586. this.cloakClassName = function (name) {
  587. if (!name) {
  588. return $cloakClassName;
  589. }
  590. $cloakClassName = name;
  591. return this;
  592. };
  593. /**
  594. * @ngdoc function
  595. * @name pascalprecht.translate.$translateProvider#nestedObjectDelimeter
  596. * @methodOf pascalprecht.translate.$translateProvider
  597. *
  598. * @description
  599. *
  600. * Let's you change the delimiter for namespaced translations.
  601. * Default delimiter is `.`.
  602. *
  603. * @param {string} delimiter namespace separator
  604. */
  605. this.nestedObjectDelimeter = function (delimiter) {
  606. if (!delimiter) {
  607. return $nestedObjectDelimeter;
  608. }
  609. $nestedObjectDelimeter = delimiter;
  610. return this;
  611. };
  612. /**
  613. * @name flatObject
  614. * @private
  615. *
  616. * @description
  617. * Flats an object. This function is used to flatten given translation data with
  618. * namespaces, so they are later accessible via dot notation.
  619. */
  620. var flatObject = function (data, path, result, prevKey) {
  621. var key, keyWithPath, keyWithShortPath, val;
  622. if (!path) {
  623. path = [];
  624. }
  625. if (!result) {
  626. result = {};
  627. }
  628. for (key in data) {
  629. if (!Object.prototype.hasOwnProperty.call(data, key)) {
  630. continue;
  631. }
  632. val = data[key];
  633. if (angular.isObject(val)) {
  634. flatObject(val, path.concat(key), result, key);
  635. } else {
  636. keyWithPath = path.length ? ('' + path.join($nestedObjectDelimeter) + $nestedObjectDelimeter + key) : key;
  637. if (path.length && key === prevKey) {
  638. // Create shortcut path (foo.bar == foo.bar.bar)
  639. keyWithShortPath = '' + path.join($nestedObjectDelimeter);
  640. // Link it to original path
  641. result[keyWithShortPath] = '@:' + keyWithPath;
  642. }
  643. result[keyWithPath] = val;
  644. }
  645. }
  646. return result;
  647. };
  648. flatObject.displayName = 'flatObject';
  649. /**
  650. * @ngdoc function
  651. * @name pascalprecht.translate.$translateProvider#addInterpolation
  652. * @methodOf pascalprecht.translate.$translateProvider
  653. *
  654. * @description
  655. * Adds interpolation services to angular-translate, so it can manage them.
  656. *
  657. * @param {object} factory Interpolation service factory
  658. */
  659. this.addInterpolation = function (factory) {
  660. $interpolatorFactories.push(factory);
  661. return this;
  662. };
  663. /**
  664. * @ngdoc function
  665. * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation
  666. * @methodOf pascalprecht.translate.$translateProvider
  667. *
  668. * @description
  669. * Tells angular-translate to use interpolation functionality of messageformat.js.
  670. * This is useful when having high level pluralization and gender selection.
  671. */
  672. this.useMessageFormatInterpolation = function () {
  673. return this.useInterpolation('$translateMessageFormatInterpolation');
  674. };
  675. /**
  676. * @ngdoc function
  677. * @name pascalprecht.translate.$translateProvider#useInterpolation
  678. * @methodOf pascalprecht.translate.$translateProvider
  679. *
  680. * @description
  681. * Tells angular-translate which interpolation style to use as default, application-wide.
  682. * Simply pass a factory/service name. The interpolation service has to implement
  683. * the correct interface.
  684. *
  685. * @param {string} factory Interpolation service name.
  686. */
  687. this.useInterpolation = function (factory) {
  688. $interpolationFactory = factory;
  689. return this;
  690. };
  691. /**
  692. * @ngdoc function
  693. * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy
  694. * @methodOf pascalprecht.translate.$translateProvider
  695. *
  696. * @description
  697. * Simply sets a sanitation strategy type.
  698. *
  699. * @param {string} value Strategy type.
  700. */
  701. this.useSanitizeValueStrategy = function (value) {
  702. $translateSanitizationProvider.useStrategy(value);
  703. return this;
  704. };
  705. /**
  706. * @ngdoc function
  707. * @name pascalprecht.translate.$translateProvider#preferredLanguage
  708. * @methodOf pascalprecht.translate.$translateProvider
  709. *
  710. * @description
  711. * Tells the module which of the registered translation tables to use for translation
  712. * at initial startup by passing a language key. Similar to `$translateProvider#use`
  713. * only that it says which language to **prefer**.
  714. *
  715. * @param {string} langKey A language key.
  716. */
  717. this.preferredLanguage = function (langKey) {
  718. if (langKey) {
  719. setupPreferredLanguage(langKey);
  720. return this;
  721. }
  722. return $preferredLanguage;
  723. };
  724. var setupPreferredLanguage = function (langKey) {
  725. if (langKey) {
  726. $preferredLanguage = langKey;
  727. }
  728. return $preferredLanguage;
  729. };
  730. /**
  731. * @ngdoc function
  732. * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator
  733. * @methodOf pascalprecht.translate.$translateProvider
  734. *
  735. * @description
  736. * Sets an indicator which is used when a translation isn't found. E.g. when
  737. * setting the indicator as 'X' and one tries to translate a translation id
  738. * called `NOT_FOUND`, this will result in `X NOT_FOUND X`.
  739. *
  740. * Internally this methods sets a left indicator and a right indicator using
  741. * `$translateProvider.translationNotFoundIndicatorLeft()` and
  742. * `$translateProvider.translationNotFoundIndicatorRight()`.
  743. *
  744. * **Note**: These methods automatically add a whitespace between the indicators
  745. * and the translation id.
  746. *
  747. * @param {string} indicator An indicator, could be any string.
  748. */
  749. this.translationNotFoundIndicator = function (indicator) {
  750. this.translationNotFoundIndicatorLeft(indicator);
  751. this.translationNotFoundIndicatorRight(indicator);
  752. return this;
  753. };
  754. /**
  755. * ngdoc function
  756. * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
  757. * @methodOf pascalprecht.translate.$translateProvider
  758. *
  759. * @description
  760. * Sets an indicator which is used when a translation isn't found left to the
  761. * translation id.
  762. *
  763. * @param {string} indicator An indicator.
  764. */
  765. this.translationNotFoundIndicatorLeft = function (indicator) {
  766. if (!indicator) {
  767. return $notFoundIndicatorLeft;
  768. }
  769. $notFoundIndicatorLeft = indicator;
  770. return this;
  771. };
  772. /**
  773. * ngdoc function
  774. * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
  775. * @methodOf pascalprecht.translate.$translateProvider
  776. *
  777. * @description
  778. * Sets an indicator which is used when a translation isn't found right to the
  779. * translation id.
  780. *
  781. * @param {string} indicator An indicator.
  782. */
  783. this.translationNotFoundIndicatorRight = function (indicator) {
  784. if (!indicator) {
  785. return $notFoundIndicatorRight;
  786. }
  787. $notFoundIndicatorRight = indicator;
  788. return this;
  789. };
  790. /**
  791. * @ngdoc function
  792. * @name pascalprecht.translate.$translateProvider#fallbackLanguage
  793. * @methodOf pascalprecht.translate.$translateProvider
  794. *
  795. * @description
  796. * Tells the module which of the registered translation tables to use when missing translations
  797. * at initial startup by passing a language key. Similar to `$translateProvider#use`
  798. * only that it says which language to **fallback**.
  799. *
  800. * @param {string||array} langKey A language key.
  801. *
  802. */
  803. this.fallbackLanguage = function (langKey) {
  804. fallbackStack(langKey);
  805. return this;
  806. };
  807. var fallbackStack = function (langKey) {
  808. if (langKey) {
  809. if (angular.isString(langKey)) {
  810. $fallbackWasString = true;
  811. $fallbackLanguage = [langKey];
  812. } else if (angular.isArray(langKey)) {
  813. $fallbackWasString = false;
  814. $fallbackLanguage = langKey;
  815. }
  816. if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) {
  817. $fallbackLanguage.push($preferredLanguage);
  818. }
  819. return this;
  820. } else {
  821. if ($fallbackWasString) {
  822. return $fallbackLanguage[0];
  823. } else {
  824. return $fallbackLanguage;
  825. }
  826. }
  827. };
  828. /**
  829. * @ngdoc function
  830. * @name pascalprecht.translate.$translateProvider#use
  831. * @methodOf pascalprecht.translate.$translateProvider
  832. *
  833. * @description
  834. * Set which translation table to use for translation by given language key. When
  835. * trying to 'use' a language which isn't provided, it'll throw an error.
  836. *
  837. * You actually don't have to use this method since `$translateProvider#preferredLanguage`
  838. * does the job too.
  839. *
  840. * @param {string} langKey A language key.
  841. */
  842. this.use = function (langKey) {
  843. if (langKey) {
  844. if (!$translationTable[langKey] && (!$loaderFactory)) {
  845. // only throw an error, when not loading translation data asynchronously
  846. throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\'');
  847. }
  848. $uses = langKey;
  849. return this;
  850. }
  851. return $uses;
  852. };
  853. /**
  854. * @ngdoc function
  855. * @name pascalprecht.translate.$translateProvider#resolveClientLocale
  856. * @methodOf pascalprecht.translate.$translateProvider
  857. *
  858. * @description
  859. * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
  860. *
  861. * @returns {string} the current client/browser language key
  862. */
  863. this.resolveClientLocale = function () {
  864. return getLocale();
  865. };
  866. /**
  867. * @ngdoc function
  868. * @name pascalprecht.translate.$translateProvider#storageKey
  869. * @methodOf pascalprecht.translate.$translateProvider
  870. *
  871. * @description
  872. * Tells the module which key must represent the choosed language by a user in the storage.
  873. *
  874. * @param {string} key A key for the storage.
  875. */
  876. var storageKey = function (key) {
  877. if (!key) {
  878. if ($storagePrefix) {
  879. return $storagePrefix + $storageKey;
  880. }
  881. return $storageKey;
  882. }
  883. $storageKey = key;
  884. return this;
  885. };
  886. this.storageKey = storageKey;
  887. /**
  888. * @ngdoc function
  889. * @name pascalprecht.translate.$translateProvider#useUrlLoader
  890. * @methodOf pascalprecht.translate.$translateProvider
  891. *
  892. * @description
  893. * Tells angular-translate to use `$translateUrlLoader` extension service as loader.
  894. *
  895. * @param {string} url Url
  896. * @param {Object=} options Optional configuration object
  897. */
  898. this.useUrlLoader = function (url, options) {
  899. return this.useLoader('$translateUrlLoader', angular.extend({url : url}, options));
  900. };
  901. /**
  902. * @ngdoc function
  903. * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader
  904. * @methodOf pascalprecht.translate.$translateProvider
  905. *
  906. * @description
  907. * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader.
  908. *
  909. * @param {Object=} options Optional configuration object
  910. */
  911. this.useStaticFilesLoader = function (options) {
  912. return this.useLoader('$translateStaticFilesLoader', options);
  913. };
  914. /**
  915. * @ngdoc function
  916. * @name pascalprecht.translate.$translateProvider#useLoader
  917. * @methodOf pascalprecht.translate.$translateProvider
  918. *
  919. * @description
  920. * Tells angular-translate to use any other service as loader.
  921. *
  922. * @param {string} loaderFactory Factory name to use
  923. * @param {Object=} options Optional configuration object
  924. */
  925. this.useLoader = function (loaderFactory, options) {
  926. $loaderFactory = loaderFactory;
  927. $loaderOptions = options || {};
  928. return this;
  929. };
  930. /**
  931. * @ngdoc function
  932. * @name pascalprecht.translate.$translateProvider#useLocalStorage
  933. * @methodOf pascalprecht.translate.$translateProvider
  934. *
  935. * @description
  936. * Tells angular-translate to use `$translateLocalStorage` service as storage layer.
  937. *
  938. */
  939. this.useLocalStorage = function () {
  940. return this.useStorage('$translateLocalStorage');
  941. };
  942. /**
  943. * @ngdoc function
  944. * @name pascalprecht.translate.$translateProvider#useCookieStorage
  945. * @methodOf pascalprecht.translate.$translateProvider
  946. *
  947. * @description
  948. * Tells angular-translate to use `$translateCookieStorage` service as storage layer.
  949. */
  950. this.useCookieStorage = function () {
  951. return this.useStorage('$translateCookieStorage');
  952. };
  953. /**
  954. * @ngdoc function
  955. * @name pascalprecht.translate.$translateProvider#useStorage
  956. * @methodOf pascalprecht.translate.$translateProvider
  957. *
  958. * @description
  959. * Tells angular-translate to use custom service as storage layer.
  960. */
  961. this.useStorage = function (storageFactory) {
  962. $storageFactory = storageFactory;
  963. return this;
  964. };
  965. /**
  966. * @ngdoc function
  967. * @name pascalprecht.translate.$translateProvider#storagePrefix
  968. * @methodOf pascalprecht.translate.$translateProvider
  969. *
  970. * @description
  971. * Sets prefix for storage key.
  972. *
  973. * @param {string} prefix Storage key prefix
  974. */
  975. this.storagePrefix = function (prefix) {
  976. if (!prefix) {
  977. return prefix;
  978. }
  979. $storagePrefix = prefix;
  980. return this;
  981. };
  982. /**
  983. * @ngdoc function
  984. * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog
  985. * @methodOf pascalprecht.translate.$translateProvider
  986. *
  987. * @description
  988. * Tells angular-translate to use built-in log handler when trying to translate
  989. * a translation Id which doesn't exist.
  990. *
  991. * This is actually a shortcut method for `useMissingTranslationHandler()`.
  992. *
  993. */
  994. this.useMissingTranslationHandlerLog = function () {
  995. return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
  996. };
  997. /**
  998. * @ngdoc function
  999. * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler
  1000. * @methodOf pascalprecht.translate.$translateProvider
  1001. *
  1002. * @description
  1003. * Expects a factory name which later gets instantiated with `$injector`.
  1004. * This method can be used to tell angular-translate to use a custom
  1005. * missingTranslationHandler. Just build a factory which returns a function
  1006. * and expects a translation id as argument.
  1007. *
  1008. * Example:
  1009. * <pre>
  1010. * app.config(function ($translateProvider) {
  1011. * $translateProvider.useMissingTranslationHandler('customHandler');
  1012. * });
  1013. *
  1014. * app.factory('customHandler', function (dep1, dep2) {
  1015. * return function (translationId) {
  1016. * // something with translationId and dep1 and dep2
  1017. * };
  1018. * });
  1019. * </pre>
  1020. *
  1021. * @param {string} factory Factory name
  1022. */
  1023. this.useMissingTranslationHandler = function (factory) {
  1024. $missingTranslationHandlerFactory = factory;
  1025. return this;
  1026. };
  1027. /**
  1028. * @ngdoc function
  1029. * @name pascalprecht.translate.$translateProvider#usePostCompiling
  1030. * @methodOf pascalprecht.translate.$translateProvider
  1031. *
  1032. * @description
  1033. * If post compiling is enabled, all translated values will be processed
  1034. * again with AngularJS' $compile.
  1035. *
  1036. * Example:
  1037. * <pre>
  1038. * app.config(function ($translateProvider) {
  1039. * $translateProvider.usePostCompiling(true);
  1040. * });
  1041. * </pre>
  1042. *
  1043. * @param {string} factory Factory name
  1044. */
  1045. this.usePostCompiling = function (value) {
  1046. $postCompilingEnabled = !(!value);
  1047. return this;
  1048. };
  1049. /**
  1050. * @ngdoc function
  1051. * @name pascalprecht.translate.$translateProvider#forceAsyncReload
  1052. * @methodOf pascalprecht.translate.$translateProvider
  1053. *
  1054. * @description
  1055. * If force async reload is enabled, async loader will always be called
  1056. * even if $translationTable already contains the language key, adding
  1057. * possible new entries to the $translationTable.
  1058. *
  1059. * Example:
  1060. * <pre>
  1061. * app.config(function ($translateProvider) {
  1062. * $translateProvider.forceAsyncReload(true);
  1063. * });
  1064. * </pre>
  1065. *
  1066. * @param {boolean} value - valid values are true or false
  1067. */
  1068. this.forceAsyncReload = function (value) {
  1069. $forceAsyncReloadEnabled = !(!value);
  1070. return this;
  1071. };
  1072. /**
  1073. * @ngdoc function
  1074. * @name pascalprecht.translate.$translateProvider#uniformLanguageTag
  1075. * @methodOf pascalprecht.translate.$translateProvider
  1076. *
  1077. * @description
  1078. * Tells angular-translate which language tag should be used as a result when determining
  1079. * the current browser language.
  1080. *
  1081. * This setting must be set before invoking {@link pascalprecht.translate.$translateProvider#methods_determinePreferredLanguage determinePreferredLanguage()}.
  1082. *
  1083. * <pre>
  1084. * $translateProvider
  1085. * .uniformLanguageTag('bcp47')
  1086. * .determinePreferredLanguage()
  1087. * </pre>
  1088. *
  1089. * The resolver currently supports:
  1090. * * default
  1091. * (traditionally: hyphens will be converted into underscores, i.e. en-US => en_US)
  1092. * en-US => en_US
  1093. * en_US => en_US
  1094. * en-us => en_us
  1095. * * java
  1096. * like default, but the second part will be always in uppercase
  1097. * en-US => en_US
  1098. * en_US => en_US
  1099. * en-us => en_US
  1100. * * BCP 47 (RFC 4646 & 4647)
  1101. * en-US => en-US
  1102. * en_US => en-US
  1103. * en-us => en-US
  1104. *
  1105. * See also:
  1106. * * http://en.wikipedia.org/wiki/IETF_language_tag
  1107. * * http://www.w3.org/International/core/langtags/
  1108. * * http://tools.ietf.org/html/bcp47
  1109. *
  1110. * @param {string|object} options - options (or standard)
  1111. * @param {string} options.standard - valid values are 'default', 'bcp47', 'java'
  1112. */
  1113. this.uniformLanguageTag = function (options) {
  1114. if (!options) {
  1115. options = {};
  1116. } else if (angular.isString(options)) {
  1117. options = {
  1118. standard : options
  1119. };
  1120. }
  1121. uniformLanguageTagResolver = options.standard;
  1122. return this;
  1123. };
  1124. /**
  1125. * @ngdoc function
  1126. * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage
  1127. * @methodOf pascalprecht.translate.$translateProvider
  1128. *
  1129. * @description
  1130. * Tells angular-translate to try to determine on its own which language key
  1131. * to set as preferred language. When `fn` is given, angular-translate uses it
  1132. * to determine a language key, otherwise it uses the built-in `getLocale()`
  1133. * method.
  1134. *
  1135. * The `getLocale()` returns a language key in the format `[lang]_[country]` or
  1136. * `[lang]` depending on what the browser provides.
  1137. *
  1138. * Use this method at your own risk, since not all browsers return a valid
  1139. * locale (see {@link pascalprecht.translate.$translateProvider#methods_uniformLanguageTag uniformLanguageTag()}).
  1140. *
  1141. * @param {Function=} fn Function to determine a browser's locale
  1142. */
  1143. this.determinePreferredLanguage = function (fn) {
  1144. var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale();
  1145. if (!$availableLanguageKeys.length) {
  1146. $preferredLanguage = locale;
  1147. } else {
  1148. $preferredLanguage = negotiateLocale(locale) || locale;
  1149. }
  1150. return this;
  1151. };
  1152. /**
  1153. * @ngdoc function
  1154. * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys
  1155. * @methodOf pascalprecht.translate.$translateProvider
  1156. *
  1157. * @description
  1158. * Registers a set of language keys the app will work with. Use this method in
  1159. * combination with
  1160. * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
  1161. * When available languages keys are registered, angular-translate
  1162. * tries to find the best fitting language key depending on the browsers locale,
  1163. * considering your language key convention.
  1164. *
  1165. * @param {object} languageKeys Array of language keys the your app will use
  1166. * @param {object=} aliases Alias map.
  1167. */
  1168. this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
  1169. if (languageKeys) {
  1170. $availableLanguageKeys = languageKeys;
  1171. if (aliases) {
  1172. $languageKeyAliases = aliases;
  1173. }
  1174. return this;
  1175. }
  1176. return $availableLanguageKeys;
  1177. };
  1178. /**
  1179. * @ngdoc function
  1180. * @name pascalprecht.translate.$translateProvider#useLoaderCache
  1181. * @methodOf pascalprecht.translate.$translateProvider
  1182. *
  1183. * @description
  1184. * Registers a cache for internal $http based loaders.
  1185. * {@link pascalprecht.translate.$translationCache $translationCache}.
  1186. * When false the cache will be disabled (default). When true or undefined
  1187. * the cache will be a default (see $cacheFactory). When an object it will
  1188. * be treat as a cache object itself: the usage is $http({cache: cache})
  1189. *
  1190. * @param {object} cache boolean, string or cache-object
  1191. */
  1192. this.useLoaderCache = function (cache) {
  1193. if (cache === false) {
  1194. // disable cache
  1195. loaderCache = undefined;
  1196. } else if (cache === true) {
  1197. // enable cache using AJS defaults
  1198. loaderCache = true;
  1199. } else if (typeof(cache) === 'undefined') {
  1200. // enable cache using default
  1201. loaderCache = '$translationCache';
  1202. } else if (cache) {
  1203. // enable cache using given one (see $cacheFactory)
  1204. loaderCache = cache;
  1205. }
  1206. return this;
  1207. };
  1208. /**
  1209. * @ngdoc function
  1210. * @name pascalprecht.translate.$translateProvider#directivePriority
  1211. * @methodOf pascalprecht.translate.$translateProvider
  1212. *
  1213. * @description
  1214. * Sets the default priority of the translate directive. The standard value is `0`.
  1215. * Calling this function without an argument will return the current value.
  1216. *
  1217. * @param {number} priority for the translate-directive
  1218. */
  1219. this.directivePriority = function (priority) {
  1220. if (priority === undefined) {
  1221. // getter
  1222. return directivePriority;
  1223. } else {
  1224. // setter with chaining
  1225. directivePriority = priority;
  1226. return this;
  1227. }
  1228. };
  1229. /**
  1230. * @ngdoc function
  1231. * @name pascalprecht.translate.$translateProvider#statefulFilter
  1232. * @methodOf pascalprecht.translate.$translateProvider
  1233. *
  1234. * @description
  1235. * Since AngularJS 1.3, filters which are not stateless (depending at the scope)
  1236. * have to explicit define this behavior.
  1237. * Sets whether the translate filter should be stateful or stateless. The standard value is `true`
  1238. * meaning being stateful.
  1239. * Calling this function without an argument will return the current value.
  1240. *
  1241. * @param {boolean} state - defines the state of the filter
  1242. */
  1243. this.statefulFilter = function (state) {
  1244. if (state === undefined) {
  1245. // getter
  1246. return statefulFilter;
  1247. } else {
  1248. // setter with chaining
  1249. statefulFilter = state;
  1250. return this;
  1251. }
  1252. };
  1253. /**
  1254. * @ngdoc function
  1255. * @name pascalprecht.translate.$translateProvider#postProcess
  1256. * @methodOf pascalprecht.translate.$translateProvider
  1257. *
  1258. * @description
  1259. * The post processor will be intercept right after the translation result. It can modify the result.
  1260. *
  1261. * @param {object} fn Function or service name (string) to be called after the translation value has been set / resolved. The function itself will enrich every value being processed and then continue the normal resolver process
  1262. */
  1263. this.postProcess = function (fn) {
  1264. if (fn) {
  1265. postProcessFn = fn;
  1266. } else {
  1267. postProcessFn = undefined;
  1268. }
  1269. return this;
  1270. };
  1271. /**
  1272. * @ngdoc function
  1273. * @name pascalprecht.translate.$translateProvider#keepContent
  1274. * @methodOf pascalprecht.translate.$translateProvider
  1275. *
  1276. * @description
  1277. * If keepContent is set to true than translate directive will always use innerHTML
  1278. * as a default translation
  1279. *
  1280. * Example:
  1281. * <pre>
  1282. * app.config(function ($translateProvider) {
  1283. * $translateProvider.keepContent(true);
  1284. * });
  1285. * </pre>
  1286. *
  1287. * @param {boolean} value - valid values are true or false
  1288. */
  1289. this.keepContent = function (value) {
  1290. $keepContent = !(!value);
  1291. return this;
  1292. };
  1293. /**
  1294. * @ngdoc object
  1295. * @name pascalprecht.translate.$translate
  1296. * @requires $interpolate
  1297. * @requires $log
  1298. * @requires $rootScope
  1299. * @requires $q
  1300. *
  1301. * @description
  1302. * The `$translate` service is the actual core of angular-translate. It expects a translation id
  1303. * and optional interpolate parameters to translate contents.
  1304. *
  1305. * <pre>
  1306. * $translate('HEADLINE_TEXT').then(function (translation) {
  1307. * $scope.translatedText = translation;
  1308. * });
  1309. * </pre>
  1310. *
  1311. * @param {string|array} translationId A token which represents a translation id
  1312. * This can be optionally an array of translation ids which
  1313. * results that the function returns an object where each key
  1314. * is the translation id and the value the translation.
  1315. * @param {object=} interpolateParams An object hash for dynamic values
  1316. * @param {string} interpolationId The id of the interpolation to use
  1317. * @param {string} defaultTranslationText the optional default translation text that is written as
  1318. * as default text in case it is not found in any configured language
  1319. * @param {string} forceLanguage A language to be used instead of the current language
  1320. * @returns {object} promise
  1321. */
  1322. this.$get = ['$log', '$injector', '$rootScope', '$q', function ($log, $injector, $rootScope, $q) {
  1323. var Storage,
  1324. defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
  1325. pendingLoader = false,
  1326. interpolatorHashMap = {},
  1327. langPromises = {},
  1328. fallbackIndex,
  1329. startFallbackIteration;
  1330. var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage) {
  1331. if (!$uses && $preferredLanguage) {
  1332. $uses = $preferredLanguage;
  1333. }
  1334. var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
  1335. (negotiateLocale(forceLanguage) || forceLanguage) : $uses;
  1336. // Check forceLanguage is present
  1337. if (forceLanguage) {
  1338. loadTranslationsIfMissing(forceLanguage);
  1339. }
  1340. // Duck detection: If the first argument is an array, a bunch of translations was requested.
  1341. // The result is an object.
  1342. if (angular.isArray(translationId)) {
  1343. // Inspired by Q.allSettled by Kris Kowal
  1344. // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
  1345. // This transforms all promises regardless resolved or rejected
  1346. var translateAll = function (translationIds) {
  1347. var results = {}; // storing the actual results
  1348. var promises = []; // promises to wait for
  1349. // Wraps the promise a) being always resolved and b) storing the link id->value
  1350. var translate = function (translationId) {
  1351. var deferred = $q.defer();
  1352. var regardless = function (value) {
  1353. results[translationId] = value;
  1354. deferred.resolve([translationId, value]);
  1355. };
  1356. // we don't care whether the promise was resolved or rejected; just store the values
  1357. $translate(translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage).then(regardless, regardless);
  1358. return deferred.promise;
  1359. };
  1360. for (var i = 0, c = translationIds.length; i < c; i++) {
  1361. promises.push(translate(translationIds[i]));
  1362. }
  1363. // wait for all (including storing to results)
  1364. return $q.all(promises).then(function () {
  1365. // return the results
  1366. return results;
  1367. });
  1368. };
  1369. return translateAll(translationId);
  1370. }
  1371. var deferred = $q.defer();
  1372. // trim off any whitespace
  1373. if (translationId) {
  1374. translationId = trim.apply(translationId);
  1375. }
  1376. var promiseToWaitFor = (function () {
  1377. var promise = $preferredLanguage ?
  1378. langPromises[$preferredLanguage] :
  1379. langPromises[uses];
  1380. fallbackIndex = 0;
  1381. if ($storageFactory && !promise) {
  1382. // looks like there's no pending promise for $preferredLanguage or
  1383. // $uses. Maybe there's one pending for a language that comes from
  1384. // storage.
  1385. var langKey = Storage.get($storageKey);
  1386. promise = langPromises[langKey];
  1387. if ($fallbackLanguage && $fallbackLanguage.length) {
  1388. var index = indexOf($fallbackLanguage, langKey);
  1389. // maybe the language from storage is also defined as fallback language
  1390. // we increase the fallback language index to not search in that language
  1391. // as fallback, since it's probably the first used language
  1392. // in that case the index starts after the first element
  1393. fallbackIndex = (index === 0) ? 1 : 0;
  1394. // but we can make sure to ALWAYS fallback to preferred language at least
  1395. if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
  1396. $fallbackLanguage.push($preferredLanguage);
  1397. }
  1398. }
  1399. }
  1400. return promise;
  1401. }());
  1402. if (!promiseToWaitFor) {
  1403. // no promise to wait for? okay. Then there's no loader registered
  1404. // nor is a one pending for language that comes from storage.
  1405. // We can just translate.
  1406. determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject);
  1407. } else {
  1408. var promiseResolved = function () {
  1409. // $uses may have changed while waiting
  1410. if (!forceLanguage) {
  1411. uses = $uses;
  1412. }
  1413. determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject);
  1414. };
  1415. promiseResolved.displayName = 'promiseResolved';
  1416. promiseToWaitFor['finally'](promiseResolved)
  1417. .catch(angular.noop); // we don't care about errors here, already handled
  1418. }
  1419. return deferred.promise;
  1420. };
  1421. /**
  1422. * @name applyNotFoundIndicators
  1423. * @private
  1424. *
  1425. * @description
  1426. * Applies not fount indicators to given translation id, if needed.
  1427. * This function gets only executed, if a translation id doesn't exist,
  1428. * which is why a translation id is expected as argument.
  1429. *
  1430. * @param {string} translationId Translation id.
  1431. * @returns {string} Same as given translation id but applied with not found
  1432. * indicators.
  1433. */
  1434. var applyNotFoundIndicators = function (translationId) {
  1435. // applying notFoundIndicators
  1436. if ($notFoundIndicatorLeft) {
  1437. translationId = [$notFoundIndicatorLeft, translationId].join(' ');
  1438. }
  1439. if ($notFoundIndicatorRight) {
  1440. translationId = [translationId, $notFoundIndicatorRight].join(' ');
  1441. }
  1442. return translationId;
  1443. };
  1444. /**
  1445. * @name useLanguage
  1446. * @private
  1447. *
  1448. * @description
  1449. * Makes actual use of a language by setting a given language key as used
  1450. * language and informs registered interpolators to also use the given
  1451. * key as locale.
  1452. *
  1453. * @param {string} key Locale key.
  1454. */
  1455. var useLanguage = function (key) {
  1456. $uses = key;
  1457. // make sure to store new language key before triggering success event
  1458. if ($storageFactory) {
  1459. Storage.put($translate.storageKey(), $uses);
  1460. }
  1461. $rootScope.$emit('$translateChangeSuccess', {language : key});
  1462. // inform default interpolator
  1463. defaultInterpolator.setLocale($uses);
  1464. var eachInterpolator = function (interpolator, id) {
  1465. interpolatorHashMap[id].setLocale($uses);
  1466. };
  1467. eachInterpolator.displayName = 'eachInterpolatorLocaleSetter';
  1468. // inform all others too!
  1469. angular.forEach(interpolatorHashMap, eachInterpolator);
  1470. $rootScope.$emit('$translateChangeEnd', {language : key});
  1471. };
  1472. /**
  1473. * @name loadAsync
  1474. * @private
  1475. *
  1476. * @description
  1477. * Kicks off registered async loader using `$injector` and applies existing
  1478. * loader options. When resolved, it updates translation tables accordingly
  1479. * or rejects with given language key.
  1480. *
  1481. * @param {string} key Language key.
  1482. * @return {Promise} A promise.
  1483. */
  1484. var loadAsync = function (key) {
  1485. if (!key) {
  1486. throw 'No language key specified for loading.';
  1487. }
  1488. var deferred = $q.defer();
  1489. $rootScope.$emit('$translateLoadingStart', {language : key});
  1490. pendingLoader = true;
  1491. var cache = loaderCache;
  1492. if (typeof(cache) === 'string') {
  1493. // getting on-demand instance of loader
  1494. cache = $injector.get(cache);
  1495. }
  1496. var loaderOptions = angular.extend({}, $loaderOptions, {
  1497. key : key,
  1498. $http : angular.extend({}, {
  1499. cache : cache
  1500. }, $loaderOptions.$http)
  1501. });
  1502. var onLoaderSuccess = function (data) {
  1503. var translationTable = {};
  1504. $rootScope.$emit('$translateLoadingSuccess', {language : key});
  1505. if (angular.isArray(data)) {
  1506. angular.forEach(data, function (table) {
  1507. angular.extend(translationTable, flatObject(table));
  1508. });
  1509. } else {
  1510. angular.extend(translationTable, flatObject(data));
  1511. }
  1512. pendingLoader = false;
  1513. deferred.resolve({
  1514. key : key,
  1515. table : translationTable
  1516. });
  1517. $rootScope.$emit('$translateLoadingEnd', {language : key});
  1518. };
  1519. onLoaderSuccess.displayName = 'onLoaderSuccess';
  1520. var onLoaderError = function (key) {
  1521. $rootScope.$emit('$translateLoadingError', {language : key});
  1522. deferred.reject(key);
  1523. $rootScope.$emit('$translateLoadingEnd', {language : key});
  1524. };
  1525. onLoaderError.displayName = 'onLoaderError';
  1526. $injector.get($loaderFactory)(loaderOptions)
  1527. .then(onLoaderSuccess, onLoaderError);
  1528. return deferred.promise;
  1529. };
  1530. if ($storageFactory) {
  1531. Storage = $injector.get($storageFactory);
  1532. if (!Storage.get || !Storage.put) {
  1533. throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!');
  1534. }
  1535. }
  1536. // if we have additional interpolations that were added via
  1537. // $translateProvider.addInterpolation(), we have to map'em
  1538. if ($interpolatorFactories.length) {
  1539. var eachInterpolationFactory = function (interpolatorFactory) {
  1540. var interpolator = $injector.get(interpolatorFactory);
  1541. // setting initial locale for each interpolation service
  1542. interpolator.setLocale($preferredLanguage || $uses);
  1543. // make'em recognizable through id
  1544. interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
  1545. };
  1546. eachInterpolationFactory.displayName = 'interpolationFactoryAdder';
  1547. angular.forEach($interpolatorFactories, eachInterpolationFactory);
  1548. }
  1549. /**
  1550. * @name getTranslationTable
  1551. * @private
  1552. *
  1553. * @description
  1554. * Returns a promise that resolves to the translation table
  1555. * or is rejected if an error occurred.
  1556. *
  1557. * @param langKey
  1558. * @returns {Q.promise}
  1559. */
  1560. var getTranslationTable = function (langKey) {
  1561. var deferred = $q.defer();
  1562. if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) {
  1563. deferred.resolve($translationTable[langKey]);
  1564. } else if (langPromises[langKey]) {
  1565. var onResolve = function (data) {
  1566. translations(data.key, data.table);
  1567. deferred.resolve(data.table);
  1568. };
  1569. onResolve.displayName = 'translationTableResolver';
  1570. langPromises[langKey].then(onResolve, deferred.reject);
  1571. } else {
  1572. deferred.reject();
  1573. }
  1574. return deferred.promise;
  1575. };
  1576. /**
  1577. * @name getFallbackTranslation
  1578. * @private
  1579. *
  1580. * @description
  1581. * Returns a promise that will resolve to the translation
  1582. * or be rejected if no translation was found for the language.
  1583. * This function is currently only used for fallback language translation.
  1584. *
  1585. * @param langKey The language to translate to.
  1586. * @param translationId
  1587. * @param interpolateParams
  1588. * @param Interpolator
  1589. * @param sanitizeStrategy
  1590. * @returns {Q.promise}
  1591. */
  1592. var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
  1593. var deferred = $q.defer();
  1594. var onResolve = function (translationTable) {
  1595. if (Object.prototype.hasOwnProperty.call(translationTable, translationId) && translationTable[translationId] !== null) {
  1596. Interpolator.setLocale(langKey);
  1597. var translation = translationTable[translationId];
  1598. if (translation.substr(0, 2) === '@:') {
  1599. getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator, sanitizeStrategy)
  1600. .then(deferred.resolve, deferred.reject);
  1601. } else {
  1602. var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service', sanitizeStrategy, translationId);
  1603. interpolatedValue = applyPostProcessing(translationId, translationTable[translationId], interpolatedValue, interpolateParams, langKey);
  1604. deferred.resolve(interpolatedValue);
  1605. }
  1606. Interpolator.setLocale($uses);
  1607. } else {
  1608. deferred.reject();
  1609. }
  1610. };
  1611. onResolve.displayName = 'fallbackTranslationResolver';
  1612. getTranslationTable(langKey).then(onResolve, deferred.reject);
  1613. return deferred.promise;
  1614. };
  1615. /**
  1616. * @name getFallbackTranslationInstant
  1617. * @private
  1618. *
  1619. * @description
  1620. * Returns a translation
  1621. * This function is currently only used for fallback language translation.
  1622. *
  1623. * @param langKey The language to translate to.
  1624. * @param translationId
  1625. * @param interpolateParams
  1626. * @param Interpolator
  1627. * @param sanitizeStrategy sanitize strategy override
  1628. *
  1629. * @returns {string} translation
  1630. */
  1631. var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
  1632. var result, translationTable = $translationTable[langKey];
  1633. if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId) && translationTable[translationId] !== null) {
  1634. Interpolator.setLocale(langKey);
  1635. result = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'filter', sanitizeStrategy, translationId);
  1636. result = applyPostProcessing(translationId, translationTable[translationId], result, interpolateParams, langKey, sanitizeStrategy);
  1637. // workaround for TrustedValueHolderType
  1638. if (!angular.isString(result) && angular.isFunction(result.$$unwrapTrustedValue)) {
  1639. var result2 = result.$$unwrapTrustedValue();
  1640. if (result2.substr(0, 2) === '@:') {
  1641. return getFallbackTranslationInstant(langKey, result2.substr(2), interpolateParams, Interpolator, sanitizeStrategy);
  1642. }
  1643. } else if (result.substr(0, 2) === '@:') {
  1644. return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator, sanitizeStrategy);
  1645. }
  1646. Interpolator.setLocale($uses);
  1647. }
  1648. return result;
  1649. };
  1650. /**
  1651. * @name translateByHandler
  1652. * @private
  1653. *
  1654. * Translate by missing translation handler.
  1655. *
  1656. * @param translationId
  1657. * @param interpolateParams
  1658. * @param defaultTranslationText
  1659. * @param sanitizeStrategy sanitize strategy override
  1660. *
  1661. * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
  1662. * absent
  1663. */
  1664. var translateByHandler = function (translationId, interpolateParams, defaultTranslationText, sanitizeStrategy) {
  1665. // If we have a handler factory - we might also call it here to determine if it provides
  1666. // a default text for a translationid that can't be found anywhere in our tables
  1667. if ($missingTranslationHandlerFactory) {
  1668. return $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams, defaultTranslationText, sanitizeStrategy);
  1669. } else {
  1670. return translationId;
  1671. }
  1672. };
  1673. /**
  1674. * @name resolveForFallbackLanguage
  1675. * @private
  1676. *
  1677. * Recursive helper function for fallbackTranslation that will sequentially look
  1678. * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
  1679. *
  1680. * @param fallbackLanguageIndex
  1681. * @param translationId
  1682. * @param interpolateParams
  1683. * @param Interpolator
  1684. * @param defaultTranslationText
  1685. * @param sanitizeStrategy
  1686. * @returns {Q.promise} Promise that will resolve to the translation.
  1687. */
  1688. var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy) {
  1689. var deferred = $q.defer();
  1690. if (fallbackLanguageIndex < $fallbackLanguage.length) {
  1691. var langKey = $fallbackLanguage[fallbackLanguageIndex];
  1692. getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy).then(
  1693. function (data) {
  1694. deferred.resolve(data);
  1695. },
  1696. function () {
  1697. // Look in the next fallback language for a translation.
  1698. // It delays the resolving by passing another promise to resolve.
  1699. return resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy).then(deferred.resolve, deferred.reject);
  1700. }
  1701. );
  1702. } else {
  1703. // No translation found in any fallback language
  1704. // if a default translation text is set in the directive, then return this as a result
  1705. if (defaultTranslationText) {
  1706. deferred.resolve(defaultTranslationText);
  1707. } else {
  1708. var missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText);
  1709. // if no default translation is set and an error handler is defined, send it to the handler
  1710. // and then return the result if it isn't undefined
  1711. if ($missingTranslationHandlerFactory && missingTranslationHandlerTranslation) {
  1712. deferred.resolve(missingTranslationHandlerTranslation);
  1713. } else {
  1714. deferred.reject(applyNotFoundIndicators(translationId));
  1715. }
  1716. }
  1717. }
  1718. return deferred.promise;
  1719. };
  1720. /**
  1721. * @name resolveForFallbackLanguageInstant
  1722. * @private
  1723. *
  1724. * Recursive helper function for fallbackTranslation that will sequentially look
  1725. * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
  1726. *
  1727. * @param fallbackLanguageIndex
  1728. * @param translationId
  1729. * @param interpolateParams
  1730. * @param Interpolator
  1731. * @param sanitizeStrategy
  1732. * @returns {string} translation
  1733. */
  1734. var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
  1735. var result;
  1736. if (fallbackLanguageIndex < $fallbackLanguage.length) {
  1737. var langKey = $fallbackLanguage[fallbackLanguageIndex];
  1738. result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy);
  1739. if (!result && result !== '') {
  1740. result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
  1741. }
  1742. }
  1743. return result;
  1744. };
  1745. /**
  1746. * Translates with the usage of the fallback languages.
  1747. *
  1748. * @param translationId
  1749. * @param interpolateParams
  1750. * @param Interpolator
  1751. * @param defaultTranslationText
  1752. * @param sanitizeStrategy
  1753. * @returns {Q.promise} Promise, that resolves to the translation.
  1754. */
  1755. var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy) {
  1756. // Start with the fallbackLanguage with index 0
  1757. return resolveForFallbackLanguage((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy);
  1758. };
  1759. /**
  1760. * Translates with the usage of the fallback languages.
  1761. *
  1762. * @param translationId
  1763. * @param interpolateParams
  1764. * @param Interpolator
  1765. * @param sanitizeStrategy
  1766. * @returns {String} translation
  1767. */
  1768. var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator, sanitizeStrategy) {
  1769. // Start with the fallbackLanguage with index 0
  1770. return resolveForFallbackLanguageInstant((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, sanitizeStrategy);
  1771. };
  1772. var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText, uses, sanitizeStrategy) {
  1773. var deferred = $q.defer();
  1774. var table = uses ? $translationTable[uses] : $translationTable,
  1775. Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;
  1776. // if the translation id exists, we can just interpolate it
  1777. if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null) {
  1778. var translation = table[translationId];
  1779. // If using link, rerun $translate with linked translationId and return it
  1780. if (translation.substr(0, 2) === '@:') {
  1781. $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText, uses)
  1782. .then(deferred.resolve, deferred.reject);
  1783. } else {
  1784. //
  1785. var resolvedTranslation = Interpolator.interpolate(translation, interpolateParams, 'service', sanitizeStrategy, translationId);
  1786. resolvedTranslation = applyPostProcessing(translationId, translation, resolvedTranslation, interpolateParams, uses);
  1787. deferred.resolve(resolvedTranslation);
  1788. }
  1789. } else {
  1790. var missingTranslationHandlerTranslation;
  1791. // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
  1792. if ($missingTranslationHandlerFactory && !pendingLoader) {
  1793. missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText);
  1794. }
  1795. // since we couldn't translate the inital requested translation id,
  1796. // we try it now with one or more fallback languages, if fallback language(s) is
  1797. // configured.
  1798. if (uses && $fallbackLanguage && $fallbackLanguage.length) {
  1799. fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy)
  1800. .then(function (translation) {
  1801. deferred.resolve(translation);
  1802. }, function (_translationId) {
  1803. deferred.reject(applyNotFoundIndicators(_translationId));
  1804. });
  1805. } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
  1806. // looks like the requested translation id doesn't exists.
  1807. // Now, if there is a registered handler for missing translations and no
  1808. // asyncLoader is pending, we execute the handler
  1809. if (defaultTranslationText) {
  1810. deferred.resolve(defaultTranslationText);
  1811. } else {
  1812. deferred.resolve(missingTranslationHandlerTranslation);
  1813. }
  1814. } else {
  1815. if (defaultTranslationText) {
  1816. deferred.resolve(defaultTranslationText);
  1817. } else {
  1818. deferred.reject(applyNotFoundIndicators(translationId));
  1819. }
  1820. }
  1821. }
  1822. return deferred.promise;
  1823. };
  1824. var determineTranslationInstant = function (translationId, interpolateParams, interpolationId, uses, sanitizeStrategy) {
  1825. var result, table = uses ? $translationTable[uses] : $translationTable,
  1826. Interpolator = defaultInterpolator;
  1827. // if the interpolation id exists use custom interpolator
  1828. if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
  1829. Interpolator = interpolatorHashMap[interpolationId];
  1830. }
  1831. // if the translation id exists, we can just interpolate it
  1832. if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null) {
  1833. var translation = table[translationId];
  1834. // If using link, rerun $translate with linked translationId and return it
  1835. if (translation.substr(0, 2) === '@:') {
  1836. result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId, uses, sanitizeStrategy);
  1837. } else {
  1838. result = Interpolator.interpolate(translation, interpolateParams, 'filter', sanitizeStrategy, translationId);
  1839. result = applyPostProcessing(translationId, translation, result, interpolateParams, uses, sanitizeStrategy);
  1840. }
  1841. } else {
  1842. var missingTranslationHandlerTranslation;
  1843. // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
  1844. if ($missingTranslationHandlerFactory && !pendingLoader) {
  1845. missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, sanitizeStrategy);
  1846. }
  1847. // since we couldn't translate the inital requested translation id,
  1848. // we try it now with one or more fallback languages, if fallback language(s) is
  1849. // configured.
  1850. if (uses && $fallbackLanguage && $fallbackLanguage.length) {
  1851. fallbackIndex = 0;
  1852. result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator, sanitizeStrategy);
  1853. } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
  1854. // looks like the requested translation id doesn't exists.
  1855. // Now, if there is a registered handler for missing translations and no
  1856. // asyncLoader is pending, we execute the handler
  1857. result = missingTranslationHandlerTranslation;
  1858. } else {
  1859. result = applyNotFoundIndicators(translationId);
  1860. }
  1861. }
  1862. return result;
  1863. };
  1864. var clearNextLangAndPromise = function (key) {
  1865. if ($nextLang === key) {
  1866. $nextLang = undefined;
  1867. }
  1868. langPromises[key] = undefined;
  1869. };
  1870. var applyPostProcessing = function (translationId, translation, resolvedTranslation, interpolateParams, uses, sanitizeStrategy) {
  1871. var fn = postProcessFn;
  1872. if (fn) {
  1873. if (typeof(fn) === 'string') {
  1874. // getting on-demand instance
  1875. fn = $injector.get(fn);
  1876. }
  1877. if (fn) {
  1878. return fn(translationId, translation, resolvedTranslation, interpolateParams, uses, sanitizeStrategy);
  1879. }
  1880. }
  1881. return resolvedTranslation;
  1882. };
  1883. var loadTranslationsIfMissing = function (key) {
  1884. if (!$translationTable[key] && $loaderFactory && !langPromises[key]) {
  1885. langPromises[key] = loadAsync(key).then(function (translation) {
  1886. translations(translation.key, translation.table);
  1887. return translation;
  1888. });
  1889. }
  1890. };
  1891. /**
  1892. * @ngdoc function
  1893. * @name pascalprecht.translate.$translate#preferredLanguage
  1894. * @methodOf pascalprecht.translate.$translate
  1895. *
  1896. * @description
  1897. * Returns the language key for the preferred language.
  1898. *
  1899. * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime)
  1900. *
  1901. * @return {string} preferred language key
  1902. */
  1903. $translate.preferredLanguage = function (langKey) {
  1904. if (langKey) {
  1905. setupPreferredLanguage(langKey);
  1906. }
  1907. return $preferredLanguage;
  1908. };
  1909. /**
  1910. * @ngdoc function
  1911. * @name pascalprecht.translate.$translate#cloakClassName
  1912. * @methodOf pascalprecht.translate.$translate
  1913. *
  1914. * @description
  1915. * Returns the configured class name for `translate-cloak` directive.
  1916. *
  1917. * @return {string} cloakClassName
  1918. */
  1919. $translate.cloakClassName = function () {
  1920. return $cloakClassName;
  1921. };
  1922. /**
  1923. * @ngdoc function
  1924. * @name pascalprecht.translate.$translate#nestedObjectDelimeter
  1925. * @methodOf pascalprecht.translate.$translate
  1926. *
  1927. * @description
  1928. * Returns the configured delimiter for nested namespaces.
  1929. *
  1930. * @return {string} nestedObjectDelimeter
  1931. */
  1932. $translate.nestedObjectDelimeter = function () {
  1933. return $nestedObjectDelimeter;
  1934. };
  1935. /**
  1936. * @ngdoc function
  1937. * @name pascalprecht.translate.$translate#fallbackLanguage
  1938. * @methodOf pascalprecht.translate.$translate
  1939. *
  1940. * @description
  1941. * Returns the language key for the fallback languages or sets a new fallback stack.
  1942. *
  1943. * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime)
  1944. *
  1945. * @return {string||array} fallback language key
  1946. */
  1947. $translate.fallbackLanguage = function (langKey) {
  1948. if (langKey !== undefined && langKey !== null) {
  1949. fallbackStack(langKey);
  1950. // as we might have an async loader initiated and a new translation language might have been defined
  1951. // we need to add the promise to the stack also. So - iterate.
  1952. if ($loaderFactory) {
  1953. if ($fallbackLanguage && $fallbackLanguage.length) {
  1954. for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
  1955. if (!langPromises[$fallbackLanguage[i]]) {
  1956. langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
  1957. }
  1958. }
  1959. }
  1960. }
  1961. $translate.use($translate.use());
  1962. }
  1963. if ($fallbackWasString) {
  1964. return $fallbackLanguage[0];
  1965. } else {
  1966. return $fallbackLanguage;
  1967. }
  1968. };
  1969. /**
  1970. * @ngdoc function
  1971. * @name pascalprecht.translate.$translate#useFallbackLanguage
  1972. * @methodOf pascalprecht.translate.$translate
  1973. *
  1974. * @description
  1975. * Sets the first key of the fallback language stack to be used for translation.
  1976. * Therefore all languages in the fallback array BEFORE this key will be skipped!
  1977. *
  1978. * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to
  1979. * get back to the whole stack
  1980. */
  1981. $translate.useFallbackLanguage = function (langKey) {
  1982. if (langKey !== undefined && langKey !== null) {
  1983. if (!langKey) {
  1984. startFallbackIteration = 0;
  1985. } else {
  1986. var langKeyPosition = indexOf($fallbackLanguage, langKey);
  1987. if (langKeyPosition > -1) {
  1988. startFallbackIteration = langKeyPosition;
  1989. }
  1990. }
  1991. }
  1992. };
  1993. /**
  1994. * @ngdoc function
  1995. * @name pascalprecht.translate.$translate#proposedLanguage
  1996. * @methodOf pascalprecht.translate.$translate
  1997. *
  1998. * @description
  1999. * Returns the language key of language that is currently loaded asynchronously.
  2000. *
  2001. * @return {string} language key
  2002. */
  2003. $translate.proposedLanguage = function () {
  2004. return $nextLang;
  2005. };
  2006. /**
  2007. * @ngdoc function
  2008. * @name pascalprecht.translate.$translate#storage
  2009. * @methodOf pascalprecht.translate.$translate
  2010. *
  2011. * @description
  2012. * Returns registered storage.
  2013. *
  2014. * @return {object} Storage
  2015. */
  2016. $translate.storage = function () {
  2017. return Storage;
  2018. };
  2019. /**
  2020. * @ngdoc function
  2021. * @name pascalprecht.translate.$translate#negotiateLocale
  2022. * @methodOf pascalprecht.translate.$translate
  2023. *
  2024. * @description
  2025. * Returns a language key based on available languages and language aliases. If a
  2026. * language key cannot be resolved, returns undefined.
  2027. *
  2028. * If no or a falsy key is given, returns undefined.
  2029. *
  2030. * @param {string} [key] Language key
  2031. * @return {string|undefined} Language key or undefined if no language key is found.
  2032. */
  2033. $translate.negotiateLocale = negotiateLocale;
  2034. /**
  2035. * @ngdoc function
  2036. * @name pascalprecht.translate.$translate#use
  2037. * @methodOf pascalprecht.translate.$translate
  2038. *
  2039. * @description
  2040. * Tells angular-translate which language to use by given language key. This method is
  2041. * used to change language at runtime. It also takes care of storing the language
  2042. * key in a configured store to let your app remember the choosed language.
  2043. *
  2044. * When trying to 'use' a language which isn't available it tries to load it
  2045. * asynchronously with registered loaders.
  2046. *
  2047. * Returns promise object with loaded language file data or string of the currently used language.
  2048. *
  2049. * If no or a falsy key is given it returns the currently used language key.
  2050. * The returned string will be ```undefined``` if setting up $translate hasn't finished.
  2051. * @example
  2052. * $translate.use("en_US").then(function(data){
  2053. * $scope.text = $translate("HELLO");
  2054. * });
  2055. *
  2056. * @param {string} [key] Language key
  2057. * @return {object|string} Promise with loaded language data or the language key if a falsy param was given.
  2058. */
  2059. $translate.use = function (key) {
  2060. if (!key) {
  2061. return $uses;
  2062. }
  2063. var deferred = $q.defer();
  2064. deferred.promise.then(null, angular.noop); // AJS "Possibly unhandled rejection"
  2065. $rootScope.$emit('$translateChangeStart', {language : key});
  2066. // Try to get the aliased language key
  2067. var aliasedKey = negotiateLocale(key);
  2068. // Ensure only registered language keys will be loaded
  2069. if ($availableLanguageKeys.length > 0 && !aliasedKey) {
  2070. return $q.reject(key);
  2071. }
  2072. if (aliasedKey) {
  2073. key = aliasedKey;
  2074. }
  2075. // if there isn't a translation table for the language we've requested,
  2076. // we load it asynchronously
  2077. $nextLang = key;
  2078. if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) {
  2079. langPromises[key] = loadAsync(key).then(function (translation) {
  2080. translations(translation.key, translation.table);
  2081. deferred.resolve(translation.key);
  2082. if ($nextLang === key) {
  2083. useLanguage(translation.key);
  2084. }
  2085. return translation;
  2086. }, function (key) {
  2087. $rootScope.$emit('$translateChangeError', {language : key});
  2088. deferred.reject(key);
  2089. $rootScope.$emit('$translateChangeEnd', {language : key});
  2090. return $q.reject(key);
  2091. });
  2092. langPromises[key]['finally'](function () {
  2093. clearNextLangAndPromise(key);
  2094. }).catch(angular.noop); // we don't care about errors (clearing)
  2095. } else if (langPromises[key]) {
  2096. // we are already loading this asynchronously
  2097. // resolve our new deferred when the old langPromise is resolved
  2098. langPromises[key].then(function (translation) {
  2099. if ($nextLang === translation.key) {
  2100. useLanguage(translation.key);
  2101. }
  2102. deferred.resolve(translation.key);
  2103. return translation;
  2104. }, function (key) {
  2105. // find first available fallback language if that request has failed
  2106. if (!$uses && $fallbackLanguage && $fallbackLanguage.length > 0 && $fallbackLanguage[0] !== key) {
  2107. return $translate.use($fallbackLanguage[0]).then(deferred.resolve, deferred.reject);
  2108. } else {
  2109. return deferred.reject(key);
  2110. }
  2111. });
  2112. } else {
  2113. deferred.resolve(key);
  2114. useLanguage(key);
  2115. }
  2116. return deferred.promise;
  2117. };
  2118. /**
  2119. * @ngdoc function
  2120. * @name pascalprecht.translate.$translate#resolveClientLocale
  2121. * @methodOf pascalprecht.translate.$translate
  2122. *
  2123. * @description
  2124. * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
  2125. *
  2126. * @returns {string} the current client/browser language key
  2127. */
  2128. $translate.resolveClientLocale = function () {
  2129. return getLocale();
  2130. };
  2131. /**
  2132. * @ngdoc function
  2133. * @name pascalprecht.translate.$translate#storageKey
  2134. * @methodOf pascalprecht.translate.$translate
  2135. *
  2136. * @description
  2137. * Returns the key for the storage.
  2138. *
  2139. * @return {string} storage key
  2140. */
  2141. $translate.storageKey = function () {
  2142. return storageKey();
  2143. };
  2144. /**
  2145. * @ngdoc function
  2146. * @name pascalprecht.translate.$translate#isPostCompilingEnabled
  2147. * @methodOf pascalprecht.translate.$translate
  2148. *
  2149. * @description
  2150. * Returns whether post compiling is enabled or not
  2151. *
  2152. * @return {bool} storage key
  2153. */
  2154. $translate.isPostCompilingEnabled = function () {
  2155. return $postCompilingEnabled;
  2156. };
  2157. /**
  2158. * @ngdoc function
  2159. * @name pascalprecht.translate.$translate#isForceAsyncReloadEnabled
  2160. * @methodOf pascalprecht.translate.$translate
  2161. *
  2162. * @description
  2163. * Returns whether force async reload is enabled or not
  2164. *
  2165. * @return {boolean} forceAsyncReload value
  2166. */
  2167. $translate.isForceAsyncReloadEnabled = function () {
  2168. return $forceAsyncReloadEnabled;
  2169. };
  2170. /**
  2171. * @ngdoc function
  2172. * @name pascalprecht.translate.$translate#isKeepContent
  2173. * @methodOf pascalprecht.translate.$translate
  2174. *
  2175. * @description
  2176. * Returns whether keepContent or not
  2177. *
  2178. * @return {boolean} keepContent value
  2179. */
  2180. $translate.isKeepContent = function () {
  2181. return $keepContent;
  2182. };
  2183. /**
  2184. * @ngdoc function
  2185. * @name pascalprecht.translate.$translate#refresh
  2186. * @methodOf pascalprecht.translate.$translate
  2187. *
  2188. * @description
  2189. * Refreshes a translation table pointed by the given langKey. If langKey is not specified,
  2190. * the module will drop all existent translation tables and load new version of those which
  2191. * are currently in use.
  2192. *
  2193. * Refresh means that the module will drop target translation table and try to load it again.
  2194. *
  2195. * In case there are no loaders registered the refresh() method will throw an Error.
  2196. *
  2197. * If the module is able to refresh translation tables refresh() method will broadcast
  2198. * $translateRefreshStart and $translateRefreshEnd events.
  2199. *
  2200. * @example
  2201. * // this will drop all currently existent translation tables and reload those which are
  2202. * // currently in use
  2203. * $translate.refresh();
  2204. * // this will refresh a translation table for the en_US language
  2205. * $translate.refresh('en_US');
  2206. *
  2207. * @param {string} langKey A language key of the table, which has to be refreshed
  2208. *
  2209. * @return {promise} Promise, which will be resolved in case a translation tables refreshing
  2210. * process is finished successfully, and reject if not.
  2211. */
  2212. $translate.refresh = function (langKey) {
  2213. if (!$loaderFactory) {
  2214. throw new Error('Couldn\'t refresh translation table, no loader registered!');
  2215. }
  2216. $rootScope.$emit('$translateRefreshStart', {language : langKey});
  2217. var deferred = $q.defer(), updatedLanguages = {};
  2218. //private helper
  2219. function loadNewData(languageKey) {
  2220. var promise = loadAsync(languageKey);
  2221. //update the load promise cache for this language
  2222. langPromises[languageKey] = promise;
  2223. //register a data handler for the promise
  2224. promise.then(function (data) {
  2225. //clear the cache for this language
  2226. $translationTable[languageKey] = {};
  2227. //add the new data for this language
  2228. translations(languageKey, data.table);
  2229. //track that we updated this language
  2230. updatedLanguages[languageKey] = true;
  2231. },
  2232. //handle rejection to appease the $q validation
  2233. angular.noop);
  2234. return promise;
  2235. }
  2236. //set up post-processing
  2237. deferred.promise.then(
  2238. function () {
  2239. for (var key in $translationTable) {
  2240. if ($translationTable.hasOwnProperty(key)) {
  2241. //delete cache entries that were not updated
  2242. if (!(key in updatedLanguages)) {
  2243. delete $translationTable[key];
  2244. }
  2245. }
  2246. }
  2247. if ($uses) {
  2248. useLanguage($uses);
  2249. }
  2250. },
  2251. //handle rejection to appease the $q validation
  2252. angular.noop
  2253. ).finally(
  2254. function () {
  2255. $rootScope.$emit('$translateRefreshEnd', {language : langKey});
  2256. }
  2257. );
  2258. if (!langKey) {
  2259. // if there's no language key specified we refresh ALL THE THINGS!
  2260. var languagesToReload = $fallbackLanguage && $fallbackLanguage.slice() || [];
  2261. if ($uses && languagesToReload.indexOf($uses) === -1) {
  2262. languagesToReload.push($uses);
  2263. }
  2264. $q.all(languagesToReload.map(loadNewData)).then(deferred.resolve, deferred.reject);
  2265. } else if ($translationTable[langKey]) {
  2266. //just refresh the specified language cache
  2267. loadNewData(langKey).then(deferred.resolve, deferred.reject);
  2268. } else {
  2269. deferred.reject();
  2270. }
  2271. return deferred.promise;
  2272. };
  2273. /**
  2274. * @ngdoc function
  2275. * @name pascalprecht.translate.$translate#instant
  2276. * @methodOf pascalprecht.translate.$translate
  2277. *
  2278. * @description
  2279. * Returns a translation instantly from the internal state of loaded translation. All rules
  2280. * regarding the current language, the preferred language of even fallback languages will be
  2281. * used except any promise handling. If a language was not found, an asynchronous loading
  2282. * will be invoked in the background.
  2283. *
  2284. * @param {string|array} translationId A token which represents a translation id
  2285. * This can be optionally an array of translation ids which
  2286. * results that the function's promise returns an object where
  2287. * each key is the translation id and the value the translation.
  2288. * @param {object} interpolateParams Params
  2289. * @param {string} interpolationId The id of the interpolation to use
  2290. * @param {string} forceLanguage A language to be used instead of the current language
  2291. * @param {string} sanitizeStrategy force sanitize strategy for this call instead of using the configured one
  2292. *
  2293. * @return {string|object} translation
  2294. */
  2295. $translate.instant = function (translationId, interpolateParams, interpolationId, forceLanguage, sanitizeStrategy) {
  2296. // we don't want to re-negotiate $uses
  2297. var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
  2298. (negotiateLocale(forceLanguage) || forceLanguage) : $uses;
  2299. // Detect undefined and null values to shorten the execution and prevent exceptions
  2300. if (translationId === null || angular.isUndefined(translationId)) {
  2301. return translationId;
  2302. }
  2303. // Check forceLanguage is present
  2304. if (forceLanguage) {
  2305. loadTranslationsIfMissing(forceLanguage);
  2306. }
  2307. // Duck detection: If the first argument is an array, a bunch of translations was requested.
  2308. // The result is an object.
  2309. if (angular.isArray(translationId)) {
  2310. var results = {};
  2311. for (var i = 0, c = translationId.length; i < c; i++) {
  2312. results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId, forceLanguage, sanitizeStrategy);
  2313. }
  2314. return results;
  2315. }
  2316. // We discarded unacceptable values. So we just need to verify if translationId is empty String
  2317. if (angular.isString(translationId) && translationId.length < 1) {
  2318. return translationId;
  2319. }
  2320. // trim off any whitespace
  2321. if (translationId) {
  2322. translationId = trim.apply(translationId);
  2323. }
  2324. var result, possibleLangKeys = [];
  2325. if ($preferredLanguage) {
  2326. possibleLangKeys.push($preferredLanguage);
  2327. }
  2328. if (uses) {
  2329. possibleLangKeys.push(uses);
  2330. }
  2331. if ($fallbackLanguage && $fallbackLanguage.length) {
  2332. possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
  2333. }
  2334. for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
  2335. var possibleLangKey = possibleLangKeys[j];
  2336. if ($translationTable[possibleLangKey]) {
  2337. if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
  2338. result = determineTranslationInstant(translationId, interpolateParams, interpolationId, uses, sanitizeStrategy);
  2339. }
  2340. }
  2341. if (typeof result !== 'undefined') {
  2342. break;
  2343. }
  2344. }
  2345. if (!result && result !== '') {
  2346. if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
  2347. result = applyNotFoundIndicators(translationId);
  2348. } else {
  2349. // Return translation of default interpolator if not found anything.
  2350. result = defaultInterpolator.interpolate(translationId, interpolateParams, 'filter', sanitizeStrategy);
  2351. // looks like the requested translation id doesn't exists.
  2352. // Now, if there is a registered handler for missing translations and no
  2353. // asyncLoader is pending, we execute the handler
  2354. var missingTranslationHandlerTranslation;
  2355. if ($missingTranslationHandlerFactory && !pendingLoader) {
  2356. missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, sanitizeStrategy);
  2357. }
  2358. if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
  2359. result = missingTranslationHandlerTranslation;
  2360. }
  2361. }
  2362. }
  2363. return result;
  2364. };
  2365. /**
  2366. * @ngdoc function
  2367. * @name pascalprecht.translate.$translate#versionInfo
  2368. * @methodOf pascalprecht.translate.$translate
  2369. *
  2370. * @description
  2371. * Returns the current version information for the angular-translate library
  2372. *
  2373. * @return {string} angular-translate version
  2374. */
  2375. $translate.versionInfo = function () {
  2376. return version;
  2377. };
  2378. /**
  2379. * @ngdoc function
  2380. * @name pascalprecht.translate.$translate#loaderCache
  2381. * @methodOf pascalprecht.translate.$translate
  2382. *
  2383. * @description
  2384. * Returns the defined loaderCache.
  2385. *
  2386. * @return {boolean|string|object} current value of loaderCache
  2387. */
  2388. $translate.loaderCache = function () {
  2389. return loaderCache;
  2390. };
  2391. // internal purpose only
  2392. $translate.directivePriority = function () {
  2393. return directivePriority;
  2394. };
  2395. // internal purpose only
  2396. $translate.statefulFilter = function () {
  2397. return statefulFilter;
  2398. };
  2399. /**
  2400. * @ngdoc function
  2401. * @name pascalprecht.translate.$translate#isReady
  2402. * @methodOf pascalprecht.translate.$translate
  2403. *
  2404. * @description
  2405. * Returns whether the service is "ready" to translate (i.e. loading 1st language).
  2406. *
  2407. * See also {@link pascalprecht.translate.$translate#methods_onReady onReady()}.
  2408. *
  2409. * @return {boolean} current value of ready
  2410. */
  2411. $translate.isReady = function () {
  2412. return $isReady;
  2413. };
  2414. var $onReadyDeferred = $q.defer();
  2415. $onReadyDeferred.promise.then(function () {
  2416. $isReady = true;
  2417. });
  2418. /**
  2419. * @ngdoc function
  2420. * @name pascalprecht.translate.$translate#onReady
  2421. * @methodOf pascalprecht.translate.$translate
  2422. *
  2423. * @description
  2424. * Calls the function provided or resolved the returned promise after the service is "ready" to translate (i.e. loading 1st language).
  2425. *
  2426. * See also {@link pascalprecht.translate.$translate#methods_isReady isReady()}.
  2427. *
  2428. * @param {Function=} fn Function to invoke when service is ready
  2429. * @return {object} Promise resolved when service is ready
  2430. */
  2431. $translate.onReady = function (fn) {
  2432. var deferred = $q.defer();
  2433. if (angular.isFunction(fn)) {
  2434. deferred.promise.then(fn);
  2435. }
  2436. if ($isReady) {
  2437. deferred.resolve();
  2438. } else {
  2439. $onReadyDeferred.promise.then(deferred.resolve);
  2440. }
  2441. return deferred.promise;
  2442. };
  2443. /**
  2444. * @ngdoc function
  2445. * @name pascalprecht.translate.$translate#getAvailableLanguageKeys
  2446. * @methodOf pascalprecht.translate.$translate
  2447. *
  2448. * @description
  2449. * This function simply returns the registered language keys being defined before in the config phase
  2450. * With this, an application can use the array to provide a language selection dropdown or similar
  2451. * without any additional effort
  2452. *
  2453. * @returns {object} returns the list of possibly registered language keys and mapping or null if not defined
  2454. */
  2455. $translate.getAvailableLanguageKeys = function () {
  2456. if ($availableLanguageKeys.length > 0) {
  2457. return $availableLanguageKeys;
  2458. }
  2459. return null;
  2460. };
  2461. /**
  2462. * @ngdoc function
  2463. * @name pascalprecht.translate.$translate#getTranslationTable
  2464. * @methodOf pascalprecht.translate.$translate
  2465. *
  2466. * @description
  2467. * Returns translation table by the given language key.
  2468. *
  2469. * Unless a language is provided it returns a translation table of the current one.
  2470. * Note: If translation dictionary is currently downloading or in progress
  2471. * it will return null.
  2472. *
  2473. * @param {string} langKey A token which represents a translation id
  2474. *
  2475. * @return {object} a copy of angular-translate $translationTable
  2476. */
  2477. $translate.getTranslationTable = function (langKey) {
  2478. langKey = langKey || $translate.use();
  2479. if (langKey && $translationTable[langKey]) {
  2480. return angular.copy($translationTable[langKey]);
  2481. }
  2482. return null;
  2483. };
  2484. // Whenever $translateReady is being fired, this will ensure the state of $isReady
  2485. var globalOnReadyListener = $rootScope.$on('$translateReady', function () {
  2486. $onReadyDeferred.resolve();
  2487. globalOnReadyListener(); // one time only
  2488. globalOnReadyListener = null;
  2489. });
  2490. var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () {
  2491. $onReadyDeferred.resolve();
  2492. globalOnChangeListener(); // one time only
  2493. globalOnChangeListener = null;
  2494. });
  2495. if ($loaderFactory) {
  2496. // If at least one async loader is defined and there are no
  2497. // (default) translations available we should try to load them.
  2498. if (angular.equals($translationTable, {})) {
  2499. if ($translate.use()) {
  2500. $translate.use($translate.use());
  2501. }
  2502. }
  2503. // Also, if there are any fallback language registered, we start
  2504. // loading them asynchronously as soon as we can.
  2505. if ($fallbackLanguage && $fallbackLanguage.length) {
  2506. var processAsyncResult = function (translation) {
  2507. translations(translation.key, translation.table);
  2508. $rootScope.$emit('$translateChangeEnd', {language : translation.key});
  2509. return translation;
  2510. };
  2511. for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
  2512. var fallbackLanguageId = $fallbackLanguage[i];
  2513. if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) {
  2514. langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult);
  2515. }
  2516. }
  2517. }
  2518. } else {
  2519. $rootScope.$emit('$translateReady', {language : $translate.use()});
  2520. }
  2521. return $translate;
  2522. }];
  2523. }
  2524. $translate.displayName = 'displayName';
  2525. /**
  2526. * @ngdoc object
  2527. * @name pascalprecht.translate.$translateDefaultInterpolation
  2528. * @requires $interpolate
  2529. *
  2530. * @description
  2531. * Uses angular's `$interpolate` services to interpolate strings against some values.
  2532. *
  2533. * Be aware to configure a proper sanitization strategy.
  2534. *
  2535. * See also:
  2536. * * {@link pascalprecht.translate.$translateSanitization}
  2537. *
  2538. * @return {object} $translateDefaultInterpolation Interpolator service
  2539. */
  2540. angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation);
  2541. function $translateDefaultInterpolation ($interpolate, $translateSanitization) {
  2542. 'use strict';
  2543. var $translateInterpolator = {},
  2544. $locale,
  2545. $identifier = 'default';
  2546. /**
  2547. * @ngdoc function
  2548. * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale
  2549. * @methodOf pascalprecht.translate.$translateDefaultInterpolation
  2550. *
  2551. * @description
  2552. * Sets current locale (this is currently not use in this interpolation).
  2553. *
  2554. * @param {string} locale Language key or locale.
  2555. */
  2556. $translateInterpolator.setLocale = function (locale) {
  2557. $locale = locale;
  2558. };
  2559. /**
  2560. * @ngdoc function
  2561. * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier
  2562. * @methodOf pascalprecht.translate.$translateDefaultInterpolation
  2563. *
  2564. * @description
  2565. * Returns an identifier for this interpolation service.
  2566. *
  2567. * @returns {string} $identifier
  2568. */
  2569. $translateInterpolator.getInterpolationIdentifier = function () {
  2570. return $identifier;
  2571. };
  2572. /**
  2573. * @deprecated will be removed in 3.0
  2574. * @see {@link pascalprecht.translate.$translateSanitization}
  2575. */
  2576. $translateInterpolator.useSanitizeValueStrategy = function (value) {
  2577. $translateSanitization.useStrategy(value);
  2578. return this;
  2579. };
  2580. /**
  2581. * @ngdoc function
  2582. * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate
  2583. * @methodOf pascalprecht.translate.$translateDefaultInterpolation
  2584. *
  2585. * @description
  2586. * Interpolates given value agains given interpolate params using angulars
  2587. * `$interpolate` service.
  2588. *
  2589. * Since AngularJS 1.5, `value` must not be a string but can be anything input.
  2590. *
  2591. * @param {string} value translation
  2592. * @param {object} interpolationParams interpolation params
  2593. * @param {string} context current context (filter, directive, service)
  2594. * @param {string} sanitizeStrategy sanitize strategy
  2595. * @param {string} translationId current translationId
  2596. *
  2597. * @returns {string} interpolated string
  2598. */
  2599. $translateInterpolator.interpolate = function (value, interpolationParams, context, sanitizeStrategy, translationId) { // jshint ignore:line
  2600. interpolationParams = interpolationParams || {};
  2601. interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params', sanitizeStrategy, context);
  2602. var interpolatedText;
  2603. if (angular.isNumber(value)) {
  2604. // numbers are safe
  2605. interpolatedText = '' + value;
  2606. } else if (angular.isString(value)) {
  2607. // strings must be interpolated (that's the job here)
  2608. interpolatedText = $interpolate(value)(interpolationParams);
  2609. interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text', sanitizeStrategy, context);
  2610. } else {
  2611. // neither a number or a string, cant interpolate => empty string
  2612. interpolatedText = '';
  2613. }
  2614. return interpolatedText;
  2615. };
  2616. return $translateInterpolator;
  2617. }
  2618. $translateDefaultInterpolation.displayName = '$translateDefaultInterpolation';
  2619. angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
  2620. angular.module('pascalprecht.translate')
  2621. /**
  2622. * @ngdoc directive
  2623. * @name pascalprecht.translate.directive:translate
  2624. * @requires $interpolate,
  2625. * @requires $compile,
  2626. * @requires $parse,
  2627. * @requires $rootScope
  2628. * @restrict AE
  2629. *
  2630. * @description
  2631. * Translates given translation id either through attribute or DOM content.
  2632. * Internally it uses $translate service to translate the translation id. It possible to
  2633. * pass an optional `translate-values` object literal as string into translation id.
  2634. *
  2635. * @param {string=} translate Translation id which could be either string or interpolated string.
  2636. * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
  2637. * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
  2638. * @param {string=} translate-default will be used unless translation was successful
  2639. * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling}
  2640. * @param {boolean=} translate-keep-content (default true if present) defines that in case a KEY could not be translated, that the existing content is left in the innerHTML}
  2641. *
  2642. * @example
  2643. <example module="ngView">
  2644. <file name="index.html">
  2645. <div ng-controller="TranslateCtrl">
  2646. <pre translate="TRANSLATION_ID"></pre>
  2647. <pre translate>TRANSLATION_ID</pre>
  2648. <pre translate translate-attr-title="TRANSLATION_ID"></pre>
  2649. <pre translate="{{translationId}}"></pre>
  2650. <pre translate>{{translationId}}</pre>
  2651. <pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
  2652. <pre translate translate-values="{value: 5}">WITH_VALUES</pre>
  2653. <pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
  2654. <pre translate translate-values="{{values}}">WITH_VALUES</pre>
  2655. <pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>
  2656. <pre translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hi"></pre>
  2657. </div>
  2658. </file>
  2659. <file name="script.js">
  2660. angular.module('ngView', ['pascalprecht.translate'])
  2661. .config(function ($translateProvider) {
  2662. $translateProvider.translations('en',{
  2663. 'TRANSLATION_ID': 'Hello there!',
  2664. 'WITH_VALUES': 'The following value is dynamic: {{value}}',
  2665. 'WITH_CAMEL_CASE_KEY': 'The interpolation key is camel cased: {{camelCaseKey}}'
  2666. }).preferredLanguage('en');
  2667. });
  2668. angular.module('ngView').controller('TranslateCtrl', function ($scope) {
  2669. $scope.translationId = 'TRANSLATION_ID';
  2670. $scope.values = {
  2671. value: 78
  2672. };
  2673. });
  2674. </file>
  2675. <file name="scenario.js">
  2676. it('should translate', function () {
  2677. inject(function ($rootScope, $compile) {
  2678. $rootScope.translationId = 'TRANSLATION_ID';
  2679. element = $compile('<p translate="TRANSLATION_ID"></p>')($rootScope);
  2680. $rootScope.$digest();
  2681. expect(element.text()).toBe('Hello there!');
  2682. element = $compile('<p translate="{{translationId}}"></p>')($rootScope);
  2683. $rootScope.$digest();
  2684. expect(element.text()).toBe('Hello there!');
  2685. element = $compile('<p translate>TRANSLATION_ID</p>')($rootScope);
  2686. $rootScope.$digest();
  2687. expect(element.text()).toBe('Hello there!');
  2688. element = $compile('<p translate>{{translationId}}</p>')($rootScope);
  2689. $rootScope.$digest();
  2690. expect(element.text()).toBe('Hello there!');
  2691. element = $compile('<p translate translate-attr-title="TRANSLATION_ID"></p>')($rootScope);
  2692. $rootScope.$digest();
  2693. expect(element.attr('title')).toBe('Hello there!');
  2694. element = $compile('<p translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hello"></p>')($rootScope);
  2695. $rootScope.$digest();
  2696. expect(element.text()).toBe('The interpolation key is camel cased: Hello');
  2697. });
  2698. });
  2699. </file>
  2700. </example>
  2701. */
  2702. .directive('translate', translateDirective);
  2703. function translateDirective($translate, $interpolate, $compile, $parse, $rootScope) {
  2704. 'use strict';
  2705. /**
  2706. * @name trim
  2707. * @private
  2708. *
  2709. * @description
  2710. * trim polyfill
  2711. *
  2712. * @returns {string} The string stripped of whitespace from both ends
  2713. */
  2714. var trim = function() {
  2715. return this.toString().replace(/^\s+|\s+$/g, '');
  2716. };
  2717. return {
  2718. restrict: 'AE',
  2719. scope: true,
  2720. priority: $translate.directivePriority(),
  2721. compile: function (tElement, tAttr) {
  2722. var translateValuesExist = (tAttr.translateValues) ?
  2723. tAttr.translateValues : undefined;
  2724. var translateInterpolation = (tAttr.translateInterpolation) ?
  2725. tAttr.translateInterpolation : undefined;
  2726. var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
  2727. var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)',
  2728. watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)';
  2729. return function linkFn(scope, iElement, iAttr) {
  2730. scope.interpolateParams = {};
  2731. scope.preText = '';
  2732. scope.postText = '';
  2733. scope.translateNamespace = getTranslateNamespace(scope);
  2734. var translationIds = {};
  2735. var initInterpolationParams = function (interpolateParams, iAttr, tAttr) {
  2736. // initial setup
  2737. if (iAttr.translateValues) {
  2738. angular.extend(interpolateParams, $parse(iAttr.translateValues)(scope.$parent));
  2739. }
  2740. // initially fetch all attributes if existing and fill the params
  2741. if (translateValueExist) {
  2742. for (var attr in tAttr) {
  2743. if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
  2744. var attributeName = angular.lowercase(attr.substr(14, 1)) + attr.substr(15);
  2745. interpolateParams[attributeName] = tAttr[attr];
  2746. }
  2747. }
  2748. }
  2749. };
  2750. // Ensures any change of the attribute "translate" containing the id will
  2751. // be re-stored to the scope's "translationId".
  2752. // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
  2753. var observeElementTranslation = function (translationId) {
  2754. // Remove any old watcher
  2755. if (angular.isFunction(observeElementTranslation._unwatchOld)) {
  2756. observeElementTranslation._unwatchOld();
  2757. observeElementTranslation._unwatchOld = undefined;
  2758. }
  2759. if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
  2760. var iElementText = trim.apply(iElement.text());
  2761. // Resolve translation id by inner html if required
  2762. var interpolateMatches = iElementText.match(interpolateRegExp);
  2763. // Interpolate translation id if required
  2764. if (angular.isArray(interpolateMatches)) {
  2765. scope.preText = interpolateMatches[1];
  2766. scope.postText = interpolateMatches[3];
  2767. translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent);
  2768. var watcherMatches = iElementText.match(watcherRegExp);
  2769. if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) {
  2770. observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) {
  2771. translationIds.translate = newValue;
  2772. updateTranslations();
  2773. });
  2774. }
  2775. } else {
  2776. // do not assigne the translation id if it is empty.
  2777. translationIds.translate = !iElementText ? undefined : iElementText;
  2778. }
  2779. } else {
  2780. translationIds.translate = translationId;
  2781. }
  2782. updateTranslations();
  2783. };
  2784. var observeAttributeTranslation = function (translateAttr) {
  2785. iAttr.$observe(translateAttr, function (translationId) {
  2786. translationIds[translateAttr] = translationId;
  2787. updateTranslations();
  2788. });
  2789. };
  2790. // initial setup with values
  2791. initInterpolationParams(scope.interpolateParams, iAttr, tAttr);
  2792. var firstAttributeChangedEvent = true;
  2793. iAttr.$observe('translate', function (translationId) {
  2794. if (typeof translationId === 'undefined') {
  2795. // case of element "<translate>xyz</translate>"
  2796. observeElementTranslation('');
  2797. } else {
  2798. // case of regular attribute
  2799. if (translationId !== '' || !firstAttributeChangedEvent) {
  2800. translationIds.translate = translationId;
  2801. updateTranslations();
  2802. }
  2803. }
  2804. firstAttributeChangedEvent = false;
  2805. });
  2806. for (var translateAttr in iAttr) {
  2807. if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr' && translateAttr.length > 13) {
  2808. observeAttributeTranslation(translateAttr);
  2809. }
  2810. }
  2811. iAttr.$observe('translateDefault', function (value) {
  2812. scope.defaultText = value;
  2813. updateTranslations();
  2814. });
  2815. if (translateValuesExist) {
  2816. iAttr.$observe('translateValues', function (interpolateParams) {
  2817. if (interpolateParams) {
  2818. scope.$parent.$watch(function () {
  2819. angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
  2820. });
  2821. }
  2822. });
  2823. }
  2824. if (translateValueExist) {
  2825. var observeValueAttribute = function (attrName) {
  2826. iAttr.$observe(attrName, function (value) {
  2827. var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
  2828. scope.interpolateParams[attributeName] = value;
  2829. });
  2830. };
  2831. for (var attr in iAttr) {
  2832. if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
  2833. observeValueAttribute(attr);
  2834. }
  2835. }
  2836. }
  2837. // Master update function
  2838. var updateTranslations = function () {
  2839. for (var key in translationIds) {
  2840. if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) {
  2841. updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText, scope.translateNamespace);
  2842. }
  2843. }
  2844. };
  2845. // Put translation processing function outside loop
  2846. var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText, translateNamespace) {
  2847. if (translationId) {
  2848. // if translation id starts with '.' and translateNamespace given, prepend namespace
  2849. if (translateNamespace && translationId.charAt(0) === '.') {
  2850. translationId = translateNamespace + translationId;
  2851. }
  2852. $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText, scope.translateLanguage)
  2853. .then(function (translation) {
  2854. applyTranslation(translation, scope, true, translateAttr);
  2855. }, function (translationId) {
  2856. applyTranslation(translationId, scope, false, translateAttr);
  2857. });
  2858. } else {
  2859. // as an empty string cannot be translated, we can solve this using successful=false
  2860. applyTranslation(translationId, scope, false, translateAttr);
  2861. }
  2862. };
  2863. var applyTranslation = function (value, scope, successful, translateAttr) {
  2864. if (!successful) {
  2865. if (typeof scope.defaultText !== 'undefined') {
  2866. value = scope.defaultText;
  2867. }
  2868. }
  2869. if (translateAttr === 'translate') {
  2870. // default translate into innerHTML
  2871. if (successful || (!successful && !$translate.isKeepContent() && typeof iAttr.translateKeepContent === 'undefined')) {
  2872. iElement.empty().append(scope.preText + value + scope.postText);
  2873. }
  2874. var globallyEnabled = $translate.isPostCompilingEnabled();
  2875. var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
  2876. var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
  2877. if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
  2878. $compile(iElement.contents())(scope);
  2879. }
  2880. } else {
  2881. // translate attribute
  2882. var attributeName = iAttr.$attr[translateAttr];
  2883. if (attributeName.substr(0, 5) === 'data-') {
  2884. // ensure html5 data prefix is stripped
  2885. attributeName = attributeName.substr(5);
  2886. }
  2887. attributeName = attributeName.substr(15);
  2888. iElement.attr(attributeName, value);
  2889. }
  2890. };
  2891. if (translateValuesExist || translateValueExist || iAttr.translateDefault) {
  2892. scope.$watch('interpolateParams', updateTranslations, true);
  2893. }
  2894. // Replaced watcher on translateLanguage with event listener
  2895. scope.$on('translateLanguageChanged', updateTranslations);
  2896. // Ensures the text will be refreshed after the current language was changed
  2897. // w/ $translate.use(...)
  2898. var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
  2899. // ensure translation will be looked up at least one
  2900. if (iElement.text().length) {
  2901. if (iAttr.translate) {
  2902. observeElementTranslation(iAttr.translate);
  2903. } else {
  2904. observeElementTranslation('');
  2905. }
  2906. } else if (iAttr.translate) {
  2907. // ensure attribute will be not skipped
  2908. observeElementTranslation(iAttr.translate);
  2909. }
  2910. updateTranslations();
  2911. scope.$on('$destroy', unbind);
  2912. };
  2913. }
  2914. };
  2915. }
  2916. /**
  2917. * Returns the scope's namespace.
  2918. * @private
  2919. * @param scope
  2920. * @returns {string}
  2921. */
  2922. function getTranslateNamespace(scope) {
  2923. 'use strict';
  2924. if (scope.translateNamespace) {
  2925. return scope.translateNamespace;
  2926. }
  2927. if (scope.$parent) {
  2928. return getTranslateNamespace(scope.$parent);
  2929. }
  2930. }
  2931. translateDirective.displayName = 'translateDirective';
  2932. angular.module('pascalprecht.translate')
  2933. /**
  2934. * @ngdoc directive
  2935. * @name pascalprecht.translate.directive:translate-attr
  2936. * @restrict A
  2937. *
  2938. * @description
  2939. * Translates attributes like translate-attr-ATTR, but with an object like ng-class.
  2940. * Internally it uses `translate` service to translate translation id. It possible to
  2941. * pass an optional `translate-values` object literal as string into translation id.
  2942. *
  2943. * @param {string=} translate-attr Object literal mapping attributes to translation ids.
  2944. * @param {string=} translate-values Values to pass into the translation ids. Can be passed as object literal string.
  2945. *
  2946. * @example
  2947. <example module="ngView">
  2948. <file name="index.html">
  2949. <div ng-controller="TranslateCtrl">
  2950. <input translate-attr="{ placeholder: translationId, title: 'WITH_VALUES' }" translate-values="{value: 5}" />
  2951. </div>
  2952. </file>
  2953. <file name="script.js">
  2954. angular.module('ngView', ['pascalprecht.translate'])
  2955. .config(function ($translateProvider) {
  2956. $translateProvider.translations('en',{
  2957. 'TRANSLATION_ID': 'Hello there!',
  2958. 'WITH_VALUES': 'The following value is dynamic: {{value}}',
  2959. }).preferredLanguage('en');
  2960. });
  2961. angular.module('ngView').controller('TranslateCtrl', function ($scope) {
  2962. $scope.translationId = 'TRANSLATION_ID';
  2963. $scope.values = {
  2964. value: 78
  2965. };
  2966. });
  2967. </file>
  2968. <file name="scenario.js">
  2969. it('should translate', function () {
  2970. inject(function ($rootScope, $compile) {
  2971. $rootScope.translationId = 'TRANSLATION_ID';
  2972. element = $compile('<input translate-attr="{ placeholder: translationId, title: 'WITH_VALUES' }" translate-values="{ value: 5 }" />')($rootScope);
  2973. $rootScope.$digest();
  2974. expect(element.attr('placeholder)).toBe('Hello there!');
  2975. expect(element.attr('title)).toBe('The following value is dynamic: 5');
  2976. });
  2977. });
  2978. </file>
  2979. </example>
  2980. */
  2981. .directive('translateAttr', translateAttrDirective);
  2982. function translateAttrDirective($translate, $rootScope) {
  2983. 'use strict';
  2984. return {
  2985. restrict: 'A',
  2986. priority: $translate.directivePriority(),
  2987. link: function linkFn(scope, element, attr) {
  2988. var translateAttr,
  2989. translateValues,
  2990. previousAttributes = {};
  2991. // Main update translations function
  2992. var updateTranslations = function () {
  2993. angular.forEach(translateAttr, function (translationId, attributeName) {
  2994. if (!translationId) {
  2995. return;
  2996. }
  2997. previousAttributes[attributeName] = true;
  2998. // if translation id starts with '.' and translateNamespace given, prepend namespace
  2999. if (scope.translateNamespace && translationId.charAt(0) === '.') {
  3000. translationId = scope.translateNamespace + translationId;
  3001. }
  3002. $translate(translationId, translateValues, attr.translateInterpolation, undefined, scope.translateLanguage)
  3003. .then(function (translation) {
  3004. element.attr(attributeName, translation);
  3005. }, function (translationId) {
  3006. element.attr(attributeName, translationId);
  3007. });
  3008. });
  3009. // Removing unused attributes that were previously used
  3010. angular.forEach(previousAttributes, function (flag, attributeName) {
  3011. if (!translateAttr[attributeName]) {
  3012. element.removeAttr(attributeName);
  3013. delete previousAttributes[attributeName];
  3014. }
  3015. });
  3016. };
  3017. // Watch for attribute changes
  3018. watchAttribute(
  3019. scope,
  3020. attr.translateAttr,
  3021. function (newValue) { translateAttr = newValue; },
  3022. updateTranslations
  3023. );
  3024. // Watch for value changes
  3025. watchAttribute(
  3026. scope,
  3027. attr.translateValues,
  3028. function (newValue) { translateValues = newValue; },
  3029. updateTranslations
  3030. );
  3031. if (attr.translateValues) {
  3032. scope.$watch(attr.translateValues, updateTranslations, true);
  3033. }
  3034. // Replaced watcher on translateLanguage with event listener
  3035. scope.$on('translateLanguageChanged', updateTranslations);
  3036. // Ensures the text will be refreshed after the current language was changed
  3037. // w/ $translate.use(...)
  3038. var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
  3039. updateTranslations();
  3040. scope.$on('$destroy', unbind);
  3041. }
  3042. };
  3043. }
  3044. function watchAttribute(scope, attribute, valueCallback, changeCallback) {
  3045. 'use strict';
  3046. if (!attribute) {
  3047. return;
  3048. }
  3049. if (attribute.substr(0, 2) === '::') {
  3050. attribute = attribute.substr(2);
  3051. } else {
  3052. scope.$watch(attribute, function(newValue) {
  3053. valueCallback(newValue);
  3054. changeCallback();
  3055. }, true);
  3056. }
  3057. valueCallback(scope.$eval(attribute));
  3058. }
  3059. translateAttrDirective.displayName = 'translateAttrDirective';
  3060. angular.module('pascalprecht.translate')
  3061. /**
  3062. * @ngdoc directive
  3063. * @name pascalprecht.translate.directive:translateCloak
  3064. * @requires $translate
  3065. * @restrict A
  3066. *
  3067. * $description
  3068. * Adds a `translate-cloak` class name to the given element where this directive
  3069. * is applied initially and removes it, once a loader has finished loading.
  3070. *
  3071. * This directive can be used to prevent initial flickering when loading translation
  3072. * data asynchronously.
  3073. *
  3074. * The class name is defined in
  3075. * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}.
  3076. *
  3077. * @param {string=} translate-cloak If a translationId is provided, it will be used for showing
  3078. * or hiding the cloak. Basically it relies on the translation
  3079. * resolve.
  3080. */
  3081. .directive('translateCloak', translateCloakDirective);
  3082. function translateCloakDirective($translate, $rootScope) {
  3083. 'use strict';
  3084. return {
  3085. compile : function (tElement) {
  3086. var applyCloak = function (element) {
  3087. element.addClass($translate.cloakClassName());
  3088. },
  3089. removeCloak = function (element) {
  3090. element.removeClass($translate.cloakClassName());
  3091. };
  3092. applyCloak(tElement);
  3093. return function linkFn(scope, iElement, iAttr) {
  3094. //Create bound functions that incorporate the active DOM element.
  3095. var iRemoveCloak = removeCloak.bind(this, iElement), iApplyCloak = applyCloak.bind(this, iElement);
  3096. if (iAttr.translateCloak && iAttr.translateCloak.length) {
  3097. // Register a watcher for the defined translation allowing a fine tuned cloak
  3098. iAttr.$observe('translateCloak', function (translationId) {
  3099. $translate(translationId).then(iRemoveCloak, iApplyCloak);
  3100. });
  3101. $rootScope.$on('$translateChangeSuccess', function () {
  3102. $translate(iAttr.translateCloak).then(iRemoveCloak, iApplyCloak);
  3103. });
  3104. } else {
  3105. $translate.onReady(iRemoveCloak);
  3106. }
  3107. };
  3108. }
  3109. };
  3110. }
  3111. translateCloakDirective.displayName = 'translateCloakDirective';
  3112. angular.module('pascalprecht.translate')
  3113. /**
  3114. * @ngdoc directive
  3115. * @name pascalprecht.translate.directive:translateNamespace
  3116. * @restrict A
  3117. *
  3118. * @description
  3119. * Translates given translation id either through attribute or DOM content.
  3120. * Internally it uses `translate` filter to translate translation id. It possible to
  3121. * pass an optional `translate-values` object literal as string into translation id.
  3122. *
  3123. * @param {string=} translate namespace name which could be either string or interpolated string.
  3124. *
  3125. * @example
  3126. <example module="ngView">
  3127. <file name="index.html">
  3128. <div translate-namespace="CONTENT">
  3129. <div>
  3130. <h1 translate>.HEADERS.TITLE</h1>
  3131. <h1 translate>.HEADERS.WELCOME</h1>
  3132. </div>
  3133. <div translate-namespace=".HEADERS">
  3134. <h1 translate>.TITLE</h1>
  3135. <h1 translate>.WELCOME</h1>
  3136. </div>
  3137. </div>
  3138. </file>
  3139. <file name="script.js">
  3140. angular.module('ngView', ['pascalprecht.translate'])
  3141. .config(function ($translateProvider) {
  3142. $translateProvider.translations('en',{
  3143. 'TRANSLATION_ID': 'Hello there!',
  3144. 'CONTENT': {
  3145. 'HEADERS': {
  3146. TITLE: 'Title'
  3147. }
  3148. },
  3149. 'CONTENT.HEADERS.WELCOME': 'Welcome'
  3150. }).preferredLanguage('en');
  3151. });
  3152. </file>
  3153. </example>
  3154. */
  3155. .directive('translateNamespace', translateNamespaceDirective);
  3156. function translateNamespaceDirective() {
  3157. 'use strict';
  3158. return {
  3159. restrict: 'A',
  3160. scope: true,
  3161. compile: function () {
  3162. return {
  3163. pre: function (scope, iElement, iAttrs) {
  3164. scope.translateNamespace = getTranslateNamespace(scope);
  3165. if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') {
  3166. scope.translateNamespace += iAttrs.translateNamespace;
  3167. } else {
  3168. scope.translateNamespace = iAttrs.translateNamespace;
  3169. }
  3170. }
  3171. };
  3172. }
  3173. };
  3174. }
  3175. /**
  3176. * Returns the scope's namespace.
  3177. * @private
  3178. * @param scope
  3179. * @returns {string}
  3180. */
  3181. function getTranslateNamespace(scope) {
  3182. 'use strict';
  3183. if (scope.translateNamespace) {
  3184. return scope.translateNamespace;
  3185. }
  3186. if (scope.$parent) {
  3187. return getTranslateNamespace(scope.$parent);
  3188. }
  3189. }
  3190. translateNamespaceDirective.displayName = 'translateNamespaceDirective';
  3191. angular.module('pascalprecht.translate')
  3192. /**
  3193. * @ngdoc directive
  3194. * @name pascalprecht.translate.directive:translateLanguage
  3195. * @restrict A
  3196. *
  3197. * @description
  3198. * Forces the language to the directives in the underlying scope.
  3199. *
  3200. * @param {string=} translate language that will be negotiated.
  3201. *
  3202. * @example
  3203. <example module="ngView">
  3204. <file name="index.html">
  3205. <div>
  3206. <div>
  3207. <h1 translate>HELLO</h1>
  3208. </div>
  3209. <div translate-language="de">
  3210. <h1 translate>HELLO</h1>
  3211. </div>
  3212. </div>
  3213. </file>
  3214. <file name="script.js">
  3215. angular.module('ngView', ['pascalprecht.translate'])
  3216. .config(function ($translateProvider) {
  3217. $translateProvider
  3218. .translations('en',{
  3219. 'HELLO': 'Hello world!'
  3220. })
  3221. .translations('de',{
  3222. 'HELLO': 'Hallo Welt!'
  3223. })
  3224. .preferredLanguage('en');
  3225. });
  3226. </file>
  3227. </example>
  3228. */
  3229. .directive('translateLanguage', translateLanguageDirective);
  3230. function translateLanguageDirective() {
  3231. 'use strict';
  3232. return {
  3233. restrict: 'A',
  3234. scope: true,
  3235. compile: function () {
  3236. return function linkFn(scope, iElement, iAttrs) {
  3237. iAttrs.$observe('translateLanguage', function (newTranslateLanguage) {
  3238. scope.translateLanguage = newTranslateLanguage;
  3239. });
  3240. scope.$watch('translateLanguage', function(){
  3241. scope.$broadcast('translateLanguageChanged');
  3242. });
  3243. };
  3244. }
  3245. };
  3246. }
  3247. translateLanguageDirective.displayName = 'translateLanguageDirective';
  3248. angular.module('pascalprecht.translate')
  3249. /**
  3250. * @ngdoc filter
  3251. * @name pascalprecht.translate.filter:translate
  3252. * @requires $parse
  3253. * @requires pascalprecht.translate.$translate
  3254. * @function
  3255. *
  3256. * @description
  3257. * Uses `$translate` service to translate contents. Accepts interpolate parameters
  3258. * to pass dynamized values though translation.
  3259. *
  3260. * @param {string} translationId A translation id to be translated.
  3261. * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation.
  3262. *
  3263. * @returns {string} Translated text.
  3264. *
  3265. * @example
  3266. <example module="ngView">
  3267. <file name="index.html">
  3268. <div ng-controller="TranslateCtrl">
  3269. <pre>{{ 'TRANSLATION_ID' | translate }}</pre>
  3270. <pre>{{ translationId | translate }}</pre>
  3271. <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre>
  3272. <pre>{{ 'WITH_VALUES' | translate:values }}</pre>
  3273. </div>
  3274. </file>
  3275. <file name="script.js">
  3276. angular.module('ngView', ['pascalprecht.translate'])
  3277. .config(function ($translateProvider) {
  3278. $translateProvider.translations('en', {
  3279. 'TRANSLATION_ID': 'Hello there!',
  3280. 'WITH_VALUES': 'The following value is dynamic: {{value}}'
  3281. });
  3282. $translateProvider.preferredLanguage('en');
  3283. });
  3284. angular.module('ngView').controller('TranslateCtrl', function ($scope) {
  3285. $scope.translationId = 'TRANSLATION_ID';
  3286. $scope.values = {
  3287. value: 78
  3288. };
  3289. });
  3290. </file>
  3291. </example>
  3292. */
  3293. .filter('translate', translateFilterFactory);
  3294. function translateFilterFactory($parse, $translate) {
  3295. 'use strict';
  3296. var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) {
  3297. if (!angular.isObject(interpolateParams)) {
  3298. var ctx = this || {
  3299. '__SCOPE_IS_NOT_AVAILABLE': 'More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f'
  3300. };
  3301. interpolateParams = $parse(interpolateParams)(ctx);
  3302. }
  3303. return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage);
  3304. };
  3305. if ($translate.statefulFilter()) {
  3306. translateFilter.$stateful = true;
  3307. }
  3308. return translateFilter;
  3309. }
  3310. translateFilterFactory.displayName = 'translateFilterFactory';
  3311. angular.module('pascalprecht.translate')
  3312. /**
  3313. * @ngdoc object
  3314. * @name pascalprecht.translate.$translationCache
  3315. * @requires $cacheFactory
  3316. *
  3317. * @description
  3318. * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You
  3319. * can load translation tables directly into the cache by consuming the
  3320. * `$translationCache` service directly.
  3321. *
  3322. * @return {object} $cacheFactory object.
  3323. */
  3324. .factory('$translationCache', $translationCache);
  3325. function $translationCache($cacheFactory) {
  3326. 'use strict';
  3327. return $cacheFactory('translations');
  3328. }
  3329. $translationCache.displayName = '$translationCache';
  3330. return 'pascalprecht.translate';
  3331. }));