angular-translate.js 81 KB

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