lit 1.0.2 → 1.1.0
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.
- checksums.yaml +4 -4
- data/README.md +10 -2
- data/app/assets/javascripts/lit/backend/bootstrap.min.js +7 -0
- data/app/controllers/lit/api/v1/localizations_controller.rb +7 -2
- data/app/controllers/lit/localization_keys_controller.rb +6 -0
- data/app/controllers/lit/localizations_controller.rb +1 -1
- data/app/controllers/lit/sources_controller.rb +1 -1
- data/app/helpers/lit/frontend_helper.rb +1 -1
- data/app/helpers/lit/localizations_helper.rb +9 -0
- data/app/models/lit/base.rb +33 -17
- data/app/models/lit/incomming_localization.rb +2 -2
- data/app/services/remote_interactor_service.rb +1 -0
- data/app/views/layouts/lit/application.html.erb +0 -1
- data/app/views/lit/localization_keys/index.html.erb +2 -0
- data/app/views/lit/localization_keys/not_translated.html.erb +2 -0
- data/app/views/lit/localization_keys/visited_again.html.erb +3 -0
- data/lib/generators/lit/install/templates/initializer.rb +6 -3
- data/lib/lit.rb +19 -6
- data/lib/lit/adapters/hash_storage.rb +35 -1
- data/lib/lit/adapters/redis_storage.rb +41 -7
- data/lib/lit/cache.rb +40 -9
- data/lib/lit/engine.rb +6 -6
- data/lib/lit/export.rb +2 -18
- data/lib/lit/i18n_backend.rb +18 -6
- data/lib/lit/import.rb +14 -11
- data/lib/lit/services/localization_keys_to_hash_service.rb +23 -0
- data/lib/lit/version.rb +1 -1
- metadata +12 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ecbd506c6be8279728e5da85a4857dd542c75cdc752efac9745dd7e2ba022a5c
|
|
4
|
+
data.tar.gz: 45bf91076203d8be22b2da854886657ddb85f07acbc42cd287b9246883e0955d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b78bb6b6ef52a2010d0725165a0e898d72dbf5fcf843b4ae756291f5648032e12ac1c6ded47a90418b258627c5ea1521e956dbd3e1387f428d623ec144a0c9cc
|
|
7
|
+
data.tar.gz: ce87f326e76dce205a987bbb1474a154406afd5fda42a9f3b92017cd8912f621a54ef36a16bd1f0861e6193341a0db24220341a8d1c5120cdfbbb3d2438473c5
|
data/README.md
CHANGED
|
@@ -128,7 +128,7 @@ Lit::CloudTranslation.provider = Lit::CloudTranslation::Providers::Google
|
|
|
128
128
|
|
|
129
129
|
...and make sure you have this in your Gemfile:
|
|
130
130
|
```
|
|
131
|
-
gem 'google-cloud-translate'
|
|
131
|
+
gem 'google-cloud-translate', '~> 1.2.4'
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
To use translation via Google, you need to obtain a [service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) containing all the credentials required by the API.
|
|
@@ -145,6 +145,13 @@ These credentials can be given in three ways:
|
|
|
145
145
|
... # see Google docs link above for reference
|
|
146
146
|
}
|
|
147
147
|
end
|
|
148
|
+
|
|
149
|
+
# For example, for Rails 6, from encrypted credentials file (HashWithIndifferentAccess is used, because the keys in
|
|
150
|
+
the credentials.config could be strings or, as well, symbols):
|
|
151
|
+
|
|
152
|
+
Lit::CloudTranslation.configure do |config|
|
|
153
|
+
config.keyfile_hash = HashWithIndifferentAccess.new(Rails.application.credentials.config[:google_translate_api])
|
|
154
|
+
end
|
|
148
155
|
```
|
|
149
156
|
* directly via `GOOGLE_TRANSLATE_API_<element>` environment variables, where e.g. the `GOOGLE_TRANSLATE_API_PROJECT_ID` variable corresponds to the `project_id` element of a JSON keyfile. Typically, only the following variables are mandatory:
|
|
150
157
|
* `GOOGLE_TRANSLATE_API_PROJECT_ID`
|
|
@@ -273,7 +280,8 @@ Lit.store_request_info = true
|
|
|
273
280
|
1. `gem install bundler && bundle install` - ensure Bundler and all required gems are installed
|
|
274
281
|
2. `bundle exec appraisal install` - install gems from appraisal's gemfiles
|
|
275
282
|
3. `cp test/dummy/config/database.yml.sample test/dummy/config/database.yml` - move a database.yml in place (remember to fill your DB credentials in it)
|
|
276
|
-
4. `RAILS_ENV=test appraisal rails-5.2 rake db:setup` - setup lit DB (see test/config/database.yml); do it
|
|
283
|
+
4. `RAILS_ENV=test bundle exec appraisal rails-5.2 rake db:setup` - setup lit DB (see test/config/database.yml); do it
|
|
284
|
+
only once, it does not matter which Rails version you use for `appraisal`
|
|
277
285
|
5. `bundle exec appraisal rake` - run the tests!
|
|
278
286
|
|
|
279
287
|
### License
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Bootstrap v3.0.3 (http://getbootstrap.com)
|
|
3
|
+
* Copyright 2013 Twitter, Inc.
|
|
4
|
+
* Licensed under http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]'),b=!0;if(a.length){var c=this.$element.find("input");"radio"===c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?b=!1:a.find(".active").removeClass("active")),b&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}b&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b),f.trigger(d=a.Event("show.bs.dropdown")),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown"),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=a("[role=menu] li:not(.divider):visible a",f);if(h.length){var i=h.index(h.filter(":focus"));38==b.keyCode&&i>0&&i--,40==b.keyCode&&i<h.length-1&&i++,~i||(i=0),h.eq(i).focus()}}}};var g=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new f(this)),"string"==typeof b&&d[b].call(c)})},a.fn.dropdown.Constructor=f,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=g,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",e,f.prototype.toggle).on("keydown.bs.dropdown.data-api",e+", [role=menu]",f.prototype.keydown)}(jQuery),+function(a){"use strict";var b=function(b,c){this.options=c,this.$element=a(b),this.$backdrop=this.isShown=null,this.options.remote&&this.$element.load(this.options.remote)};b.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},b.prototype.toggle=function(a){return this[this.isShown?"hide":"show"](a)},b.prototype.show=function(b){var c=this,d=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(d),this.isShown||d.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.$element.on("click.dismiss.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var d=a.support.transition&&c.$element.hasClass("fade");c.$element.parent().length||c.$element.appendTo(document.body),c.$element.show(),d&&c.$element[0].offsetWidth,c.$element.addClass("in").attr("aria-hidden",!1),c.enforceFocus();var e=a.Event("shown.bs.modal",{relatedTarget:b});d?c.$element.find(".modal-dialog").one(a.support.transition.end,function(){c.$element.focus().trigger(e)}).emulateTransitionEnd(300):c.$element.focus().trigger(e)}))},b.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one(a.support.transition.end,a.proxy(this.hideModal,this)).emulateTransitionEnd(300):this.hideModal())},b.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.focus()},this))},b.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keyup.dismiss.bs.modal")},b.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden.bs.modal")})},b.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},b.prototype.backdrop=function(b){var c=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var d=a.support.transition&&c;if(this.$backdrop=a('<div class="modal-backdrop '+c+'" />').appendTo(document.body),this.$element.on("click.dismiss.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focus",i="hover"==g?"mouseleave":"blur";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show),void 0):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide),void 0):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this.tip();this.setContent(),this.options.animation&&c.addClass("fade");var d="function"==typeof this.options.placement?this.options.placement.call(this,c[0],this.$element[0]):this.options.placement,e=/\s?auto?\s?/i,f=e.test(d);f&&(d=d.replace(e,"")||"top"),c.detach().css({top:0,left:0,display:"block"}).addClass(d),this.options.container?c.appendTo(this.options.container):c.insertAfter(this.$element);var g=this.getPosition(),h=c[0].offsetWidth,i=c[0].offsetHeight;if(f){var j=this.$element.parent(),k=d,l=document.documentElement.scrollTop||document.body.scrollTop,m="body"==this.options.container?window.innerWidth:j.outerWidth(),n="body"==this.options.container?window.innerHeight:j.outerHeight(),o="body"==this.options.container?0:j.offset().left;d="bottom"==d&&g.top+g.height+i-l>n?"top":"top"==d&&g.top-l-i<0?"bottom":"right"==d&&g.right+h>m?"left":"left"==d&&g.left-h<o?"right":d,c.removeClass(k).addClass(d)}var p=this.getCalculatedOffset(d,g,h,i);this.applyPlacement(p,d),this.$element.trigger("shown.bs."+this.type)}},b.prototype.applyPlacement=function(a,b){var c,d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),a.top=a.top+g,a.left=a.left+h,d.offset(a).addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;if("top"==b&&j!=f&&(c=!0,a.top=a.top+f-j),/bottom|top/.test(b)){var k=0;a.left<0&&(k=-2*a.left,a.left=0,d.offset(a),i=d[0].offsetWidth,j=d[0].offsetHeight),this.replaceArrow(k-e+i,i,"left")}else this.replaceArrow(j-f,j,"top");c&&d.offset(a)},b.prototype.replaceArrow=function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},b.prototype.hide=function(){function b(){"in"!=c.hoverState&&d.detach()}var c=this,d=this.tip(),e=a.Event("hide.bs."+this.type);return this.$element.trigger(e),e.isDefaultPrevented()?void 0:(d.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d.one(a.support.transition.end,b).emulateTransitionEnd(150):b(),this.$element.trigger("hidden.bs."+this.type),this)},b.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},b.prototype.hasContent=function(){return this.getTitle()},b.prototype.getPosition=function(){var b=this.$element[0];return a.extend({},"function"==typeof b.getBoundingClientRect?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},b.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},b.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},b.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},b.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},b.prototype.enable=function(){this.enabled=!0},b.prototype.disable=function(){this.enabled=!1},b.prototype.toggleEnabled=function(){this.enabled=!this.enabled},b.prototype.toggle=function(b){var c=b?a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;c.tip().hasClass("in")?c.leave(c):c.enter(c)},b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof c&&c;e||d.data("bs.tooltip",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(jQuery),+function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");b.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery);
|
|
@@ -22,12 +22,17 @@ module Lit
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
24
|
def fetch_localizations
|
|
25
|
+
scope = Localization.includes(
|
|
26
|
+
:locale,
|
|
27
|
+
:localization_key
|
|
28
|
+
)
|
|
29
|
+
|
|
25
30
|
if params[:after].present?
|
|
26
31
|
after_date = Time.parse("#{params[:after]} #{Time.zone.name}")
|
|
27
32
|
.in_time_zone
|
|
28
|
-
|
|
33
|
+
scope.after(after_date).to_a
|
|
29
34
|
else
|
|
30
|
-
|
|
35
|
+
scope.all
|
|
31
36
|
end
|
|
32
37
|
end
|
|
33
38
|
end
|
|
@@ -37,6 +37,10 @@ module Lit
|
|
|
37
37
|
@scope.respond_to?(Kaminari.config.page_method_name)
|
|
38
38
|
@scope = @scope.send(Kaminari.config.page_method_name, params[:page])
|
|
39
39
|
end
|
|
40
|
+
if defined?(WillPaginate) &&
|
|
41
|
+
@scope.respond_to?(:paginate)
|
|
42
|
+
@scope = @scope.paginate(page: params[:page])
|
|
43
|
+
end
|
|
40
44
|
get_localization_keys
|
|
41
45
|
render action: :index
|
|
42
46
|
end
|
|
@@ -88,6 +92,8 @@ module Lit
|
|
|
88
92
|
end
|
|
89
93
|
if defined?(Kaminari) and @scope.respond_to?(Kaminari.config.page_method_name)
|
|
90
94
|
@localization_keys = @scope.send(Kaminari.config.page_method_name, params[:page])
|
|
95
|
+
elsif defined?(WillPaginate) and @scope.respond_to?(:paginate)
|
|
96
|
+
@localization_keys = @scope.paginate(page: params[:page])
|
|
91
97
|
else
|
|
92
98
|
@localization_keys = @scope
|
|
93
99
|
end
|
|
@@ -20,7 +20,7 @@ module Lit
|
|
|
20
20
|
# #after_update_operations. So it'll first set :is_changed to true
|
|
21
21
|
# and then it will be properly read in the cache setting routine.
|
|
22
22
|
@localization.transaction do
|
|
23
|
-
after_update_operations if @localization.
|
|
23
|
+
after_update_operations if @localization.update(clear_params)
|
|
24
24
|
end
|
|
25
25
|
respond_to do |f|
|
|
26
26
|
f.js
|
|
@@ -9,7 +9,7 @@ module Lit
|
|
|
9
9
|
key = scope_key_by_partial(key)
|
|
10
10
|
key = pluralized_key(key, count) if count
|
|
11
11
|
|
|
12
|
-
content = super(key, options)
|
|
12
|
+
content = super(key, options.symbolize_keys)
|
|
13
13
|
if !options[:skip_lit] && lit_authorized?
|
|
14
14
|
content = get_translateable_span(key, content)
|
|
15
15
|
end
|
|
@@ -9,6 +9,15 @@ module Lit
|
|
|
9
9
|
escape_javascript val.to_s
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def locale_flag locale
|
|
13
|
+
locale = locale.to_s.upcase[0,2]
|
|
14
|
+
locale = case locale
|
|
15
|
+
when 'EN' then 'GB'
|
|
16
|
+
else locale
|
|
17
|
+
end
|
|
18
|
+
locale.tr('A-Z', "\u{1F1E6}-\u{1F1FF}")
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
def allow_wysiwyg_editor?(key)
|
|
13
22
|
Lit.all_translations_are_html_safe || key.to_s =~ /(\b|_|\.)html$/
|
|
14
23
|
end
|
data/app/models/lit/base.rb
CHANGED
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
class Lit::Base < ActiveRecord::Base
|
|
2
2
|
self.abstract_class = true
|
|
3
3
|
|
|
4
|
-
before_save :
|
|
5
|
-
before_save :mark_for_retry_on_update, on: :update
|
|
4
|
+
before_save :mark_for_retry
|
|
6
5
|
|
|
7
6
|
attr_accessor :retried_created, :retried_updated
|
|
8
7
|
|
|
9
|
-
def
|
|
8
|
+
def mark_for_retry
|
|
10
9
|
@will_retry_create = true
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
after_rollback :retry_lit_model_save
|
|
13
|
+
|
|
14
|
+
def rolledback_after_insert?
|
|
15
|
+
persisted? && @was_saved_with_insert && @was_rolled_back
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
def rolledback_after_update?
|
|
19
|
+
persisted? && @was_saved_with_update && @was_rolled_back
|
|
20
|
+
end
|
|
18
21
|
|
|
19
22
|
private
|
|
20
23
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
def create_or_update(*args, &block)
|
|
25
|
+
@was_saved_with_insert = true if new_record?
|
|
26
|
+
@was_saved_with_update = true if persisted?
|
|
27
|
+
|
|
28
|
+
super
|
|
24
29
|
end
|
|
25
30
|
|
|
26
|
-
def
|
|
27
|
-
return if
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
def retry_lit_model_save
|
|
32
|
+
return if @was_rolled_back
|
|
33
|
+
@was_rolled_back = true
|
|
34
|
+
do_retry if instance_variable_defined?(:@will_retry_create) && @will_retry_create
|
|
30
35
|
end
|
|
31
36
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
def do_retry
|
|
38
|
+
if !retried_created
|
|
39
|
+
self.retried_created = true
|
|
40
|
+
if rolledback_after_insert?
|
|
41
|
+
self.class.create! attributes.merge(retried_created: true)
|
|
42
|
+
end
|
|
43
|
+
elsif !retried_updated
|
|
44
|
+
# Why elsif and not just if? Because if a record object was first saved
|
|
45
|
+
# with INSERT and then with UPDATE (still being the same Ruby object),
|
|
46
|
+
# it makes no sense to retry both create and upadte. Let's only retry create.
|
|
47
|
+
self.retried_updated = true
|
|
48
|
+
if rolledback_after_update?
|
|
49
|
+
update_columns(attributes)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
36
52
|
end
|
|
37
53
|
|
|
38
54
|
end
|
|
@@ -59,14 +59,14 @@ module Lit
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def update_existing_localization_data
|
|
62
|
-
localization.
|
|
62
|
+
localization.update!(
|
|
63
63
|
translated_value: translated_value,
|
|
64
64
|
is_changed: true
|
|
65
65
|
)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def update_existing_localization_key_data
|
|
69
|
-
localization_key.
|
|
69
|
+
localization_key.update!(
|
|
70
70
|
is_deleted: localization_key_is_deleted
|
|
71
71
|
)
|
|
72
72
|
end
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
<%= stylesheet_link_tag '//netdna.bootstrapcdn.com/bootswatch/3.0.3/yeti/bootstrap.min.css', media: 'all' %>
|
|
11
11
|
<%= stylesheet_link_tag 'lit/application', media: 'all' %>
|
|
12
12
|
<%= javascript_include_tag 'lit/application' %>
|
|
13
|
-
<%= javascript_include_tag '//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js' %>
|
|
14
13
|
<%= csrf_meta_tags %>
|
|
15
14
|
</head>
|
|
16
15
|
<body class="<%= params[:controller] %>">
|
|
@@ -29,11 +29,14 @@ Lit.all_translations_are_html_safe = false
|
|
|
29
29
|
# <span title="translation missing string"></span>
|
|
30
30
|
Lit.humanize_key = false
|
|
31
31
|
|
|
32
|
-
# If set to `
|
|
33
|
-
# values with ones with yaml
|
|
34
|
-
# web ui before
|
|
32
|
+
# If set to `false` will always parse yaml files upon startup and update cached
|
|
33
|
+
# values with ones with yaml (but only, if those keys haven't been changed via
|
|
34
|
+
# web ui before)
|
|
35
35
|
Lit.ignore_yaml_on_startup = true
|
|
36
36
|
|
|
37
|
+
# Array of ignored keys or key prefixes
|
|
38
|
+
Lit.ignored_keys = ['i18n.transliterate'].freeze
|
|
39
|
+
|
|
37
40
|
# API enabled? API allows third party browsing your translations, as well as
|
|
38
41
|
# synchronizing them between environments
|
|
39
42
|
Lit.api_enabled = false
|
data/lib/lit.rb
CHANGED
|
@@ -17,6 +17,8 @@ module Lit
|
|
|
17
17
|
mattr_accessor :all_translations_are_html_safe
|
|
18
18
|
mattr_accessor :set_last_updated_at_upon_creation
|
|
19
19
|
mattr_accessor :store_request_info
|
|
20
|
+
mattr_accessor :store_request_keys
|
|
21
|
+
mattr_accessor :hits_counter_enabled
|
|
20
22
|
|
|
21
23
|
class << self
|
|
22
24
|
attr_accessor :loader
|
|
@@ -30,13 +32,16 @@ module Lit
|
|
|
30
32
|
Lit.humanize_key_ignored_keys = [] if Lit.humanize_key_ignored_keys.nil?
|
|
31
33
|
Lit.humanize_key_ignored = %w[i18n date datetime number time support ]
|
|
32
34
|
Lit.humanize_key_ignored |= Lit.humanize_key_ignored_keys
|
|
33
|
-
Lit.humanize_key_ignored =
|
|
34
|
-
if Lit.ignored_keys.is_a?(String)
|
|
35
|
-
keys = Lit.ignored_keys.split(',').map(&:strip)
|
|
36
|
-
Lit.ignored_keys = keys
|
|
37
|
-
end
|
|
35
|
+
Lit.humanize_key_ignored = Regexp.new("(#{Lit.humanize_key_ignored.join('|')}).*")
|
|
38
36
|
Lit.ignore_yaml_on_startup = true if Lit.ignore_yaml_on_startup.nil?
|
|
37
|
+
|
|
38
|
+
Lit.ignored_keys = Lit.ignored_keys.split(',').map(&:strip) if Lit.ignored_keys.is_a?(String)
|
|
39
39
|
Lit.ignored_keys = [] unless Lit.ignored_keys.is_a?(Array)
|
|
40
|
+
Lit.ignored_keys = Lit.ignored_keys.map(&:freeze).freeze
|
|
41
|
+
|
|
42
|
+
Lit.hits_counter_enabled = false if Lit.hits_counter_enabled.nil?
|
|
43
|
+
Lit.store_request_info = false if Lit.store_request_info.nil?
|
|
44
|
+
Lit.store_request_keys = false if Lit.store_request_keys.nil?
|
|
40
45
|
# if loading all translations on start, migrations have to be already
|
|
41
46
|
# performed, fails on first deploy
|
|
42
47
|
# self.loader.cache.load_all_translations
|
|
@@ -47,7 +52,15 @@ module Lit
|
|
|
47
52
|
|
|
48
53
|
def self.check_if_table_exists
|
|
49
54
|
Lit::Locale.table_exists?
|
|
50
|
-
rescue
|
|
55
|
+
rescue ActiveRecord::ActiveRecordError => e
|
|
56
|
+
log_txt =
|
|
57
|
+
"An #{e.class} error has been raised during Lit initialization. " \
|
|
58
|
+
"Lit assumes that database tables do not exist.\n\n" \
|
|
59
|
+
"Error: #{e.message}\n\n" \
|
|
60
|
+
"Backtrace:\n" \
|
|
61
|
+
"#{e.backtrace.join("\n")}"
|
|
62
|
+
Logger.new(STDOUT).error(log_txt) if ::Rails.env.test? # ensure this is logged to stdout in test
|
|
63
|
+
::Rails.logger.error(log_txt)
|
|
51
64
|
false
|
|
52
65
|
end
|
|
53
66
|
|
|
@@ -2,11 +2,45 @@ module Lit
|
|
|
2
2
|
class HashStorage < Hash
|
|
3
3
|
def incr(key)
|
|
4
4
|
self[key] ||= 0
|
|
5
|
-
self[key]
|
|
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,24 @@ 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
|
+
# Fall back with older gem
|
|
26
|
+
Lit.redis.exists(key)
|
|
27
|
+
end
|
|
28
|
+
|
|
19
29
|
def [](key)
|
|
20
|
-
if
|
|
30
|
+
if self.exists?(_prefixed_key_for_array(key))
|
|
21
31
|
Lit.redis.lrange(_prefixed_key(key), 0, -1)
|
|
22
|
-
elsif
|
|
32
|
+
elsif self.exists?(_prefixed_key_for_nil(key))
|
|
23
33
|
nil
|
|
24
34
|
else
|
|
25
|
-
Lit.redis.get(_prefixed_key(key))
|
|
35
|
+
val = Lit.redis.get(_prefixed_key(key))
|
|
36
|
+
return val if val.present?
|
|
37
|
+
|
|
38
|
+
subtree_of_key(key)
|
|
26
39
|
end
|
|
27
40
|
end
|
|
28
41
|
|
|
@@ -56,7 +69,7 @@ module Lit
|
|
|
56
69
|
end
|
|
57
70
|
|
|
58
71
|
def has_key?(key)
|
|
59
|
-
|
|
72
|
+
self.exists?(_prefixed_key(key))
|
|
60
73
|
end
|
|
61
74
|
alias key? has_key?
|
|
62
75
|
|
|
@@ -77,11 +90,13 @@ module Lit
|
|
|
77
90
|
private
|
|
78
91
|
|
|
79
92
|
def _prefix
|
|
80
|
-
|
|
93
|
+
return @prefix_cached if @prefix_cached.present?
|
|
94
|
+
|
|
95
|
+
@prefix_cached = 'lit:'
|
|
81
96
|
if Lit.storage_options.is_a?(Hash) && Lit.storage_options.key?(:prefix)
|
|
82
|
-
|
|
97
|
+
@prefix_cached += "#{Lit.storage_options[:prefix]}:"
|
|
83
98
|
end
|
|
84
|
-
|
|
99
|
+
@prefix_cached
|
|
85
100
|
end
|
|
86
101
|
|
|
87
102
|
def _prefixed_key(key = '')
|
|
@@ -95,5 +110,24 @@ module Lit
|
|
|
95
110
|
def _prefixed_key_for_nil(key = '')
|
|
96
111
|
_prefix + 'nil_flags:' + key.to_s
|
|
97
112
|
end
|
|
113
|
+
|
|
114
|
+
def subtree_of_key(key)
|
|
115
|
+
keys_of_subtree = Lit.redis.keys("#{_prefixed_key(key)}*")
|
|
116
|
+
return nil if keys_of_subtree.empty?
|
|
117
|
+
|
|
118
|
+
values_of_subtree = Lit.redis.mget(keys_of_subtree)
|
|
119
|
+
cache_localizations = form_cache_localizations(keys_of_subtree, values_of_subtree)
|
|
120
|
+
|
|
121
|
+
full_subtree = Lit::LocalizationKeysToHashService.call(cache_localizations)
|
|
122
|
+
requested_part = full_subtree.dig(*key.split('.'))
|
|
123
|
+
return nil if requested_part.blank?
|
|
124
|
+
return requested_part if requested_part.is_a?(String)
|
|
125
|
+
|
|
126
|
+
requested_part.deep_transform_keys(&:to_sym)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def form_cache_localizations(keys, values)
|
|
130
|
+
Hash[keys.map { |k| k.sub(_prefix, '') }.zip(values)]
|
|
131
|
+
end
|
|
98
132
|
end
|
|
99
133
|
end
|
data/lib/lit/cache.rb
CHANGED
|
@@ -21,19 +21,32 @@ module Lit
|
|
|
21
21
|
def initialize
|
|
22
22
|
@hits_counter = Lit.get_key_value_engine
|
|
23
23
|
@request_info_store = Lit.get_key_value_engine
|
|
24
|
-
@hits_counter_working =
|
|
24
|
+
@hits_counter_working = Lit.hits_counter_enabled
|
|
25
25
|
@keys = nil
|
|
26
|
+
|
|
27
|
+
# current instance cache
|
|
26
28
|
@localization_object_cache = {}
|
|
27
29
|
@localization_key_object_cache = {}
|
|
30
|
+
clear_localization_cache
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
def [](key)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
value = nil
|
|
35
|
+
unless localization_cache.key?(key)
|
|
36
|
+
value = localizations[key]
|
|
37
|
+
localization_cache[key] = value
|
|
38
|
+
else
|
|
39
|
+
value = localization_cache[key]
|
|
40
|
+
end
|
|
41
|
+
update_hits_count(key) if @hits_counter_working
|
|
42
|
+
|
|
43
|
+
if Lit.store_request_info ||
|
|
44
|
+
Lit.store_request_keys
|
|
45
|
+
key_without_locale = split_key(key).last
|
|
46
|
+
store_request_info(key_without_locale)
|
|
47
|
+
update_request_keys(key_without_locale, value)
|
|
48
|
+
end
|
|
49
|
+
value
|
|
37
50
|
end
|
|
38
51
|
|
|
39
52
|
def []=(key, value)
|
|
@@ -45,7 +58,8 @@ module Lit
|
|
|
45
58
|
end
|
|
46
59
|
|
|
47
60
|
def has_key?(key)
|
|
48
|
-
|
|
61
|
+
# check for instance cache first
|
|
62
|
+
localization_cache.has_key?(key) || localizations.has_key?(key)
|
|
49
63
|
end
|
|
50
64
|
|
|
51
65
|
def sync
|
|
@@ -68,11 +82,13 @@ module Lit
|
|
|
68
82
|
localization = find_localization(locale, key_without_locale, value: value, force_array: force_array, update_value: true)
|
|
69
83
|
return localization.translation if startup_process && localization.is_changed?
|
|
70
84
|
localizations[key] = localization.translation if localization
|
|
85
|
+
localization_cache[key] = localizations[key]
|
|
71
86
|
end
|
|
72
87
|
|
|
73
88
|
def update_cache(key, value)
|
|
74
89
|
key = key.to_s
|
|
75
90
|
localizations[key] = value
|
|
91
|
+
localization_cache[key] = value
|
|
76
92
|
end
|
|
77
93
|
|
|
78
94
|
def delete_locale(key)
|
|
@@ -83,6 +99,7 @@ module Lit
|
|
|
83
99
|
delete_localization(locale, key_without_locale)
|
|
84
100
|
@localization_key_object_cache = {}
|
|
85
101
|
@localization_object_cache = {}
|
|
102
|
+
clear_localization_cache
|
|
86
103
|
end
|
|
87
104
|
|
|
88
105
|
def load_all_translations
|
|
@@ -119,6 +136,8 @@ module Lit
|
|
|
119
136
|
@locale_cache = {}
|
|
120
137
|
@localization_key_object_cache = {}
|
|
121
138
|
@localization_object_cache = {}
|
|
139
|
+
@localization_cache = {}
|
|
140
|
+
clear_localization_cache
|
|
122
141
|
end
|
|
123
142
|
|
|
124
143
|
def reset
|
|
@@ -132,7 +151,7 @@ module Lit
|
|
|
132
151
|
def find_locale(locale_key)
|
|
133
152
|
locale_key = locale_key.to_s
|
|
134
153
|
@locale_cache ||= {}
|
|
135
|
-
unless @locale_cache.key?(locale_key)
|
|
154
|
+
unless @locale_cache.key?(locale_key) && @locale_cache[locale_key].id
|
|
136
155
|
locale = Lit::Locale.where(locale: locale_key).first_or_create!
|
|
137
156
|
@locale_cache[locale_key] = locale
|
|
138
157
|
end
|
|
@@ -155,6 +174,14 @@ module Lit
|
|
|
155
174
|
@hits_counter_working = true
|
|
156
175
|
end
|
|
157
176
|
|
|
177
|
+
def localization_cache
|
|
178
|
+
Thread.current[:lit_thread_cache] ||= {}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def clear_localization_cache
|
|
182
|
+
Thread.current[:lit_thread_cache] = {}
|
|
183
|
+
end
|
|
184
|
+
|
|
158
185
|
private
|
|
159
186
|
|
|
160
187
|
def localizations
|
|
@@ -309,6 +336,7 @@ module Lit
|
|
|
309
336
|
|
|
310
337
|
def update_hits_count(key)
|
|
311
338
|
return unless @hits_counter_working
|
|
339
|
+
|
|
312
340
|
key_without_locale = split_key(key).last
|
|
313
341
|
@hits_counter.incr('hits_counter.' + key)
|
|
314
342
|
@hits_counter.incr('global_hits_counter.' + key_without_locale)
|
|
@@ -317,6 +345,7 @@ module Lit
|
|
|
317
345
|
def store_request_info(key_without_locale)
|
|
318
346
|
return unless Lit.store_request_info
|
|
319
347
|
return unless Thread.current[:lit_current_request_path].present?
|
|
348
|
+
|
|
320
349
|
info = get_request_info(key_without_locale)
|
|
321
350
|
parts = info.split(' ').push(Thread.current[:lit_current_request_path]).uniq
|
|
322
351
|
parts.shift if parts.count > 10
|
|
@@ -324,7 +353,9 @@ module Lit
|
|
|
324
353
|
end
|
|
325
354
|
|
|
326
355
|
def update_request_keys(key_without_locale, localization)
|
|
356
|
+
return unless Lit.store_request_keys
|
|
327
357
|
return if Thread.current[:lit_request_keys].nil?
|
|
358
|
+
|
|
328
359
|
Thread.current[:lit_request_keys] ||= {}
|
|
329
360
|
Thread.current[:lit_request_keys][key_without_locale] = localization
|
|
330
361
|
end
|
data/lib/lit/engine.rb
CHANGED
|
@@ -9,12 +9,6 @@ module Lit
|
|
|
9
9
|
initializer 'lit.assets.precompile' do |app|
|
|
10
10
|
app.config.assets.precompile += %w[lit/application.css lit/application.js]
|
|
11
11
|
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
|
|
17
|
-
end
|
|
18
12
|
end
|
|
19
13
|
|
|
20
14
|
initializer 'lit.reloader' do |app|
|
|
@@ -30,5 +24,11 @@ module Lit
|
|
|
30
24
|
end
|
|
31
25
|
end
|
|
32
26
|
end
|
|
27
|
+
|
|
28
|
+
initializer :append_before_action do
|
|
29
|
+
ActionController::Base.send :before_action do
|
|
30
|
+
Thread.current[:lit_thread_cache] = {}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
33
|
end
|
|
34
34
|
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 =
|
|
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
|
data/lib/lit/i18n_backend.rb
CHANGED
|
@@ -69,11 +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
84
|
|
|
76
|
-
if content.nil? && should_cache?(key_with_locale, options)
|
|
85
|
+
if content.nil? && should_cache?(key_with_locale, options, had_key)
|
|
77
86
|
new_content = @cache.init_key_with_value(key_with_locale, content)
|
|
78
87
|
content = new_content if content.nil? # Content can change when Lit.humanize is true for example
|
|
79
88
|
# so there is no content in cache - it might not be if ie. we're doing
|
|
@@ -107,7 +116,7 @@ module Lit
|
|
|
107
116
|
# it anyway if we return nil, but then it will wrap it also in
|
|
108
117
|
# translation_missing span.
|
|
109
118
|
# Humanizing key should be last resort
|
|
110
|
-
if content.nil? && Lit.humanize_key &&
|
|
119
|
+
if content.nil? && Lit.humanize_key && Lit.humanize_key_ignored.match(key).nil?
|
|
111
120
|
content = key.to_s.split('.').last.humanize
|
|
112
121
|
if content.present?
|
|
113
122
|
@cache[key_with_locale] = content
|
|
@@ -130,7 +139,7 @@ module Lit
|
|
|
130
139
|
# end
|
|
131
140
|
elsif data.respond_to?(:to_str) || data.is_a?(Array)
|
|
132
141
|
key = ([locale] + scope).join('.')
|
|
133
|
-
return if startup_process && @cache.keys.member?(key)
|
|
142
|
+
return if startup_process && Lit.ignore_yaml_on_startup && (Thread.current[:lit_cache_keys] || @cache.keys).member?(key)
|
|
134
143
|
@cache.update_locale(key, data, data.is_a?(Array), startup_process)
|
|
135
144
|
elsif data.nil?
|
|
136
145
|
return if startup_process
|
|
@@ -140,11 +149,13 @@ module Lit
|
|
|
140
149
|
end
|
|
141
150
|
|
|
142
151
|
def load_translations_to_cache
|
|
152
|
+
Thread.current[:lit_cache_keys] = @cache.keys
|
|
143
153
|
ActiveRecord::Base.transaction do
|
|
144
154
|
(@translations || {}).each do |locale, data|
|
|
145
155
|
store_item(locale, data, [], true) if valid_locale?(locale)
|
|
146
156
|
end
|
|
147
157
|
end
|
|
158
|
+
Thread.current[:lit_cache_keys] = nil
|
|
148
159
|
end
|
|
149
160
|
|
|
150
161
|
def init_translations
|
|
@@ -156,7 +167,7 @@ module Lit
|
|
|
156
167
|
# load translations from database to cache
|
|
157
168
|
@cache.load_all_translations
|
|
158
169
|
# load translations from @translations to cache
|
|
159
|
-
load_translations_to_cache
|
|
170
|
+
load_translations_to_cache unless Lit.ignore_yaml_on_startup
|
|
160
171
|
@initialized = true
|
|
161
172
|
end
|
|
162
173
|
|
|
@@ -180,8 +191,9 @@ module Lit
|
|
|
180
191
|
Lit.ignored_keys.any?{ |k| key_without_locale.start_with?(k) }
|
|
181
192
|
end
|
|
182
193
|
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
185
197
|
return false unless options[:default] && !options[:default].is_a?(Array)
|
|
186
198
|
end
|
|
187
199
|
|
data/lib/lit/import.rb
CHANGED
|
@@ -136,17 +136,20 @@ module Lit
|
|
|
136
136
|
# is the array
|
|
137
137
|
val = value.is_a?(Array) ? [value] : value
|
|
138
138
|
I18n.t(key, default: val)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
|
|
140
|
+
# this indicates that this translation already exists
|
|
141
|
+
existing_translation =
|
|
142
|
+
Lit::Localization.joins(:locale, :localization_key)
|
|
143
|
+
.find_by('localization_key = ? and locale = ?', key, locale)
|
|
144
|
+
|
|
145
|
+
return unless existing_translation
|
|
146
|
+
|
|
147
|
+
if @raw
|
|
148
|
+
existing_translation.update(default_value: value)
|
|
149
|
+
else
|
|
150
|
+
existing_translation.update(translated_value: value, is_changed: true)
|
|
151
|
+
lkey = existing_translation.localization_key
|
|
152
|
+
lkey.update(is_deleted: false) if lkey.is_deleted
|
|
150
153
|
end
|
|
151
154
|
end
|
|
152
155
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lit
|
|
4
|
+
# Converts flat hash with localization_keys as keys and translations as values to nested hash
|
|
5
|
+
# by nesting on '.' localization key dots
|
|
6
|
+
class LocalizationKeysToHashService
|
|
7
|
+
# http://subtech.g.hatena.ne.jp/cho45/20061122
|
|
8
|
+
def self.call(db_localizations)
|
|
9
|
+
deep_proc = proc do |_k, s, o|
|
|
10
|
+
next s.merge(o, &deep_proc) if s.is_a?(Hash) && o.is_a?(Hash)
|
|
11
|
+
|
|
12
|
+
next o
|
|
13
|
+
end
|
|
14
|
+
nested_keys = {}
|
|
15
|
+
db_localizations.sort.each do |k, v|
|
|
16
|
+
key_parts = k.to_s.split('.')
|
|
17
|
+
converted = key_parts.reverse.reduce(v) { |a, n| { n => a } }
|
|
18
|
+
nested_keys.merge!(converted, &deep_proc)
|
|
19
|
+
end
|
|
20
|
+
nested_keys
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/lit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maciej Litwiniuk
|
|
8
8
|
- Piotr Boniecki
|
|
9
9
|
- Michał Buszkiewicz
|
|
10
10
|
- Szymon Soppa
|
|
11
|
-
autorequire:
|
|
11
|
+
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date:
|
|
14
|
+
date: 2020-11-03 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: rails
|
|
@@ -103,14 +103,14 @@ dependencies:
|
|
|
103
103
|
requirements:
|
|
104
104
|
- - "~>"
|
|
105
105
|
- !ruby/object:Gem::Version
|
|
106
|
-
version: 4.
|
|
106
|
+
version: 4.7.1
|
|
107
107
|
type: :development
|
|
108
108
|
prerelease: false
|
|
109
109
|
version_requirements: !ruby/object:Gem::Requirement
|
|
110
110
|
requirements:
|
|
111
111
|
- - "~>"
|
|
112
112
|
- !ruby/object:Gem::Version
|
|
113
|
-
version: 4.
|
|
113
|
+
version: 4.7.1
|
|
114
114
|
- !ruby/object:Gem::Dependency
|
|
115
115
|
name: google-cloud-translate
|
|
116
116
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -159,14 +159,14 @@ dependencies:
|
|
|
159
159
|
requirements:
|
|
160
160
|
- - "~>"
|
|
161
161
|
- !ruby/object:Gem::Version
|
|
162
|
-
version: 3.
|
|
162
|
+
version: 3.9.0
|
|
163
163
|
type: :development
|
|
164
164
|
prerelease: false
|
|
165
165
|
version_requirements: !ruby/object:Gem::Requirement
|
|
166
166
|
requirements:
|
|
167
167
|
- - "~>"
|
|
168
168
|
- !ruby/object:Gem::Version
|
|
169
|
-
version: 3.
|
|
169
|
+
version: 3.9.0
|
|
170
170
|
- !ruby/object:Gem::Dependency
|
|
171
171
|
name: vcr
|
|
172
172
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -210,6 +210,7 @@ files:
|
|
|
210
210
|
- app/assets/images/lit/jquery-te.png
|
|
211
211
|
- app/assets/javascripts/lit/application.js
|
|
212
212
|
- app/assets/javascripts/lit/backend/bootstrap.js.coffee
|
|
213
|
+
- app/assets/javascripts/lit/backend/bootstrap.min.js
|
|
213
214
|
- app/assets/javascripts/lit/backend/dashboard.js
|
|
214
215
|
- app/assets/javascripts/lit/backend/jquery-te-1.4.0.min.js
|
|
215
216
|
- app/assets/javascripts/lit/backend/localizations.js.coffee
|
|
@@ -320,13 +321,14 @@ files:
|
|
|
320
321
|
- lib/lit/loader.rb
|
|
321
322
|
- lib/lit/rails.rb
|
|
322
323
|
- lib/lit/railtie.rb
|
|
324
|
+
- lib/lit/services/localization_keys_to_hash_service.rb
|
|
323
325
|
- lib/lit/version.rb
|
|
324
326
|
- lib/tasks/lit_tasks.rake
|
|
325
327
|
homepage: https://github.com/prograils/lit
|
|
326
328
|
licenses:
|
|
327
329
|
- MIT
|
|
328
330
|
metadata: {}
|
|
329
|
-
post_install_message:
|
|
331
|
+
post_install_message:
|
|
330
332
|
rdoc_options: []
|
|
331
333
|
require_paths:
|
|
332
334
|
- lib
|
|
@@ -341,8 +343,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
341
343
|
- !ruby/object:Gem::Version
|
|
342
344
|
version: '0'
|
|
343
345
|
requirements: []
|
|
344
|
-
rubygems_version: 3.
|
|
345
|
-
signing_key:
|
|
346
|
+
rubygems_version: 3.1.2
|
|
347
|
+
signing_key:
|
|
346
348
|
specification_version: 4
|
|
347
349
|
summary: Database powered i18n backend with web gui
|
|
348
350
|
test_files: []
|