angular-translate.js 125 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704
  1. /*!
  2. * angular-translate - v2.14.0 - 2017-02-11
  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 exports === 'object') {
  13. // Node. Does not work with strict CommonJS, but
  14. // only CommonJS-like environments that support module.exports,
  15. // like Node.
  16. module.exports = factory();
  17. } else {
  18. factory();
  19. }
  20. }(this, function () {
  21. /**
  22. * @ngdoc overview
  23. * @name 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'];
  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.14.0';
  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. * Returns whether 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 value translation
  2592. * @param interpolationParams interpolation params
  2593. * @param context current context (filter, directive, service)
  2594. * @param sanitizeStrategy sanitize strategy
  2595. * @param translationId current translationId
  2596. *
  2597. * @returns {string} interpolated string
  2598. */
  2599. $translateInterpolator.interpolate = function (value, interpolationParams, context, sanitizeStrategy/*, translationId*/) {
  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) {
  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. }
  3102. else {
  3103. $translate.onReady(iRemoveCloak);
  3104. }
  3105. };
  3106. }
  3107. };
  3108. }
  3109. translateCloakDirective.displayName = 'translateCloakDirective';
  3110. angular.module('pascalprecht.translate')
  3111. /**
  3112. * @ngdoc directive
  3113. * @name pascalprecht.translate.directive:translateNamespace
  3114. * @restrict A
  3115. *
  3116. * @description
  3117. * Translates given translation id either through attribute or DOM content.
  3118. * Internally it uses `translate` filter to translate translation id. It possible to
  3119. * pass an optional `translate-values` object literal as string into translation id.
  3120. *
  3121. * @param {string=} translate namespace name which could be either string or interpolated string.
  3122. *
  3123. * @example
  3124. <example module="ngView">
  3125. <file name="index.html">
  3126. <div translate-namespace="CONTENT">
  3127. <div>
  3128. <h1 translate>.HEADERS.TITLE</h1>
  3129. <h1 translate>.HEADERS.WELCOME</h1>
  3130. </div>
  3131. <div translate-namespace=".HEADERS">
  3132. <h1 translate>.TITLE</h1>
  3133. <h1 translate>.WELCOME</h1>
  3134. </div>
  3135. </div>
  3136. </file>
  3137. <file name="script.js">
  3138. angular.module('ngView', ['pascalprecht.translate'])
  3139. .config(function ($translateProvider) {
  3140. $translateProvider.translations('en',{
  3141. 'TRANSLATION_ID': 'Hello there!',
  3142. 'CONTENT': {
  3143. 'HEADERS': {
  3144. TITLE: 'Title'
  3145. }
  3146. },
  3147. 'CONTENT.HEADERS.WELCOME': 'Welcome'
  3148. }).preferredLanguage('en');
  3149. });
  3150. </file>
  3151. </example>
  3152. */
  3153. .directive('translateNamespace', translateNamespaceDirective);
  3154. function translateNamespaceDirective() {
  3155. 'use strict';
  3156. return {
  3157. restrict: 'A',
  3158. scope: true,
  3159. compile: function () {
  3160. return {
  3161. pre: function (scope, iElement, iAttrs) {
  3162. scope.translateNamespace = getTranslateNamespace(scope);
  3163. if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') {
  3164. scope.translateNamespace += iAttrs.translateNamespace;
  3165. } else {
  3166. scope.translateNamespace = iAttrs.translateNamespace;
  3167. }
  3168. }
  3169. };
  3170. }
  3171. };
  3172. }
  3173. /**
  3174. * Returns the scope's namespace.
  3175. * @private
  3176. * @param scope
  3177. * @returns {string}
  3178. */
  3179. function getTranslateNamespace(scope) {
  3180. 'use strict';
  3181. if (scope.translateNamespace) {
  3182. return scope.translateNamespace;
  3183. }
  3184. if (scope.$parent) {
  3185. return getTranslateNamespace(scope.$parent);
  3186. }
  3187. }
  3188. translateNamespaceDirective.displayName = 'translateNamespaceDirective';
  3189. angular.module('pascalprecht.translate')
  3190. /**
  3191. * @ngdoc directive
  3192. * @name pascalprecht.translate.directive:translateLanguage
  3193. * @restrict A
  3194. *
  3195. * @description
  3196. * Forces the language to the directives in the underlying scope.
  3197. *
  3198. * @param {string=} translate language that will be negotiated.
  3199. *
  3200. * @example
  3201. <example module="ngView">
  3202. <file name="index.html">
  3203. <div>
  3204. <div>
  3205. <h1 translate>HELLO</h1>
  3206. </div>
  3207. <div translate-language="de">
  3208. <h1 translate>HELLO</h1>
  3209. </div>
  3210. </div>
  3211. </file>
  3212. <file name="script.js">
  3213. angular.module('ngView', ['pascalprecht.translate'])
  3214. .config(function ($translateProvider) {
  3215. $translateProvider
  3216. .translations('en',{
  3217. 'HELLO': 'Hello world!'
  3218. })
  3219. .translations('de',{
  3220. 'HELLO': 'Hallo Welt!'
  3221. })
  3222. .preferredLanguage('en');
  3223. });
  3224. </file>
  3225. </example>
  3226. */
  3227. .directive('translateLanguage', translateLanguageDirective);
  3228. function translateLanguageDirective() {
  3229. 'use strict';
  3230. return {
  3231. restrict: 'A',
  3232. scope: true,
  3233. compile: function () {
  3234. return function linkFn(scope, iElement, iAttrs) {
  3235. iAttrs.$observe('translateLanguage', function (newTranslateLanguage) {
  3236. scope.translateLanguage = newTranslateLanguage;
  3237. });
  3238. scope.$watch('translateLanguage', function(){
  3239. scope.$broadcast('translateLanguageChanged');
  3240. });
  3241. };
  3242. }
  3243. };
  3244. }
  3245. translateLanguageDirective.displayName = 'translateLanguageDirective';
  3246. angular.module('pascalprecht.translate')
  3247. /**
  3248. * @ngdoc filter
  3249. * @name pascalprecht.translate.filter:translate
  3250. * @requires $parse
  3251. * @requires pascalprecht.translate.$translate
  3252. * @function
  3253. *
  3254. * @description
  3255. * Uses `$translate` service to translate contents. Accepts interpolate parameters
  3256. * to pass dynamized values though translation.
  3257. *
  3258. * @param {string} translationId A translation id to be translated.
  3259. * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation.
  3260. *
  3261. * @returns {string} Translated text.
  3262. *
  3263. * @example
  3264. <example module="ngView">
  3265. <file name="index.html">
  3266. <div ng-controller="TranslateCtrl">
  3267. <pre>{{ 'TRANSLATION_ID' | translate }}</pre>
  3268. <pre>{{ translationId | translate }}</pre>
  3269. <pre>{{ 'WITH_VALUES' | translate:'{value: 5}' }}</pre>
  3270. <pre>{{ 'WITH_VALUES' | translate:values }}</pre>
  3271. </div>
  3272. </file>
  3273. <file name="script.js">
  3274. angular.module('ngView', ['pascalprecht.translate'])
  3275. .config(function ($translateProvider) {
  3276. $translateProvider.translations('en', {
  3277. 'TRANSLATION_ID': 'Hello there!',
  3278. 'WITH_VALUES': 'The following value is dynamic: {{value}}'
  3279. });
  3280. $translateProvider.preferredLanguage('en');
  3281. });
  3282. angular.module('ngView').controller('TranslateCtrl', function ($scope) {
  3283. $scope.translationId = 'TRANSLATION_ID';
  3284. $scope.values = {
  3285. value: 78
  3286. };
  3287. });
  3288. </file>
  3289. </example>
  3290. */
  3291. .filter('translate', translateFilterFactory);
  3292. function translateFilterFactory($parse, $translate) {
  3293. 'use strict';
  3294. var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) {
  3295. if (!angular.isObject(interpolateParams)) {
  3296. interpolateParams = $parse(interpolateParams)(this);
  3297. }
  3298. return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage);
  3299. };
  3300. if ($translate.statefulFilter()) {
  3301. translateFilter.$stateful = true;
  3302. }
  3303. return translateFilter;
  3304. }
  3305. translateFilterFactory.displayName = 'translateFilterFactory';
  3306. angular.module('pascalprecht.translate')
  3307. /**
  3308. * @ngdoc object
  3309. * @name pascalprecht.translate.$translationCache
  3310. * @requires $cacheFactory
  3311. *
  3312. * @description
  3313. * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You
  3314. * can load translation tables directly into the cache by consuming the
  3315. * `$translationCache` service directly.
  3316. *
  3317. * @return {object} $cacheFactory object.
  3318. */
  3319. .factory('$translationCache', $translationCache);
  3320. function $translationCache($cacheFactory) {
  3321. 'use strict';
  3322. return $cacheFactory('translations');
  3323. }
  3324. $translationCache.displayName = '$translationCache';
  3325. return 'pascalprecht.translate';
  3326. }));