angular-translate.js 97 KB

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