lolita-i18n 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.document +4 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +24 -0
  4. data/Gemfile.lock +165 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +27 -0
  7. data/Rakefile +46 -0
  8. data/VERSION +1 -0
  9. data/app/assets/images/lolita/i18n/google_translate_icon.png +0 -0
  10. data/app/assets/javascripts/lolita/i18n/application.js +2 -0
  11. data/app/assets/javascripts/lolita/i18n/i18n.js.coffee +135 -0
  12. data/app/assets/javascripts/lolita/i18n/spin.min.js +2 -0
  13. data/app/assets/stylesheets/lolita/i18n/application.scss +102 -0
  14. data/app/controllers/lolita/i18n_controller.rb +39 -0
  15. data/app/helpers/lolita/i18n_helper.rb +25 -0
  16. data/app/views/lolita/i18n/index.html.haml +34 -0
  17. data/config/locales/en.yml +5 -0
  18. data/config/locales/lv.yml +4 -0
  19. data/config/routes.rb +11 -0
  20. data/lib/lolita-i18n.rb +81 -0
  21. data/lib/lolita-i18n/backend.rb +69 -0
  22. data/lib/lolita-i18n/google_translate.rb +76 -0
  23. data/lib/lolita-i18n/module.rb +1 -0
  24. data/lib/lolita-i18n/rails.rb +6 -0
  25. data/lolita-i18n.gemspec +110 -0
  26. data/spec/controllers/lolita/i18n_controller_spec.rb +39 -0
  27. data/spec/lolita-i18n/backend_spec.rb +21 -0
  28. data/spec/lolita-i18n/google_translate_spec.rb +31 -0
  29. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  30. data/spec/rails_app/config/application.rb +18 -0
  31. data/spec/rails_app/config/enviroment.rb +5 -0
  32. data/spec/rails_app/config/initializers/lolita_i18n.rb +15 -0
  33. data/spec/rails_app/config/locales/en.yml +9 -0
  34. data/spec/rails_app/config/locales/lv.yml +2 -0
  35. data/spec/rails_app/config/routes.rb +3 -0
  36. data/spec/spec_helper.rb +33 -0
  37. metadata +197 -0
@@ -0,0 +1,102 @@
1
+ /*
2
+ *= require_self
3
+ */
4
+ .clear { clear: both; height: 0; }
5
+
6
+ div.i18n-box div.list {
7
+ background-color: white;
8
+ overflow: none;
9
+ }
10
+ .i18n-language-switch{
11
+ overflow: hidden;
12
+ list-style: none;
13
+ }
14
+ .i18n-language-switch li{
15
+ float:left;
16
+ margin-left:10px;
17
+ cursor:pointer;
18
+ }
19
+ .i18n-language-switch li a {
20
+ color: #C3C3C3;
21
+ }
22
+ .i18n-language-switch li.active a{
23
+ color: #444;
24
+ font-weight: bold;
25
+ cursor:default;
26
+ }
27
+ span.hint {
28
+ font-size: 11px;
29
+ color: #666;
30
+ display: block;
31
+ }
32
+ #i18n_edit textarea {
33
+ height: 100px;
34
+ }
35
+ div.list div.actions {
36
+ padding: 10px;
37
+ .filters {
38
+ float: left;
39
+ position: relative;
40
+ line-height: 35px;
41
+
42
+ input {
43
+ position: absolute;
44
+ top: 11px;
45
+ }
46
+ label {
47
+ padding-top: 10px;
48
+ padding-left: 20px;
49
+ }
50
+ }
51
+ button.translate {
52
+ position: relative;
53
+ padding-left: 30px;
54
+ height: 34px;
55
+ background-image: url(image-path('lolita/i18n/google_translate_icon.png'));
56
+ background-position: 5px 5px;
57
+ background-repeat: no-repeat;
58
+ box-shadow: inset 0 0 1px 1px #eaeaea;
59
+ text-shadow: 0 1px 0 white;
60
+ border: 1px solid #bbb;
61
+ float: right;
62
+ }
63
+ button.loading {
64
+ background-image: none;
65
+ color: #999;
66
+ }
67
+ }
68
+ div.list table {
69
+ table-layout: fixed;
70
+ clear: both;
71
+ td span.unapproved {
72
+ color: red;
73
+ }
74
+ tr td:first-child p {
75
+ min-height: 40px;
76
+ }
77
+ td textarea {
78
+ border: 1px solid #cdcdcd;
79
+ -moz-border-radius: 3px;
80
+ -webkit-border-radius: 3px;
81
+ border-radius: 3px;
82
+ -moz-box-sizing: border-box;
83
+ -webkit-box-sizing: border-box;
84
+ box-sizing: border-box;
85
+ padding: 3px 10px;
86
+ width: 100%;
87
+ min-height: 50px;
88
+ color: #494949;
89
+ }
90
+ td, th {
91
+ white-space: normal;
92
+ padding: 5px 20px;
93
+ line-height: 20px;
94
+ }
95
+ thead th {
96
+ line-height: 30px;
97
+ select {
98
+ float: right;
99
+ margin-top: 5px;
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,39 @@
1
+ class Lolita::I18nController < ApplicationController
2
+ include Lolita::Controllers::UserHelpers
3
+ before_filter :authenticate_lolita_user!, :set_current_locale
4
+
5
+ layout "lolita/application"
6
+
7
+ def index
8
+ @translation_keys=Lolita::I18n.flatten_keys
9
+ end
10
+
11
+ def update
12
+ respond_to do |format|
13
+ format.json do
14
+ render :nothing => true, :json => {error: !Lolita::I18n::Backend.set(params[:id],params[:translation])}
15
+ end
16
+ end
17
+ end
18
+
19
+ def translate_untranslated
20
+ respond_to do |format|
21
+ format.json do
22
+ google_translate = Lolita::I18n::GoogleTranslate.new @active_locale
23
+ google_translate.run
24
+ render :nothing => true, :status => 200, :json => {errors: google_translate.errors, :translated => google_translate.untranslated}
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def next_locale
32
+ ::I18n::available_locales.collect{|locale| locale if locale != ::I18n.default_locale}.compact.first
33
+ end
34
+
35
+ def set_current_locale
36
+ @active_locale = (params[:active_locale] || next_locale).to_sym
37
+ end
38
+
39
+ end
@@ -0,0 +1,25 @@
1
+ module Lolita
2
+ module I18nHelper
3
+ def locale_options
4
+ ::I18n::available_locales.collect{|locale|
5
+ [::I18n.t(locale, :default => locale), locale] unless [::I18n.default_locale,@active_locale].include?(locale)
6
+ }.compact.insert(0,[::I18n.t("lolita-i18n.choose-other-language", :default => "Choose other language"),""])
7
+ end
8
+
9
+ def show_translation key
10
+ value = ::I18n.t(key, :locale => @active_locale, :default => '')
11
+ unless value.blank?
12
+ value
13
+ else
14
+ value = Lolita::I18n::GoogleTranslate.get_translation(@active_locale,key)
15
+ unless value.blank?
16
+ content_tag(:span, value, :class => "unapproved")
17
+ end
18
+ end
19
+ end
20
+
21
+ def is_untranslated key
22
+ Lolita::I18n::GoogleTranslate.get_translation(@active_locale,key)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ - content_for :style do
2
+ = stylesheet_link_tag "lolita/i18n/application", :media => "screen,projection"
3
+ - content_for :script do
4
+ = javascript_include_tag "lolita/i18n/application"
5
+ .box.i18n-bo
6
+ .boxtitle
7
+ %h1.black= ::I18n.t('lolita-i18n.title', :default => "Static content translation")
8
+ .arrow
9
+ .list
10
+ %div.actions
11
+ .filters
12
+ = check_box_tag "show_untranslated", nil, params[:show_untranslated]
13
+ = label_tag "show_untranslated", "Show translations that need approval"
14
+
15
+ %button.translate{:'data-url' => translate_untranslated_lolita_i18n_index_path, :'data-locale' => @active_locale} Translate empty translations with Google
16
+ .clear
17
+ %table
18
+ %thead
19
+ %tr
20
+ %th{:style => "width:450px"}
21
+ = label_tag :active_locale, I18n.t(@active_locale, :default => @active_locale)
22
+ - if locale_options.size > 1
23
+ = select_tag :active_locale, options_for_select(locale_options), :id => "active_locale"
24
+ %th{:style => "width:90%"}= ::I18n.t(::I18n.default_locale)
25
+ %tbody
26
+ - @translation_keys.each do |key|
27
+ - if !params[:show_untranslated] or is_untranslated(key)
28
+ %tr
29
+ %td{:style => "width:450px", :'data-key' => key, :'data-locale' => @active_locale}
30
+ %p= show_translation key
31
+ %td{:style => "width:90%", :'data-key' => key, :'data-locale' => ::I18n.default_locale}
32
+ %p= ::I18n.t(key, :locale => ::I18n.default_locale, :default => "")
33
+ %span.hint
34
+ = key
@@ -0,0 +1,5 @@
1
+ en:
2
+ lolita-i18n:
3
+ title: "Static content translation"
4
+ choose-other-language: "Choose other language"
5
+
@@ -0,0 +1,4 @@
1
+ lv:
2
+ lolita-i18n:
3
+ title: "Statiskā satura tulkošana"
4
+ choose-other-language: "Izvēlies citu valodu"
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ Rails.application.routes.draw do
2
+
3
+ namespace "lolita" do
4
+ resources :i18n, :only=>[:update],:constraints=>{:id=>/.*/} do
5
+ collection do
6
+ put 'translate_untranslated'
7
+ get 'index'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,81 @@
1
+ $:<<File.dirname(__FILE__) unless $:.include?(File.dirname(__FILE__))
2
+ require 'redis'
3
+ require 'yajl'
4
+
5
+ module Lolita
6
+ # === Uses Redis DB as backend
7
+ # All translations ar stored with full key like "en.home.index.title" -> Hello world.
8
+ # Translations whitch are translated with Google translate have prefix "g" -> "g.en.home.index.title".
9
+ # These translations should be accepted/edited and approved then they will become as normal for general use.
10
+ #
11
+ # === Other stored data
12
+ # => :unapproved_keys_<locale> - a SET containing all unapproved keys from GoogleTranslate
13
+ #
14
+ module I18n
15
+ autoload :Backend, 'lolita-i18n/backend'
16
+ autoload :GoogleTranslate, 'lolita-i18n/google_translate'
17
+
18
+ # Loads given key/value engine as backend
19
+ # place this method in rails initializer lolita.rb
20
+ # === Example
21
+ #
22
+ # I18n.backend = Lolita::I18n.load Redis.new
23
+ #
24
+ def self.load store
25
+ @@store=store
26
+ @@backend=::I18n::Backend::KeyValue.new(@@store)
27
+ @@yaml_backend=::I18n.backend
28
+ ::I18n::Backend::Simple.send(:include, ::I18n::Backend::Flatten)
29
+ ::I18n::Backend::Simple.send(:include, ::I18n::Backend::Memoize)
30
+ ::I18n::Backend::Chain.new(Lolita::I18n.backend, @@yaml_backend)
31
+ end
32
+
33
+ def self.store
34
+ @@store
35
+ end
36
+
37
+ def self.backend
38
+ @@backend
39
+ end
40
+
41
+ def self.yaml_backend
42
+ @@yaml_backend
43
+ end
44
+
45
+ # returns Array of flattened keys as "home.index.title"
46
+ def self.flatten_keys
47
+ @@yaml_backend.flatten_translations(nil, @@yaml_backend.send(:translations)[::I18n.default_locale] || {}, ::I18n::Backend::Flatten::SEPARATOR_ESCAPE_CHAR, false).keys
48
+ end
49
+
50
+ end
51
+ end
52
+
53
+ module LolitaI18nConfiguration
54
+ def i18n
55
+ Lolita::I18n
56
+ end
57
+ end
58
+
59
+ Lolita.scope.extend(LolitaI18nConfiguration)
60
+
61
+ require 'lolita-i18n/module'
62
+ if Lolita.rails3?
63
+ require 'lolita-i18n/rails'
64
+ end
65
+
66
+
67
+ Lolita.after_routes_loaded do
68
+ if tree=Lolita::Navigation::Tree[:"left_side_navigation"]
69
+ unless tree.branches.detect{|b| b.title=="System"}
70
+ branch=tree.append(nil,:title=>"System")
71
+ #mapping=Lolita::Mapping.new(:i18n_index,:singular=>:i18n,:class_name=>Object,:controller=>"lolita/i18n")
72
+ branch.append(Object,:title=>"I18n",:url=>Proc.new{|view,branch|
73
+ view.send(:lolita_i18n_index_path)
74
+ }, :active=>Proc.new{|view,parent_branch,branch|
75
+ params=view.send(:params)
76
+ params[:controller].to_s.match(/lolita\/i18n/)
77
+ })
78
+ end
79
+ end
80
+ end
81
+
@@ -0,0 +1,69 @@
1
+ module Lolita
2
+ module I18n
3
+ # Allow to operate with I18n keys and values.
4
+ class Backend
5
+
6
+ class << self
7
+ # Get translation and decode it. Set translate from and to translations.
8
+ # Return Hash with
9
+ # * <tt>:key</tt> - translation key
10
+ # * <tt>:value</tt> - translation
11
+ # * <tt>:translate_to</tt> - translation locale, like <i>en</i>
12
+ # * <tt>:translate_from</tt> - translation key for default language
13
+ def get(key)
14
+ result={:key=>key}
15
+ result[:value]=decoded_value(key)
16
+ result[:original_value]=decoded_value(translate_from(key))
17
+ result[:translate_to]=translate_to(key)
18
+ result[:translate_from]=translate_from(key)
19
+ result
20
+ end
21
+
22
+ # Store translation, decode and store.
23
+ # Accept:
24
+ # * <tt>key</tt> - translation key
25
+ # * <tt>translation</tt> - String with translation
26
+ def set(key,translation)
27
+ locale=translate_to(key)
28
+ translation_key=translation_key(key)
29
+ value=Yajl::Parser.parse(translation.to_json)
30
+ if Lolita::I18n.backend.store_translations(locale,{translation_key=>value},:escape=>false)
31
+ Lolita::I18n::GoogleTranslate.del_translation locale, translation_key
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ def locale(key)
39
+ translate_to(key) || ::I18n.default_locale
40
+ end
41
+
42
+ def translation_key(key)
43
+ (key.to_s.split('.')[1..-1]).join(".")
44
+ end
45
+
46
+ private
47
+
48
+ def keys
49
+ @keys||=Lolita::I18n.flattened_translations.keys.sort
50
+ end
51
+
52
+ def decoded_value(key)
53
+ value=Lolita::I18n.backend.store[key]
54
+ value ? (Yajl::Parser.parse(value) rescue "") : ::I18n.t(key.split('.')[1..-1].join('.'), :default => "", :locale => translate_to(key))
55
+ end
56
+
57
+ def translate_to(key)
58
+ key.to_s.split(".").first
59
+ end
60
+
61
+ def translate_from(key,locale=nil)
62
+ (key.to_s.split('.')[1..-1]).insert(0,locale || ::I18n.default_locale).join(".")
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,76 @@
1
+ require 'easy_translate'
2
+
3
+ module Lolita
4
+ module I18n
5
+ # Translates untranslated strings using google translate API
6
+ class GoogleTranslate
7
+ attr_reader :untranslated, :errors
8
+ def initialize locale
9
+ @locale = locale
10
+ @untranslated = 0
11
+ @errors = []
12
+ end
13
+
14
+ def run
15
+ unless untranslated_translations.empty?
16
+ @untranslated = untranslated_translations.count
17
+ untranslated_translations.each_slice(10).to_a.each do |batch|
18
+ begin
19
+ EasyTranslate.translate(batch.map(&:last), :to => @locale).each_with_index do |result,index|
20
+ unless result.blank?
21
+ key = batch[index].first
22
+ add_translation key, result
23
+ add_to_unapproved key
24
+ end
25
+ end
26
+ rescue EasyTranslate::EasyTranslateException => e
27
+ @errors << e.to_s
28
+ rescue Exception => e
29
+ puts "#{e.to_s}\n\n#{$@.join("\n")}"
30
+ @errors << e.to_s
31
+ break
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def untranslated_translations
38
+ unless @untranslated_translations
39
+ @untranslated_translations = []
40
+ Lolita::I18n.flatten_keys.each do |key|
41
+ unless unapproved_keys.include?(key)
42
+ if !::I18n.t(key, :locale => ::I18n.default_locale, :default => '').blank? && ::I18n.t(key, :locale => @locale, :default => '').blank? && Lolita::I18n::GoogleTranslate.get_translation(@locale,key).blank?
43
+ value = ::I18n.t(key, :locale => ::I18n.default_locale, :default => '').to_s
44
+ @untranslated_translations << [key,value] unless value.blank?
45
+ end
46
+ end
47
+ end
48
+ end
49
+ @untranslated_translations
50
+ end
51
+
52
+ def self.get_translation locale, key
53
+ Lolita::I18n.store.get :"g.#{locale}.#{key}"
54
+ end
55
+
56
+ def self.del_translation locale, key
57
+ Lolita::I18n.store.del :"g.#{locale}.#{key}"
58
+ end
59
+
60
+ private
61
+
62
+ def unapproved_keys
63
+ @@unapproved_keys ||= Lolita::I18n.store.smembers(:"unapproved_keys_#{@locale}").map(&:to_sym)
64
+ end
65
+
66
+ def add_translation key, value
67
+ Lolita::I18n.store.set :"g.#{@locale}.#{key}", value
68
+ end
69
+
70
+ def add_to_unapproved key
71
+ Lolita::I18n.store.sadd(:"unapproved_keys_#{@locale}",key)
72
+ end
73
+
74
+ end
75
+ end
76
+ end