lit 1.0 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (297) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -5
  3. data/app/assets/javascripts/lit/backend/bootstrap.min.js +7 -0
  4. data/app/assets/javascripts/lit/backend/localizations.js.coffee +13 -6
  5. data/app/assets/stylesheets/lit/application.css +16 -13
  6. data/app/controllers/lit/api/v1/localizations_controller.rb +12 -15
  7. data/app/controllers/lit/cloud_translations_controller.rb +1 -1
  8. data/app/controllers/lit/concerns/request_info_store.rb +16 -10
  9. data/app/controllers/lit/concerns/request_keys_store.rb +17 -10
  10. data/app/controllers/lit/localization_keys_controller.rb +40 -34
  11. data/app/controllers/lit/localizations_controller.rb +1 -1
  12. data/app/controllers/lit/sources_controller.rb +1 -1
  13. data/app/helpers/lit/frontend_helper.rb +1 -1
  14. data/app/helpers/lit/localizations_helper.rb +9 -0
  15. data/app/models/lit/base.rb +33 -17
  16. data/app/models/lit/incomming_localization.rb +13 -27
  17. data/app/models/lit/localization.rb +6 -14
  18. data/app/services/remote_interactor_service.rb +3 -1
  19. data/app/services/synchronize_source_service.rb +4 -8
  20. data/app/views/layouts/lit/application.html.erb +0 -1
  21. data/app/views/lit/dashboard/index.html.erb +1 -1
  22. data/app/views/lit/incomming_localizations/index.html.erb +1 -1
  23. data/app/views/lit/localization_keys/_localizations_list.html.erb +19 -11
  24. data/app/views/lit/localization_keys/batch_touch.js.erb +1 -0
  25. data/app/views/lit/localization_keys/index.html.erb +2 -0
  26. data/app/views/lit/localization_keys/not_translated.html.erb +2 -0
  27. data/app/views/lit/localization_keys/visited_again.html.erb +3 -0
  28. data/app/views/lit/localizations/update.js.erb +1 -1
  29. data/config/routes.rb +1 -0
  30. data/lib/generators/lit/install/templates/initializer.rb +6 -3
  31. data/lib/lit.rb +19 -6
  32. data/lib/lit/adapters/hash_storage.rb +35 -1
  33. data/lib/lit/adapters/redis_storage.rb +42 -7
  34. data/lib/lit/cache.rb +66 -10
  35. data/lib/lit/cloud_translation/providers/google.rb +2 -0
  36. data/lib/lit/cloud_translation/providers/yandex.rb +2 -0
  37. data/lib/lit/engine.rb +12 -5
  38. data/lib/lit/export.rb +2 -18
  39. data/lib/lit/i18n_backend.rb +20 -7
  40. data/lib/lit/import.rb +18 -15
  41. data/lib/lit/loader.rb +1 -0
  42. data/lib/lit/middleware.rb +29 -0
  43. data/lib/lit/rails.rb +1 -0
  44. data/lib/lit/railtie.rb +6 -7
  45. data/lib/lit/services/localization_keys_to_hash_service.rb +23 -0
  46. data/lib/lit/version.rb +1 -1
  47. metadata +30 -263
  48. data/app/assets/images/lit/famfamfam_flags/ad.png +0 -0
  49. data/app/assets/images/lit/famfamfam_flags/ae.png +0 -0
  50. data/app/assets/images/lit/famfamfam_flags/af.png +0 -0
  51. data/app/assets/images/lit/famfamfam_flags/ag.png +0 -0
  52. data/app/assets/images/lit/famfamfam_flags/ai.png +0 -0
  53. data/app/assets/images/lit/famfamfam_flags/al.png +0 -0
  54. data/app/assets/images/lit/famfamfam_flags/am.png +0 -0
  55. data/app/assets/images/lit/famfamfam_flags/an.png +0 -0
  56. data/app/assets/images/lit/famfamfam_flags/ao.png +0 -0
  57. data/app/assets/images/lit/famfamfam_flags/ar.png +0 -0
  58. data/app/assets/images/lit/famfamfam_flags/as.png +0 -0
  59. data/app/assets/images/lit/famfamfam_flags/at.png +0 -0
  60. data/app/assets/images/lit/famfamfam_flags/au.png +0 -0
  61. data/app/assets/images/lit/famfamfam_flags/aw.png +0 -0
  62. data/app/assets/images/lit/famfamfam_flags/ax.png +0 -0
  63. data/app/assets/images/lit/famfamfam_flags/az.png +0 -0
  64. data/app/assets/images/lit/famfamfam_flags/ba.png +0 -0
  65. data/app/assets/images/lit/famfamfam_flags/bb.png +0 -0
  66. data/app/assets/images/lit/famfamfam_flags/bd.png +0 -0
  67. data/app/assets/images/lit/famfamfam_flags/be.png +0 -0
  68. data/app/assets/images/lit/famfamfam_flags/bf.png +0 -0
  69. data/app/assets/images/lit/famfamfam_flags/bg.png +0 -0
  70. data/app/assets/images/lit/famfamfam_flags/bh.png +0 -0
  71. data/app/assets/images/lit/famfamfam_flags/bi.png +0 -0
  72. data/app/assets/images/lit/famfamfam_flags/bj.png +0 -0
  73. data/app/assets/images/lit/famfamfam_flags/bm.png +0 -0
  74. data/app/assets/images/lit/famfamfam_flags/bn.png +0 -0
  75. data/app/assets/images/lit/famfamfam_flags/bo.png +0 -0
  76. data/app/assets/images/lit/famfamfam_flags/br.png +0 -0
  77. data/app/assets/images/lit/famfamfam_flags/bs.png +0 -0
  78. data/app/assets/images/lit/famfamfam_flags/bt.png +0 -0
  79. data/app/assets/images/lit/famfamfam_flags/bv.png +0 -0
  80. data/app/assets/images/lit/famfamfam_flags/bw.png +0 -0
  81. data/app/assets/images/lit/famfamfam_flags/by.png +0 -0
  82. data/app/assets/images/lit/famfamfam_flags/bz.png +0 -0
  83. data/app/assets/images/lit/famfamfam_flags/ca.png +0 -0
  84. data/app/assets/images/lit/famfamfam_flags/catalonia.png +0 -0
  85. data/app/assets/images/lit/famfamfam_flags/cc.png +0 -0
  86. data/app/assets/images/lit/famfamfam_flags/cd.png +0 -0
  87. data/app/assets/images/lit/famfamfam_flags/cf.png +0 -0
  88. data/app/assets/images/lit/famfamfam_flags/cg.png +0 -0
  89. data/app/assets/images/lit/famfamfam_flags/ch.png +0 -0
  90. data/app/assets/images/lit/famfamfam_flags/ci.png +0 -0
  91. data/app/assets/images/lit/famfamfam_flags/ck.png +0 -0
  92. data/app/assets/images/lit/famfamfam_flags/cl.png +0 -0
  93. data/app/assets/images/lit/famfamfam_flags/cm.png +0 -0
  94. data/app/assets/images/lit/famfamfam_flags/cn.png +0 -0
  95. data/app/assets/images/lit/famfamfam_flags/co.png +0 -0
  96. data/app/assets/images/lit/famfamfam_flags/cr.png +0 -0
  97. data/app/assets/images/lit/famfamfam_flags/cs.png +0 -0
  98. data/app/assets/images/lit/famfamfam_flags/cu.png +0 -0
  99. data/app/assets/images/lit/famfamfam_flags/cv.png +0 -0
  100. data/app/assets/images/lit/famfamfam_flags/cx.png +0 -0
  101. data/app/assets/images/lit/famfamfam_flags/cy.png +0 -0
  102. data/app/assets/images/lit/famfamfam_flags/cz.png +0 -0
  103. data/app/assets/images/lit/famfamfam_flags/da.png +0 -0
  104. data/app/assets/images/lit/famfamfam_flags/de.png +0 -0
  105. data/app/assets/images/lit/famfamfam_flags/dj.png +0 -0
  106. data/app/assets/images/lit/famfamfam_flags/dk.png +0 -0
  107. data/app/assets/images/lit/famfamfam_flags/dm.png +0 -0
  108. data/app/assets/images/lit/famfamfam_flags/do.png +0 -0
  109. data/app/assets/images/lit/famfamfam_flags/dz.png +0 -0
  110. data/app/assets/images/lit/famfamfam_flags/ec.png +0 -0
  111. data/app/assets/images/lit/famfamfam_flags/ee.png +0 -0
  112. data/app/assets/images/lit/famfamfam_flags/eg.png +0 -0
  113. data/app/assets/images/lit/famfamfam_flags/eh.png +0 -0
  114. data/app/assets/images/lit/famfamfam_flags/en.png +0 -0
  115. data/app/assets/images/lit/famfamfam_flags/england.png +0 -0
  116. data/app/assets/images/lit/famfamfam_flags/er.png +0 -0
  117. data/app/assets/images/lit/famfamfam_flags/es.png +0 -0
  118. data/app/assets/images/lit/famfamfam_flags/et.png +0 -0
  119. data/app/assets/images/lit/famfamfam_flags/europeanunion.png +0 -0
  120. data/app/assets/images/lit/famfamfam_flags/fam.png +0 -0
  121. data/app/assets/images/lit/famfamfam_flags/fi.png +0 -0
  122. data/app/assets/images/lit/famfamfam_flags/fj.png +0 -0
  123. data/app/assets/images/lit/famfamfam_flags/fk.png +0 -0
  124. data/app/assets/images/lit/famfamfam_flags/fm.png +0 -0
  125. data/app/assets/images/lit/famfamfam_flags/fo.png +0 -0
  126. data/app/assets/images/lit/famfamfam_flags/fr.png +0 -0
  127. data/app/assets/images/lit/famfamfam_flags/ga.png +0 -0
  128. data/app/assets/images/lit/famfamfam_flags/gb.png +0 -0
  129. data/app/assets/images/lit/famfamfam_flags/gd.png +0 -0
  130. data/app/assets/images/lit/famfamfam_flags/ge.png +0 -0
  131. data/app/assets/images/lit/famfamfam_flags/gf.png +0 -0
  132. data/app/assets/images/lit/famfamfam_flags/gh.png +0 -0
  133. data/app/assets/images/lit/famfamfam_flags/gi.png +0 -0
  134. data/app/assets/images/lit/famfamfam_flags/gl.png +0 -0
  135. data/app/assets/images/lit/famfamfam_flags/gm.png +0 -0
  136. data/app/assets/images/lit/famfamfam_flags/gn.png +0 -0
  137. data/app/assets/images/lit/famfamfam_flags/gp.png +0 -0
  138. data/app/assets/images/lit/famfamfam_flags/gq.png +0 -0
  139. data/app/assets/images/lit/famfamfam_flags/gr.png +0 -0
  140. data/app/assets/images/lit/famfamfam_flags/gs.png +0 -0
  141. data/app/assets/images/lit/famfamfam_flags/gt.png +0 -0
  142. data/app/assets/images/lit/famfamfam_flags/gu.png +0 -0
  143. data/app/assets/images/lit/famfamfam_flags/gw.png +0 -0
  144. data/app/assets/images/lit/famfamfam_flags/gy.png +0 -0
  145. data/app/assets/images/lit/famfamfam_flags/hk.png +0 -0
  146. data/app/assets/images/lit/famfamfam_flags/hm.png +0 -0
  147. data/app/assets/images/lit/famfamfam_flags/hn.png +0 -0
  148. data/app/assets/images/lit/famfamfam_flags/hr.png +0 -0
  149. data/app/assets/images/lit/famfamfam_flags/ht.png +0 -0
  150. data/app/assets/images/lit/famfamfam_flags/hu.png +0 -0
  151. data/app/assets/images/lit/famfamfam_flags/id.png +0 -0
  152. data/app/assets/images/lit/famfamfam_flags/ie.png +0 -0
  153. data/app/assets/images/lit/famfamfam_flags/il.png +0 -0
  154. data/app/assets/images/lit/famfamfam_flags/in.png +0 -0
  155. data/app/assets/images/lit/famfamfam_flags/io.png +0 -0
  156. data/app/assets/images/lit/famfamfam_flags/iq.png +0 -0
  157. data/app/assets/images/lit/famfamfam_flags/ir.png +0 -0
  158. data/app/assets/images/lit/famfamfam_flags/is.png +0 -0
  159. data/app/assets/images/lit/famfamfam_flags/it.png +0 -0
  160. data/app/assets/images/lit/famfamfam_flags/ja.png +0 -0
  161. data/app/assets/images/lit/famfamfam_flags/jm.png +0 -0
  162. data/app/assets/images/lit/famfamfam_flags/jo.png +0 -0
  163. data/app/assets/images/lit/famfamfam_flags/jp.png +0 -0
  164. data/app/assets/images/lit/famfamfam_flags/ke.png +0 -0
  165. data/app/assets/images/lit/famfamfam_flags/kg.png +0 -0
  166. data/app/assets/images/lit/famfamfam_flags/kh.png +0 -0
  167. data/app/assets/images/lit/famfamfam_flags/ki.png +0 -0
  168. data/app/assets/images/lit/famfamfam_flags/km.png +0 -0
  169. data/app/assets/images/lit/famfamfam_flags/kn.png +0 -0
  170. data/app/assets/images/lit/famfamfam_flags/kp.png +0 -0
  171. data/app/assets/images/lit/famfamfam_flags/kr.png +0 -0
  172. data/app/assets/images/lit/famfamfam_flags/kw.png +0 -0
  173. data/app/assets/images/lit/famfamfam_flags/ky.png +0 -0
  174. data/app/assets/images/lit/famfamfam_flags/kz.png +0 -0
  175. data/app/assets/images/lit/famfamfam_flags/la.png +0 -0
  176. data/app/assets/images/lit/famfamfam_flags/lb.png +0 -0
  177. data/app/assets/images/lit/famfamfam_flags/lc.png +0 -0
  178. data/app/assets/images/lit/famfamfam_flags/li.png +0 -0
  179. data/app/assets/images/lit/famfamfam_flags/lk.png +0 -0
  180. data/app/assets/images/lit/famfamfam_flags/lr.png +0 -0
  181. data/app/assets/images/lit/famfamfam_flags/ls.png +0 -0
  182. data/app/assets/images/lit/famfamfam_flags/lt.png +0 -0
  183. data/app/assets/images/lit/famfamfam_flags/lu.png +0 -0
  184. data/app/assets/images/lit/famfamfam_flags/lv.png +0 -0
  185. data/app/assets/images/lit/famfamfam_flags/ly.png +0 -0
  186. data/app/assets/images/lit/famfamfam_flags/ma.png +0 -0
  187. data/app/assets/images/lit/famfamfam_flags/mc.png +0 -0
  188. data/app/assets/images/lit/famfamfam_flags/md.png +0 -0
  189. data/app/assets/images/lit/famfamfam_flags/me.png +0 -0
  190. data/app/assets/images/lit/famfamfam_flags/mg.png +0 -0
  191. data/app/assets/images/lit/famfamfam_flags/mh.png +0 -0
  192. data/app/assets/images/lit/famfamfam_flags/mk.png +0 -0
  193. data/app/assets/images/lit/famfamfam_flags/ml.png +0 -0
  194. data/app/assets/images/lit/famfamfam_flags/mm.png +0 -0
  195. data/app/assets/images/lit/famfamfam_flags/mn.png +0 -0
  196. data/app/assets/images/lit/famfamfam_flags/mo.png +0 -0
  197. data/app/assets/images/lit/famfamfam_flags/mp.png +0 -0
  198. data/app/assets/images/lit/famfamfam_flags/mq.png +0 -0
  199. data/app/assets/images/lit/famfamfam_flags/mr.png +0 -0
  200. data/app/assets/images/lit/famfamfam_flags/ms.png +0 -0
  201. data/app/assets/images/lit/famfamfam_flags/mt.png +0 -0
  202. data/app/assets/images/lit/famfamfam_flags/mu.png +0 -0
  203. data/app/assets/images/lit/famfamfam_flags/mv.png +0 -0
  204. data/app/assets/images/lit/famfamfam_flags/mw.png +0 -0
  205. data/app/assets/images/lit/famfamfam_flags/mx.png +0 -0
  206. data/app/assets/images/lit/famfamfam_flags/my.png +0 -0
  207. data/app/assets/images/lit/famfamfam_flags/mz.png +0 -0
  208. data/app/assets/images/lit/famfamfam_flags/na.png +0 -0
  209. data/app/assets/images/lit/famfamfam_flags/nc.png +0 -0
  210. data/app/assets/images/lit/famfamfam_flags/ne.png +0 -0
  211. data/app/assets/images/lit/famfamfam_flags/nf.png +0 -0
  212. data/app/assets/images/lit/famfamfam_flags/ng.png +0 -0
  213. data/app/assets/images/lit/famfamfam_flags/ni.png +0 -0
  214. data/app/assets/images/lit/famfamfam_flags/nl.png +0 -0
  215. data/app/assets/images/lit/famfamfam_flags/no.png +0 -0
  216. data/app/assets/images/lit/famfamfam_flags/np.png +0 -0
  217. data/app/assets/images/lit/famfamfam_flags/nr.png +0 -0
  218. data/app/assets/images/lit/famfamfam_flags/nu.png +0 -0
  219. data/app/assets/images/lit/famfamfam_flags/nz.png +0 -0
  220. data/app/assets/images/lit/famfamfam_flags/om.png +0 -0
  221. data/app/assets/images/lit/famfamfam_flags/pa.png +0 -0
  222. data/app/assets/images/lit/famfamfam_flags/pe.png +0 -0
  223. data/app/assets/images/lit/famfamfam_flags/pf.png +0 -0
  224. data/app/assets/images/lit/famfamfam_flags/pg.png +0 -0
  225. data/app/assets/images/lit/famfamfam_flags/ph.png +0 -0
  226. data/app/assets/images/lit/famfamfam_flags/pk.png +0 -0
  227. data/app/assets/images/lit/famfamfam_flags/pl.png +0 -0
  228. data/app/assets/images/lit/famfamfam_flags/pm.png +0 -0
  229. data/app/assets/images/lit/famfamfam_flags/pn.png +0 -0
  230. data/app/assets/images/lit/famfamfam_flags/pr.png +0 -0
  231. data/app/assets/images/lit/famfamfam_flags/ps.png +0 -0
  232. data/app/assets/images/lit/famfamfam_flags/pt.png +0 -0
  233. data/app/assets/images/lit/famfamfam_flags/pw.png +0 -0
  234. data/app/assets/images/lit/famfamfam_flags/py.png +0 -0
  235. data/app/assets/images/lit/famfamfam_flags/qa.png +0 -0
  236. data/app/assets/images/lit/famfamfam_flags/re.png +0 -0
  237. data/app/assets/images/lit/famfamfam_flags/ro.png +0 -0
  238. data/app/assets/images/lit/famfamfam_flags/rs.png +0 -0
  239. data/app/assets/images/lit/famfamfam_flags/ru.png +0 -0
  240. data/app/assets/images/lit/famfamfam_flags/rw.png +0 -0
  241. data/app/assets/images/lit/famfamfam_flags/sa.png +0 -0
  242. data/app/assets/images/lit/famfamfam_flags/sb.png +0 -0
  243. data/app/assets/images/lit/famfamfam_flags/sc.png +0 -0
  244. data/app/assets/images/lit/famfamfam_flags/scotland.png +0 -0
  245. data/app/assets/images/lit/famfamfam_flags/sd.png +0 -0
  246. data/app/assets/images/lit/famfamfam_flags/se.png +0 -0
  247. data/app/assets/images/lit/famfamfam_flags/sg.png +0 -0
  248. data/app/assets/images/lit/famfamfam_flags/sh.png +0 -0
  249. data/app/assets/images/lit/famfamfam_flags/si.png +0 -0
  250. data/app/assets/images/lit/famfamfam_flags/sj.png +0 -0
  251. data/app/assets/images/lit/famfamfam_flags/sk.png +0 -0
  252. data/app/assets/images/lit/famfamfam_flags/sl.png +0 -0
  253. data/app/assets/images/lit/famfamfam_flags/sm.png +0 -0
  254. data/app/assets/images/lit/famfamfam_flags/sn.png +0 -0
  255. data/app/assets/images/lit/famfamfam_flags/so.png +0 -0
  256. data/app/assets/images/lit/famfamfam_flags/sr.png +0 -0
  257. data/app/assets/images/lit/famfamfam_flags/st.png +0 -0
  258. data/app/assets/images/lit/famfamfam_flags/sv.png +0 -0
  259. data/app/assets/images/lit/famfamfam_flags/sy.png +0 -0
  260. data/app/assets/images/lit/famfamfam_flags/sz.png +0 -0
  261. data/app/assets/images/lit/famfamfam_flags/tc.png +0 -0
  262. data/app/assets/images/lit/famfamfam_flags/td.png +0 -0
  263. data/app/assets/images/lit/famfamfam_flags/tf.png +0 -0
  264. data/app/assets/images/lit/famfamfam_flags/tg.png +0 -0
  265. data/app/assets/images/lit/famfamfam_flags/th.png +0 -0
  266. data/app/assets/images/lit/famfamfam_flags/tj.png +0 -0
  267. data/app/assets/images/lit/famfamfam_flags/tk.png +0 -0
  268. data/app/assets/images/lit/famfamfam_flags/tl.png +0 -0
  269. data/app/assets/images/lit/famfamfam_flags/tm.png +0 -0
  270. data/app/assets/images/lit/famfamfam_flags/tn.png +0 -0
  271. data/app/assets/images/lit/famfamfam_flags/to.png +0 -0
  272. data/app/assets/images/lit/famfamfam_flags/tr.png +0 -0
  273. data/app/assets/images/lit/famfamfam_flags/tt.png +0 -0
  274. data/app/assets/images/lit/famfamfam_flags/tv.png +0 -0
  275. data/app/assets/images/lit/famfamfam_flags/tw.png +0 -0
  276. data/app/assets/images/lit/famfamfam_flags/tz.png +0 -0
  277. data/app/assets/images/lit/famfamfam_flags/ua.png +0 -0
  278. data/app/assets/images/lit/famfamfam_flags/ug.png +0 -0
  279. data/app/assets/images/lit/famfamfam_flags/um.png +0 -0
  280. data/app/assets/images/lit/famfamfam_flags/us.png +0 -0
  281. data/app/assets/images/lit/famfamfam_flags/uy.png +0 -0
  282. data/app/assets/images/lit/famfamfam_flags/uz.png +0 -0
  283. data/app/assets/images/lit/famfamfam_flags/va.png +0 -0
  284. data/app/assets/images/lit/famfamfam_flags/vc.png +0 -0
  285. data/app/assets/images/lit/famfamfam_flags/ve.png +0 -0
  286. data/app/assets/images/lit/famfamfam_flags/vg.png +0 -0
  287. data/app/assets/images/lit/famfamfam_flags/vi.png +0 -0
  288. data/app/assets/images/lit/famfamfam_flags/vn.png +0 -0
  289. data/app/assets/images/lit/famfamfam_flags/vu.png +0 -0
  290. data/app/assets/images/lit/famfamfam_flags/wales.png +0 -0
  291. data/app/assets/images/lit/famfamfam_flags/wf.png +0 -0
  292. data/app/assets/images/lit/famfamfam_flags/ws.png +0 -0
  293. data/app/assets/images/lit/famfamfam_flags/ye.png +0 -0
  294. data/app/assets/images/lit/famfamfam_flags/yt.png +0 -0
  295. data/app/assets/images/lit/famfamfam_flags/za.png +0 -0
  296. data/app/assets/images/lit/famfamfam_flags/zm.png +0 -0
  297. data/app/assets/images/lit/famfamfam_flags/zw.png +0 -0
@@ -2,11 +2,45 @@ module Lit
2
2
  class HashStorage < Hash
3
3
  def incr(key)
4
4
  self[key] ||= 0
5
- self[key] += 1
5
+ if self[key].is_a?(Integer)
6
+ self[key] += 1
7
+ else
8
+ subtree_keys(key).each { |k| self[k] += 1 }
9
+ end
6
10
  end
7
11
 
8
12
  def prefix
9
13
  nil
10
14
  end
15
+
16
+ def [](key)
17
+ super || subtree_of_key(key)
18
+ end
19
+
20
+ private
21
+
22
+ def subtree_of_key(key)
23
+ keys_of_subtree = subtree_keys(key)
24
+ return nil if keys_of_subtree.empty?
25
+
26
+ cache_localizations = form_cache_localizations(keys_of_subtree)
27
+
28
+ full_subtree = Lit::LocalizationKeysToHashService.call(cache_localizations)
29
+ requested_part = full_subtree.dig(*key.split('.'))
30
+ return nil if requested_part.blank?
31
+ return requested_part if requested_part.is_a?(String)
32
+
33
+ requested_part.deep_transform_keys(&:to_sym)
34
+ end
35
+
36
+ def subtree_keys(key)
37
+ keys.select { |k| k.match?(/\A#{key}*/) }
38
+ end
39
+
40
+ def form_cache_localizations(keys_of_subtree)
41
+ self_copy = self.select { |k, _| k.in?(keys_of_subtree) }
42
+ values_of_subtree = keys_of_subtree.map { |k| self_copy[k] }
43
+ Hash[keys_of_subtree.zip(values_of_subtree)]
44
+ end
11
45
  end
12
46
  end
@@ -1,4 +1,6 @@
1
1
  require 'redis'
2
+ require 'lit/services/localization_keys_to_hash_service'
3
+
2
4
  module Lit
3
5
  extend self
4
6
  def redis
@@ -16,13 +18,25 @@ module Lit
16
18
  Lit.redis
17
19
  end
18
20
 
21
+ # This handles a change in the redis-rb gem that changes exists => exists?
22
+ def exists?(key)
23
+ # Use recommended binary-returning method create [with this redis-rb commit](https://github.com/redis/redis-rb/commit/bf42fc9e0db4a1719d9b1ecc65aeb20425d44427).
24
+ return Lit.redis.exists?(key) if Lit.redis.respond_to?(:exists?)
25
+
26
+ # Fall back with older gem
27
+ Lit.redis.exists(key)
28
+ end
29
+
19
30
  def [](key)
20
- if Lit.redis.exists(_prefixed_key_for_array(key))
31
+ if exists?(_prefixed_key_for_array(key))
21
32
  Lit.redis.lrange(_prefixed_key(key), 0, -1)
22
- elsif Lit.redis.exists(_prefixed_key_for_nil(key))
33
+ elsif exists?(_prefixed_key_for_nil(key))
23
34
  nil
24
35
  else
25
- Lit.redis.get(_prefixed_key(key))
36
+ val = Lit.redis.get(_prefixed_key(key))
37
+ return val if val.present?
38
+
39
+ subtree_of_key(key)
26
40
  end
27
41
  end
28
42
 
@@ -56,7 +70,7 @@ module Lit
56
70
  end
57
71
 
58
72
  def has_key?(key)
59
- Lit.redis.exists(_prefixed_key(key))
73
+ exists?(_prefixed_key(key))
60
74
  end
61
75
  alias key? has_key?
62
76
 
@@ -77,11 +91,13 @@ module Lit
77
91
  private
78
92
 
79
93
  def _prefix
80
- prefix = 'lit:'
94
+ return @prefix_cached if @prefix_cached.present?
95
+
96
+ @prefix_cached = 'lit:'
81
97
  if Lit.storage_options.is_a?(Hash) && Lit.storage_options.key?(:prefix)
82
- prefix += "#{Lit.storage_options[:prefix]}:"
98
+ @prefix_cached += "#{Lit.storage_options[:prefix]}:"
83
99
  end
84
- prefix
100
+ @prefix_cached
85
101
  end
86
102
 
87
103
  def _prefixed_key(key = '')
@@ -95,5 +111,24 @@ module Lit
95
111
  def _prefixed_key_for_nil(key = '')
96
112
  _prefix + 'nil_flags:' + key.to_s
97
113
  end
114
+
115
+ def subtree_of_key(key)
116
+ keys_of_subtree = Lit.redis.keys("#{_prefixed_key(key)}*")
117
+ return nil if keys_of_subtree.empty?
118
+
119
+ values_of_subtree = Lit.redis.mget(keys_of_subtree)
120
+ cache_localizations = form_cache_localizations(keys_of_subtree, values_of_subtree)
121
+
122
+ full_subtree = Lit::LocalizationKeysToHashService.call(cache_localizations)
123
+ requested_part = full_subtree.dig(*key.split('.'))
124
+ return nil if requested_part.blank?
125
+ return requested_part if requested_part.is_a?(String)
126
+
127
+ requested_part.deep_transform_keys(&:to_sym)
128
+ end
129
+
130
+ def form_cache_localizations(keys, values)
131
+ Hash[keys.map { |k| k.sub(_prefix, '') }.zip(values)]
132
+ end
98
133
  end
99
134
  end
data/lib/lit/cache.rb CHANGED
@@ -18,22 +18,39 @@ end
18
18
 
19
19
  module Lit
20
20
  class Cache
21
+ @@last_update = nil
22
+ @@localization_cache = {}
23
+
21
24
  def initialize
22
25
  @hits_counter = Lit.get_key_value_engine
23
26
  @request_info_store = Lit.get_key_value_engine
24
- @hits_counter_working = true
27
+ @hits_counter_working = Lit.hits_counter_enabled
25
28
  @keys = nil
29
+
30
+ # current instance cache
26
31
  @localization_object_cache = {}
27
32
  @localization_key_object_cache = {}
33
+ clear_localization_cache
28
34
  end
29
35
 
30
36
  def [](key)
31
- key_without_locale = split_key(key).last
32
- update_hits_count(key)
33
- store_request_info(key_without_locale)
34
- localization = localizations[key]
35
- update_request_keys(key_without_locale, localization)
36
- localization
37
+ value = nil
38
+ check_and_clean_if_required
39
+ unless localization_cache.key?(key)
40
+ value = localizations[key]
41
+ localization_cache[key] = value
42
+ else
43
+ value = localization_cache[key]
44
+ end
45
+ update_hits_count(key) if @hits_counter_working
46
+
47
+ if Lit.store_request_info ||
48
+ Lit.store_request_keys
49
+ key_without_locale = split_key(key).last
50
+ store_request_info(key_without_locale)
51
+ update_request_keys(key_without_locale, value)
52
+ end
53
+ value
37
54
  end
38
55
 
39
56
  def []=(key, value)
@@ -45,7 +62,8 @@ module Lit
45
62
  end
46
63
 
47
64
  def has_key?(key)
48
- localizations.has_key?(key)
65
+ # check for instance cache first
66
+ localization_cache.has_key?(key) || localizations.has_key?(key)
49
67
  end
50
68
 
51
69
  def sync
@@ -67,12 +85,15 @@ module Lit
67
85
  locale = find_locale(locale_key)
68
86
  localization = find_localization(locale, key_without_locale, value: value, force_array: force_array, update_value: true)
69
87
  return localization.translation if startup_process && localization.is_changed?
88
+
70
89
  localizations[key] = localization.translation if localization
90
+ localization_cache[key] = localizations[key]
71
91
  end
72
92
 
73
93
  def update_cache(key, value)
74
94
  key = key.to_s
75
95
  localizations[key] = value
96
+ localization_cache[key] = value
76
97
  end
77
98
 
78
99
  def delete_locale(key)
@@ -83,6 +104,7 @@ module Lit
83
104
  delete_localization(locale, key_without_locale)
84
105
  @localization_key_object_cache = {}
85
106
  @localization_object_cache = {}
107
+ clear_localization_cache
86
108
  end
87
109
 
88
110
  def load_all_translations
@@ -103,6 +125,7 @@ module Lit
103
125
  @localization_object_cache.delete(key)
104
126
  localization = find_localization(locale, key_without_locale, default_fallback: true)
105
127
  localizations[key] = localization.translation if localization
128
+ clear_localization_cache
106
129
  end
107
130
 
108
131
  def delete_key(key)
@@ -115,10 +138,16 @@ module Lit
115
138
  I18n.backend.reload!
116
139
  end
117
140
 
118
- def reset
141
+ def reset_local_cache
119
142
  @locale_cache = {}
120
143
  @localization_key_object_cache = {}
121
144
  @localization_object_cache = {}
145
+ @localization_cache = {}
146
+ clear_localization_cache
147
+ end
148
+
149
+ def reset
150
+ reset_local_cache
122
151
  localizations.clear
123
152
  localization_keys.clear
124
153
  load_all_translations
@@ -128,7 +157,7 @@ module Lit
128
157
  def find_locale(locale_key)
129
158
  locale_key = locale_key.to_s
130
159
  @locale_cache ||= {}
131
- unless @locale_cache.key?(locale_key)
160
+ unless @locale_cache.key?(locale_key) && @locale_cache[locale_key].id
132
161
  locale = Lit::Locale.where(locale: locale_key).first_or_create!
133
162
  @locale_cache[locale_key] = locale
134
163
  end
@@ -151,6 +180,29 @@ module Lit
151
180
  @hits_counter_working = true
152
181
  end
153
182
 
183
+ def localization_cache
184
+ # Thread.current[:lit_thread_cache] ||= {}
185
+ @@localization_cache ||= {}
186
+ end
187
+
188
+ def clear_localization_cache
189
+ # Thread.current[:lit_thread_cache] = {}
190
+ @@localization_cache = {}
191
+ end
192
+
193
+ def check_and_clean_if_required
194
+ # we want this method to run only once during a request or background job
195
+ return if Thread.current[:localization_cache_valid]
196
+
197
+ # get most recently updated translation and compare with what we store in class var
198
+ last_up = Lit::Localization.order(updated_at: :desc).limit(1).pluck(:updated_at).first.to_i
199
+ if last_up != @@last_update
200
+ clear_localization_cache
201
+ @@last_update = last_up
202
+ end
203
+ Thread.current[:localization_cache_valid] = true
204
+ end
205
+
154
206
  private
155
207
 
156
208
  def localizations
@@ -305,6 +357,7 @@ module Lit
305
357
 
306
358
  def update_hits_count(key)
307
359
  return unless @hits_counter_working
360
+
308
361
  key_without_locale = split_key(key).last
309
362
  @hits_counter.incr('hits_counter.' + key)
310
363
  @hits_counter.incr('global_hits_counter.' + key_without_locale)
@@ -313,6 +366,7 @@ module Lit
313
366
  def store_request_info(key_without_locale)
314
367
  return unless Lit.store_request_info
315
368
  return unless Thread.current[:lit_current_request_path].present?
369
+
316
370
  info = get_request_info(key_without_locale)
317
371
  parts = info.split(' ').push(Thread.current[:lit_current_request_path]).uniq
318
372
  parts.shift if parts.count > 10
@@ -320,7 +374,9 @@ module Lit
320
374
  end
321
375
 
322
376
  def update_request_keys(key_without_locale, localization)
377
+ return unless Lit.store_request_keys
323
378
  return if Thread.current[:lit_request_keys].nil?
379
+
324
380
  Thread.current[:lit_request_keys] ||= {}
325
381
  Thread.current[:lit_request_keys][key_without_locale] = localization
326
382
  end
@@ -96,6 +96,8 @@ module Lit::CloudTranslation::Providers
96
96
  text_or_array.gsub(/%{(.+?)}/, '<code>__LIT__\1__LIT__</code>')
97
97
  when Array
98
98
  text_or_array.map { |s| sanitize_text(s) }
99
+ when nil
100
+ ''
99
101
  else
100
102
  raise TypeError
101
103
  end
@@ -63,6 +63,8 @@ module Lit::CloudTranslation::Providers
63
63
  text_or_array.gsub(/%{(.+?)}/, '%{_LIT_\1_LIT_}')
64
64
  when Array
65
65
  text_or_array.map { |s| sanitize_text(s) }
66
+ when nil
67
+ ''
66
68
  else
67
69
  raise TypeError
68
70
  end
data/lib/lit/engine.rb CHANGED
@@ -3,17 +3,18 @@ module Lit
3
3
  require 'jquery-rails'
4
4
 
5
5
  config.autoload_paths += %W[#{Lit::Engine.root}/app/controllers/lit/concerns]
6
+ paths.add 'lib', eager_load: true # Zeitwerk compatibility
6
7
 
7
8
  isolate_namespace Lit
8
9
 
9
10
  initializer 'lit.assets.precompile' do |app|
10
11
  app.config.assets.precompile += %w[lit/application.css lit/application.js]
11
12
  app.config.assets.precompile += %w[lit/lit_frontend.css lit/lit_frontend.js]
12
- # add language flags to list of precompiled assets
13
- if app.config.i18n.available_locales
14
- app.config.i18n.available_locales.each do |l|
15
- app.config.assets.precompile << "lit/famfamfam_flags/#{l.to_s[0,2]}.png"
16
- end
13
+ end
14
+
15
+ initializer 'lit.reloader' do |app|
16
+ config.to_prepare do
17
+ Lit.loader.cache.reset_local_cache if Lit.loader
17
18
  end
18
19
  end
19
20
 
@@ -24,5 +25,11 @@ module Lit
24
25
  end
25
26
  end
26
27
  end
28
+
29
+ initializer :append_before_action do
30
+ ActionController::Base.send :before_action do
31
+ Thread.current[:lit_thread_cache] = {}
32
+ end
33
+ end
27
34
  end
28
35
  end
data/lib/lit/export.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'csv'
2
+ require 'lit/services/localization_keys_to_hash_service'
2
3
 
3
4
  module Lit
4
5
  class Export
@@ -17,7 +18,7 @@ module Lit
17
18
 
18
19
  case format
19
20
  when :yaml
20
- exported_keys = nested_string_keys_to_hash(db_localizations)
21
+ exported_keys = Lit::LocalizationKeysToHashService.call(db_localizations)
21
22
  exported_keys.to_yaml
22
23
  when :csv
23
24
  relevant_locales = locale_keys.presence || I18n.available_locales.map(&:to_s)
@@ -55,23 +56,6 @@ module Lit
55
56
  end
56
57
  end
57
58
 
58
- private_class_method def self.nested_string_keys_to_hash(db_localizations)
59
- # http://subtech.g.hatena.ne.jp/cho45/20061122
60
- deep_proc = proc do |_k, s, o|
61
- if s.is_a?(Hash) && o.is_a?(Hash)
62
- next s.merge(o, &deep_proc)
63
- end
64
- next o
65
- end
66
- nested_keys = {}
67
- db_localizations.sort.each do |k, v|
68
- key_parts = k.to_s.split('.')
69
- converted = key_parts.reverse.reduce(v) { |a, n| { n => a } }
70
- nested_keys.merge!(converted, &deep_proc)
71
- end
72
- nested_keys
73
- end
74
-
75
59
  # This is like Array#transpose but ignores size differences between inner arrays.
76
60
  private_class_method def self.transpose(matrix)
77
61
  maxlen = matrix.max { |x| x.length }.length
@@ -69,10 +69,20 @@ module Lit
69
69
 
70
70
  parts = I18n.normalize_keys(locale, key, scope, options[:separator])
71
71
  key_with_locale = parts.join('.')
72
+
73
+ # we might want to return content later, but we first need to check if it's in cache.
74
+ # it's important to rememver, that accessing non-existen key modifies cache by creating one
75
+ had_key = @cache.has_key?(key_with_locale)
76
+
72
77
  # check in cache or in simple backend
73
78
  content = @cache[key_with_locale] || super
79
+
80
+ # return if content is in cache - it CAN be `nil`
81
+ return content if had_key && !options[:default]
82
+
74
83
  return content if parts.size <= 1
75
- if content.nil? && should_cache?(key_with_locale, options)
84
+
85
+ if content.nil? && should_cache?(key_with_locale, options, had_key)
76
86
  new_content = @cache.init_key_with_value(key_with_locale, content)
77
87
  content = new_content if content.nil? # Content can change when Lit.humanize is true for example
78
88
  # so there is no content in cache - it might not be if ie. we're doing
@@ -106,7 +116,7 @@ module Lit
106
116
  # it anyway if we return nil, but then it will wrap it also in
107
117
  # translation_missing span.
108
118
  # Humanizing key should be last resort
109
- if content.nil? && Lit.humanize_key && key.match(Lit.humanize_key_ignored).nil?
119
+ if content.nil? && Lit.humanize_key && Lit.humanize_key_ignored.match(key).nil?
110
120
  content = key.to_s.split('.').last.humanize
111
121
  if content.present?
112
122
  @cache[key_with_locale] = content
@@ -129,7 +139,7 @@ module Lit
129
139
  # end
130
140
  elsif data.respond_to?(:to_str) || data.is_a?(Array)
131
141
  key = ([locale] + scope).join('.')
132
- return if startup_process && @cache.keys.member?(key) && Lit.ignore_yaml_on_startup
142
+ return if startup_process && Lit.ignore_yaml_on_startup && (Thread.current[:lit_cache_keys] || @cache.keys).member?(key)
133
143
  @cache.update_locale(key, data, data.is_a?(Array), startup_process)
134
144
  elsif data.nil?
135
145
  return if startup_process
@@ -139,11 +149,13 @@ module Lit
139
149
  end
140
150
 
141
151
  def load_translations_to_cache
152
+ Thread.current[:lit_cache_keys] = @cache.keys
142
153
  ActiveRecord::Base.transaction do
143
154
  (@translations || {}).each do |locale, data|
144
155
  store_item(locale, data, [], true) if valid_locale?(locale)
145
156
  end
146
157
  end
158
+ Thread.current[:lit_cache_keys] = nil
147
159
  end
148
160
 
149
161
  def init_translations
@@ -155,7 +167,7 @@ module Lit
155
167
  # load translations from database to cache
156
168
  @cache.load_all_translations
157
169
  # load translations from @translations to cache
158
- load_translations_to_cache
170
+ load_translations_to_cache unless Lit.ignore_yaml_on_startup
159
171
  @initialized = true
160
172
  end
161
173
 
@@ -179,9 +191,10 @@ module Lit
179
191
  Lit.ignored_keys.any?{ |k| key_without_locale.start_with?(k) }
180
192
  end
181
193
 
182
- def should_cache?(key_with_locale, options)
183
- if @cache.has_key?(key_with_locale)
184
- return false unless options[:default]
194
+ # checks if should cache. `had_key` is passed, as once cache has been accesed, it's already modified and key exists
195
+ def should_cache?(key_with_locale, options, had_key)
196
+ if had_key
197
+ return false unless options[:default] && !options[:default].is_a?(Array)
185
198
  end
186
199
 
187
200
  _, key_without_locale = ::Lit::Cache.split_key(key_with_locale)