i18n_backend_database 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/LICENSE +29 -0
  2. data/README.textile +125 -0
  3. data/data/locales.yml +4 -0
  4. data/generators/i18n_backend_database/i18n_backend_database_generator.rb +8 -0
  5. data/generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb +25 -0
  6. data/init.rb +1 -0
  7. data/lib/controllers/locales_controller.rb +86 -0
  8. data/lib/controllers/translations_controller.rb +141 -0
  9. data/lib/ext/i18n.rb +68 -0
  10. data/lib/google_language.rb +33 -0
  11. data/lib/i18n_backend_database.rb +9 -0
  12. data/lib/i18n_backend_database/database.rb +263 -0
  13. data/lib/i18n_util.rb +148 -0
  14. data/lib/models/locale.rb +67 -0
  15. data/lib/models/translation.rb +46 -0
  16. data/lib/models/translation_option.rb +26 -0
  17. data/lib/public/images/custom1_bar.gif +0 -0
  18. data/lib/public/images/custom1_box.gif +0 -0
  19. data/lib/public/images/percentImage.png +0 -0
  20. data/lib/public/images/percentImage_back.png +0 -0
  21. data/lib/public/images/percentImage_back1.png +0 -0
  22. data/lib/public/images/percentImage_back2.png +0 -0
  23. data/lib/public/images/percentImage_back3.png +0 -0
  24. data/lib/public/images/percentImage_back4.png +0 -0
  25. data/lib/public/javascripts/jsProgressBarHandler.js +509 -0
  26. data/lib/routing.rb +15 -0
  27. data/lib/views/layouts/translations.html.erb +23 -0
  28. data/lib/views/locales/edit.html.erb +20 -0
  29. data/lib/views/locales/index.html.erb +22 -0
  30. data/lib/views/locales/new.html.erb +19 -0
  31. data/lib/views/locales/show.html.erb +14 -0
  32. data/lib/views/translations/asset_translations.html.erb +32 -0
  33. data/lib/views/translations/edit.html.erb +35 -0
  34. data/lib/views/translations/index.html.erb +27 -0
  35. data/lib/views/translations/new.html.erb +19 -0
  36. data/lib/views/translations/show.html.erb +32 -0
  37. data/lib/views/translations/translations.html.erb +28 -0
  38. data/lib/views/translations/update.rjs +7 -0
  39. data/routes.rb +3 -0
  40. data/spec/assets/public/es/favicons/favicon1.gif +0 -0
  41. data/spec/assets/public/es/images/icons/icon1.gif +0 -0
  42. data/spec/assets/public/es/images/image1.gif +0 -0
  43. data/spec/assets/public/favicons/favicon1.gif +0 -0
  44. data/spec/assets/public/favicons/favicon2.gif +0 -0
  45. data/spec/assets/public/images/icons/icon1.gif +0 -0
  46. data/spec/assets/public/images/icons/icon2.gif +0 -0
  47. data/spec/assets/public/images/image1.gif +0 -0
  48. data/spec/assets/public/images/image2.gif +0 -0
  49. data/spec/assets/public/images/promo/sfc08_140x400_3.gif +0 -0
  50. data/spec/assets/views/test_view1.html.erb +1 -0
  51. data/spec/assets/views/test_view2.html.erb +30 -0
  52. data/spec/caching_spec.rb +87 -0
  53. data/spec/controllers/locales_controller_spec.rb +173 -0
  54. data/spec/controllers/locales_routing_spec.rb +59 -0
  55. data/spec/controllers/translations_controller_spec.rb +189 -0
  56. data/spec/controllers/translations_routing_spec.rb +59 -0
  57. data/spec/database_spec.rb +199 -0
  58. data/spec/i18n_ext_spec.rb +40 -0
  59. data/spec/localize_spec.rb +66 -0
  60. data/spec/localize_text_spec.rb +76 -0
  61. data/spec/models/locale_spec.rb +111 -0
  62. data/spec/models/translation_spec.rb +44 -0
  63. data/spec/spec_helper.rb +16 -0
  64. data/spec/translate_asset_spec.rb +57 -0
  65. data/spec/translate_spec.rb +546 -0
  66. data/tasks/i18n.rake +60 -0
  67. metadata +155 -0
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ * Copyright (c) 2008, 2009
2
+ * ELC Technologies. All rights reserved.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions
6
+ * are met:
7
+ * 1. Redistributions of source code must retain the above copyright
8
+ * notice, this list of conditions and the following disclaimer.
9
+ * 2. Redistributions in binary form must reproduce the above copyright
10
+ * notice, this list of conditions and the following disclaimer in the
11
+ * documentation and/or other materials provided with the distribution.
12
+ * 3. All advertising materials mentioning features or use of this software
13
+ * must display the following acknowledgement:
14
+ * This product includes software developed by ELC Technologies
15
+ * 4. Neither the name of the Company nor the names of its contributors
16
+ * may be used to endorse or promote products derived from this software
17
+ * without specific prior written permission.
18
+ *
19
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29
+ * SUCH DAMAGE.
data/README.textile ADDED
@@ -0,0 +1,125 @@
1
+ h1. A Database Backend For Rails I18N
2
+
3
+ Stores your translations in the database, rather than yaml files. As you tag items with i18n.t() throughout your code base, all untranslated items are marked and added to the database. An admin panel is provided so translators can quickly translate untranslated text. All lookups occur in a cache store of your choice prior to hitting the database.
4
+
5
+ h2. DISCLAIMER!
6
+
7
+ * -In implementing this into another project, I realized that the currency support is currently broken. It basically boils down to the i18n gem accessing some of the Rails i18n yaml's in a different way than others. In this case, it's requesting a parent key, and expects a hash to be returned containing the children. This is not baked in the plugin at the moment, and any contributions toward this would be well received.- Thank you wlorentson!
8
+
9
+ * The translations_controller is unprotected, and you'll probably want to add some kind of authorization filter to it to make sure the outside world can't access it.
10
+
11
+ h2. Installation
12
+
13
+ <pre>
14
+ <code>
15
+ script/generate i18n_backend_database # add migration
16
+ rake db:migrate # migrate
17
+
18
+ rake i18n:populate:load_default_locales # populate default locales
19
+ rake i18n:populate:from_rails # populate the locales and translations tables from all Rails Locale YAML files.
20
+ rake i18n:populate:from_application # populate the translation tables from translation calls within the application.
21
+ rake i18n:populate:synchronize_translations # create non-default locale translation records from default locale translations.
22
+ rake i18n:populate:all # run all populate tasks.
23
+ rake i18n:translate:google # translate all untranslated string values using Google Language Translation API.
24
+ </code>
25
+ </pre>
26
+
27
+
28
+ In config/initialisers/i18n.rb
29
+
30
+ <pre>
31
+ <code>
32
+ I18n.backend = I18n::Backend::Database.new # registers the backend
33
+ I18n.backend.cache_store = :memory_store # optional: specify an alternate cache store
34
+ I18n.backend.localize_text_tag = '##' # optional: specify an alternate localize text tag, the default is ^^
35
+ </code>
36
+ </pre>
37
+
38
+ In config/routes.rb to register admin panel routes
39
+
40
+ <pre>
41
+ <code>
42
+ map.from_plugin 'i18n_backend_database'
43
+ </code>
44
+ </pre>
45
+
46
+ h2. Use
47
+
48
+ All non-user generated text provided by the application needs to be wrapped in a call to I18n.t().
49
+ <pre>
50
+ <code>
51
+ I18n.t("Hello there!")
52
+ </code>
53
+ </pre>
54
+
55
+ Interpolation is handled by passing in key/value pairs as a value to an interpolation tag ( {{ }} ).
56
+ <pre>
57
+ <code>
58
+ I18n.t("Hello there {{name}}!", :name => "Dylan")
59
+ </code>
60
+ </pre><pre>
61
+ <code>
62
+ I18n.t("Click {{here}} or {{there}}", :here => "link_to(I18n.t('midnite'), croix_path)", :there => "link_to(I18n.t('staten_island'), wu_path)")
63
+ </code>
64
+ </pre>
65
+
66
+ Pluralization is handled by passing in a "count" key value pair, which is a unique interpolation value.
67
+ <pre>
68
+ <code>
69
+ I18n.t("You are {{count}} years old", :count => 100)
70
+ </code>
71
+ </pre>
72
+
73
+ Links to external documents that need to be translated should be tagged as well.
74
+ <pre>
75
+ <code>
76
+ I18n.t('http://www.elctech.com/core')
77
+ </code>
78
+ </pre>
79
+
80
+ All fragment cache view blocks need to have their keys prepended with the current locale.
81
+ <pre>
82
+ <code>
83
+ cache("#{I18n.locale}-footer_#{controller.action_name}")
84
+ </code>
85
+ </pre>
86
+
87
+ Date/Time localization is handled by using the I18n.l method. The format used will be :default (see next item for explanation).
88
+ <pre>
89
+ <code>
90
+ I18n.l(@user.joined_at)
91
+ </code>
92
+ </pre>
93
+
94
+ Date/Time localization can take a format parameter that corresponds to a key in the translations table (the Rails defaults :default, :short, and :long are available). We could in theory create our own like en.date.formats.espn_default.
95
+ <pre>
96
+ <code>
97
+ I18n.l(@user.joined_at, :format => :default)
98
+ I18n.l(@user.joined_at, :format => :short)
99
+ I18n.l(@user.joined_at, :format => :espn_default)
100
+ </code>
101
+ </pre>
102
+
103
+ Date/Time localization can take a custom format string as well.
104
+ <pre>
105
+ <code>
106
+ I18n.l(@user.joined_at, :format => "%B %e, %Y")
107
+ </code>
108
+ </pre>
109
+
110
+ Text stored in a database can be localized by tagging the text being stored and then localizing in the view etc.
111
+ <pre>
112
+ <code>
113
+ I18n.tlt("is now friends with") => "^^is now friends with^^"
114
+ I18n.lt("shane ^^is now friends with^^ dylan") => "shane ahora es con amigos dylan"
115
+ </code>
116
+ </pre>
117
+
118
+ Images can be translated with the I18n.ta tag
119
+ <pre>
120
+ <code>
121
+ <%= image_tag(I18n.ta("logos/elc.gif"), :size => "134x75") %>
122
+ </code>
123
+ </pre>
124
+ In this example, for a locale es, there should be an image: public/es/images/logos/elc.gif
125
+
data/data/locales.yml ADDED
@@ -0,0 +1,4 @@
1
+ en:
2
+ name: English
3
+ es:
4
+ name: Spanish
@@ -0,0 +1,8 @@
1
+ class I18nBackendDatabaseGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record { |m|
4
+ m.migration_template "migrate/create_i18n_tables.rb", "db/migrate",
5
+ :migration_file_name => "create_i18n_tables"
6
+ }
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ class CreateI18nTables < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :locales do |t|
4
+ t.string :code
5
+ t.string :name
6
+ end
7
+ add_index :locales, :code
8
+
9
+ create_table :translations do |t|
10
+ t.string :key
11
+ t.text :raw_key
12
+ t.text :value
13
+ t.integer :pluralization_index, :default => 1
14
+ t.integer :locale_id
15
+ end
16
+ add_index :translations, [:locale_id, :key, :pluralization_index]
17
+
18
+ end
19
+
20
+ def self.down
21
+ drop_table :locales
22
+ drop_table :translations
23
+ drop_table :asset_translations
24
+ end
25
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'i18n_backend_database'
@@ -0,0 +1,86 @@
1
+ class LocalesController < ActionController::Base
2
+ prepend_view_path(File.join(File.dirname(__FILE__), "..", "views"))
3
+ # GET /locales
4
+ # GET /locales.xml
5
+ def index
6
+ @locales = Locale.find(:all)
7
+
8
+ respond_to do |format|
9
+ format.html # index.html.erb
10
+ format.xml { render :xml => @locales }
11
+ end
12
+ end
13
+
14
+ # GET /locales/1
15
+ # GET /locales/1.xml
16
+ def show
17
+ @locale = Locale.find_by_code(params[:id])
18
+
19
+ respond_to do |format|
20
+ format.html # show.html.erb
21
+ format.xml { render :xml => @locale }
22
+ end
23
+ end
24
+
25
+ # GET /locales/new
26
+ # GET /locales/new.xml
27
+ def new
28
+ @locale = Locale.new
29
+
30
+ respond_to do |format|
31
+ format.html # new.html.erb
32
+ format.xml { render :xml => @locale }
33
+ end
34
+ end
35
+
36
+ # GET /locales/1/edit
37
+ def edit
38
+ @locale = Locale.find_by_code(params[:id])
39
+ end
40
+
41
+ # POST /locales
42
+ # POST /locales.xml
43
+ def create
44
+ @locale = Locale.new(params[:locale])
45
+
46
+ respond_to do |format|
47
+ if @locale.save
48
+ flash[:notice] = 'Locale was successfully created.'
49
+ format.html { redirect_to(@locale) }
50
+ format.xml { render :xml => @locale, :status => :created, :location => @locale }
51
+ else
52
+ format.html { render :action => "new" }
53
+ format.xml { render :xml => @locale.errors, :status => :unprocessable_entity }
54
+ end
55
+ end
56
+ end
57
+
58
+ # PUT /locales/1
59
+ # PUT /locales/1.xml
60
+ def update
61
+ @locale = Locale.find_by_code(params[:id])
62
+
63
+ respond_to do |format|
64
+ if @locale.update_attributes(params[:locale])
65
+ flash[:notice] = 'Locale was successfully updated.'
66
+ format.html { redirect_to(@locale) }
67
+ format.xml { head :ok }
68
+ else
69
+ format.html { render :action => "edit" }
70
+ format.xml { render :xml => @locale.errors, :status => :unprocessable_entity }
71
+ end
72
+ end
73
+ end
74
+
75
+ # DELETE /locales/1
76
+ # DELETE /locales/1.xml
77
+ def destroy
78
+ @locale = Locale.find_by_code(params[:id])
79
+ @locale.destroy
80
+
81
+ respond_to do |format|
82
+ format.html { redirect_to(locales_url) }
83
+ format.xml { head :ok }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,141 @@
1
+ class TranslationsController < ActionController::Base
2
+ prepend_view_path(File.join(File.dirname(__FILE__), "..", "views"))
3
+ layout 'translations'
4
+ before_filter :find_locale
5
+
6
+ ## FIXME: you'll probably want add authorization to this controller!
7
+
8
+ # GET /translations
9
+ # GET /translations.xml
10
+ def index
11
+ @translations = @locale.translations.find(:all, :order => "raw_key, pluralization_index")
12
+
13
+ respond_to do |format|
14
+ format.html # index.html.erb
15
+ format.xml { render :xml => @translations }
16
+ end
17
+ end
18
+
19
+ # GET /translations
20
+ # GET /translations.xml
21
+ def translations
22
+ @locale ||= Locale.default_locale
23
+ @translation_option = TranslationOption.find(params[:translation_option])
24
+
25
+ if @translation_option == TranslationOption.translated
26
+ @translations = @locale.translations.translated
27
+ else
28
+ @translations = @locale.translations.untranslated
29
+ end
30
+
31
+ respond_to do |format|
32
+ format.html # index.html.erb
33
+ format.xml { render :xml => @translations }
34
+ end
35
+ end
36
+
37
+ # GET /asset_translations
38
+ # GET /asset_translations.xml
39
+ def asset_translations
40
+ @locale ||= Locale.default_locale
41
+ @translation_option = TranslationOption.find(params[:translation_option])
42
+
43
+ @asset_translations = I18n.asset_translations
44
+ @untranslated_assets = I18n.untranslated_assets(@locale.code)
45
+ @percentage_translated = (((@asset_translations.size - @untranslated_assets.size).to_f / @asset_translations.size.to_f * 100).round) rescue 0
46
+
47
+ if @translation_option == TranslationOption.translated
48
+ @asset_translations = @asset_translations.reject{|e| @untranslated_assets.include?(e)}
49
+ else
50
+ @asset_translations = @untranslated_assets
51
+ end
52
+
53
+ respond_to do |format|
54
+ format.html # index.html.erb
55
+ format.xml { render :xml => @untranslated_assets }
56
+ end
57
+ end
58
+
59
+ # GET /translations/1
60
+ # GET /translations/1.xml
61
+ def show
62
+ @translation = @locale.translations.find(params[:id])
63
+
64
+ respond_to do |format|
65
+ format.html # show.html.erb
66
+ format.xml { render :xml => @translation }
67
+ end
68
+ end
69
+
70
+ # GET /translations/new
71
+ # GET /translations/new.xml
72
+ def new
73
+ @translation = Translation.new
74
+
75
+ respond_to do |format|
76
+ format.html # new.html.erb
77
+ format.xml { render :xml => @translation }
78
+ end
79
+ end
80
+
81
+ # GET /translations/1/edit
82
+ def edit
83
+ @translation = @locale.translations.find(params[:id])
84
+ end
85
+
86
+ # POST /translations
87
+ # POST /translations.xml
88
+ def create
89
+ @translation = @locale.translations.build(params[:translation])
90
+
91
+ respond_to do |format|
92
+ if @translation.save
93
+ flash[:notice] = 'Translation was successfully created.'
94
+ format.html { redirect_to locale_translation_path(@locale, @translation) }
95
+ format.xml { render :xml => @translation, :status => :created, :location => @translation }
96
+ else
97
+ format.html { render :action => "new" }
98
+ format.xml { render :xml => @translation.errors, :status => :unprocessable_entity }
99
+ end
100
+ end
101
+ end
102
+
103
+ # PUT /translations/1
104
+ # PUT /translations/1.xml
105
+ def update
106
+ @translation = @locale.translations.find(params[:id])
107
+ @first_time_translating = @translation.value.nil?
108
+
109
+ respond_to do |format|
110
+ if @translation.update_attributes(params[:translation])
111
+ format.html do
112
+ flash[:notice] = 'Translation was successfully updated.'
113
+ redirect_to locale_translation_path(@locale, @translation)
114
+ end
115
+ format.xml { head :ok }
116
+ format.js {}
117
+ else
118
+ format.html { render :action => "edit" }
119
+ format.xml { render :xml => @translation.errors, :status => :unprocessable_entity }
120
+ end
121
+ end
122
+ end
123
+
124
+ # DELETE /translations/1
125
+ # DELETE /translations/1.xml
126
+ def destroy
127
+ @translation = @locale.translations.find(params[:id])
128
+ @translation.destroy
129
+
130
+ respond_to do |format|
131
+ format.html { redirect_to(locale_translations_url) }
132
+ format.xml { head :ok }
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def find_locale
139
+ @locale = Locale.find_by_code(params[:locale_id])
140
+ end
141
+ end
data/lib/ext/i18n.rb ADDED
@@ -0,0 +1,68 @@
1
+ module I18n
2
+ APP_DIRECTORY = 'app/views'
3
+
4
+ class << self
5
+
6
+ def locale_segment
7
+ I18n.locale.to_s == I18n.default_locale.to_s ? "" : "/#{I18n.locale}"
8
+ end
9
+ alias :ls :locale_segment
10
+
11
+ def localize_text(text, options = {})
12
+ locale = options[:locale] || I18n.locale
13
+ backend.localize_text(locale, text)
14
+ end
15
+ alias :lt :localize_text
16
+
17
+ def tag_localized_text(text)
18
+ backend.localize_text_tag + text + backend.localize_text_tag
19
+ end
20
+ alias :tlt :tag_localized_text
21
+
22
+ def translate_asset(asset, options={})
23
+ locale = options.delete(:locale) || I18n.locale
24
+ if locale_asset = locale_asset(asset, locale)
25
+ locale_asset
26
+ else
27
+ asset
28
+ end
29
+ end
30
+ alias ta translate_asset
31
+
32
+ def untranslated_assets(locale)
33
+ return [] if locale.to_s == I18n.default_locale.to_s #default locale assets are assumed to exist
34
+ assets = asset_translations
35
+ assets.reject! {|asset| locale_asset_exists?(locale, asset) }
36
+ assets
37
+ end
38
+
39
+ def asset_translations(dir=APP_DIRECTORY)
40
+ assets = []
41
+ Dir.glob("#{dir}/*").each do |item|
42
+ if File.directory?(item)
43
+ assets += asset_translations(item)
44
+ else
45
+ File.readlines(item).each do |l|
46
+ assets += l.scan(/I18n.ta\(["'](.*?)["']\)/).flatten
47
+ end
48
+ end
49
+ end
50
+ assets.uniq
51
+ end
52
+
53
+ protected
54
+
55
+ def locale_asset_exists?(locale, asset)
56
+ File.exists?("#{ActionView::Helpers::AssetTagHelper::ASSETS_DIR}/#{locale}#{asset_path(asset)}")
57
+ end
58
+
59
+ def locale_asset(asset, locale)
60
+ locale_asset_exists?(locale, asset) ? "/#{locale}#{asset_path(asset)}" : nil
61
+ end
62
+
63
+ def asset_path(asset)
64
+ asset[0] == ?/ ? asset : "/images/#{asset}"
65
+ end
66
+
67
+ end
68
+ end