lit 1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)