diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..49e83b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +.bundle/ +log/*.log +pkg/ diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..fa2cb37 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--format=documentation \ No newline at end of file diff --git a/.rvmrc b/.rvmrc new file mode 100644 index 0000000..f49904c --- /dev/null +++ b/.rvmrc @@ -0,0 +1 @@ +rvm use ruby-1.9.2@i18n --create \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..76af18e --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "http://rubygems.org" +gemspec + +gem 'rails','3.2.12' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..01feee5 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,113 @@ +PATH + remote: . + specs: + i18n_backend_database (0.1.0) + rails (>= 3) + +GEM + remote: http://rubygems.org/ + specs: + actionmailer (3.2.12) + actionpack (= 3.2.12) + mail (~> 2.4.4) + actionpack (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) + builder (~> 3.0.0) + erubis (~> 2.7.0) + journey (~> 1.0.4) + rack (~> 1.4.5) + rack-cache (~> 1.2) + rack-test (~> 0.6.1) + sprockets (~> 2.2.1) + activemodel (3.2.12) + activesupport (= 3.2.12) + builder (~> 3.0.0) + activerecord (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) + arel (~> 3.0.2) + tzinfo (~> 0.3.29) + activeresource (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) + activesupport (3.2.12) + i18n (~> 0.6) + multi_json (~> 1.0) + arel (3.0.2) + builder (3.0.4) + diff-lcs (1.1.3) + erubis (2.7.0) + hike (1.2.1) + i18n (0.6.1) + journey (1.0.4) + json (1.7.7) + mail (2.4.4) + i18n (>= 0.4.0) + mime-types (~> 1.16) + treetop (~> 1.4.8) + mime-types (1.21) + multi_json (1.6.0) + polyglot (0.3.3) + rack (1.4.5) + rack-cache (1.2) + rack (>= 0.4) + rack-ssl (1.3.3) + rack + rack-test (0.6.2) + rack (>= 1.0) + rails (3.2.12) + actionmailer (= 3.2.12) + actionpack (= 3.2.12) + activerecord (= 3.2.12) + activeresource (= 3.2.12) + activesupport (= 3.2.12) + bundler (~> 1.0) + railties (= 3.2.12) + railties (3.2.12) + actionpack (= 3.2.12) + activesupport (= 3.2.12) + rack-ssl (~> 1.3.2) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (>= 0.14.6, < 2.0) + rake (10.0.3) + rdoc (3.12.1) + json (~> 1.4) + rspec (2.12.0) + rspec-core (~> 2.12.0) + rspec-expectations (~> 2.12.0) + rspec-mocks (~> 2.12.0) + rspec-core (2.12.2) + rspec-expectations (2.12.1) + diff-lcs (~> 1.1.3) + rspec-mocks (2.12.2) + rspec-rails (2.12.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 2.12.0) + rspec-expectations (~> 2.12.0) + rspec-mocks (~> 2.12.0) + sprockets (2.2.2) + hike (~> 1.2) + multi_json (~> 1.0) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sqlite3 (1.3.7) + thor (0.17.0) + tilt (1.3.3) + treetop (1.4.12) + polyglot + polyglot (>= 0.3.1) + tzinfo (0.3.35) + +PLATFORMS + ruby + +DEPENDENCIES + i18n_backend_database! + rails (= 3.2.12) + rspec (>= 2.0.0) + rspec-rails (>= 2.0.0) + sqlite3 (>= 1.3.4) diff --git a/README.textile b/README.textile index fdb5ea8..ebaaab7 100644 --- a/README.textile +++ b/README.textile @@ -1,81 +1,72 @@ -h1. A Database Backend For Rails I18N +h1. A Database Backend For Rails I18N (Now for rails 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. +Stores your translations in the database, rather than yaml files. As you tag items with 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. h2. DISCLAIMER! -* -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! - * 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. h2. Installation
   
-    script/generate i18n_backend_database       # add migration
-    rake db:migrate                             # migrate
-
-    rake i18n:populate:load_default_locales     # populate default locales
-    rake i18n:populate:from_rails               # populate the locales and translations tables from all Rails Locale YAML files.
-    rake i18n:populate:from_application         # populate the translation tables from translation calls within the application.
-    rake i18n:populate:synchronize_translations # create non-default locale translation records from default locale translations.
-    rake i18n:populate:all                      # run all populate tasks.
-    rake i18n:translate:google                  # translate all untranslated string values using Google Language Translation API.
+  script/generate i18n_backend_database             # add migration
+  rake db:migrate                                   # migrate
+
+  rake i18n:populate:load_default_locales           # populate default locales
+  rake i18n:populate:from_rails                     # populate the locales and translations tables from all Rails Locale YAML files.
+  rake i18n:populate:from_application               # populate the translation tables from translation calls within the application.
+  rake i18n:populate:synchronize_translations       # create non-default locale translation records from default locale translations.
+  rake i18n:populate:all                            # run all populate tasks.
+  rake i18n:translate:google                        # translate all untranslated string values using Google Language Translation API.
+  
+  rake i18n:import_translations locale=     # Import the translations from the translations/.yml
+  rake i18n:export_translations                     # Exports all the database translations to the translations/ folder
   
 
- In config/initialisers/i18n.rb
   
-    I18n.backend = I18n::Backend::Database.new # registers the backend
     I18n.backend.cache_store = :memory_store   # optional: specify an alternate cache store
     I18n.backend.localize_text_tag = '##'      # optional: specify an alternate localize text tag, the default is ^^
   
 
-In config/routes.rb to register admin panel routes - -
-  
-    map.from_plugin 'i18n_backend_database'
-  
-
- h2. Use All non-user generated text provided by the application needs to be wrapped in a call to I18n.t().
   
-    I18n.t("Hello there!")
+    t("Hello there!")
   
 
-Interpolation is handled by passing in key/value pairs as a value to an interpolation tag ( {{ }} ). +Interpolation is handled by passing in key/value pairs as a value to an interpolation tag ( %{ } ).
   
-    I18n.t("Hello there {{name}}!", :name => "Dylan")
+    t("Hello there %{name}!", :name => "Dylan")
   
 
   
-    I18n.t("Click {{here}} or {{there}}", :here => "link_to(I18n.t('midnite'), croix_path)", :there => "link_to(I18n.t('staten_island'), wu_path)")
+    t("Click %{here} or %{there}", :here => "link_to(t('midnite'), croix_path)", :there => "link_to(t('staten_island'), wu_path)")
   
 
Pluralization is handled by passing in a "count" key value pair, which is a unique interpolation value.
   
-    I18n.t("You are {{count}} years old", :count => 100)
+    I18n.t("You are %{count} years old", :count => 100)
   
 
Links to external documents that need to be translated should be tagged as well.
   
-    I18n.t('http://www.elctech.com/core')
-    
-  
+ t('http://www.elctech.com/core') + + All fragment cache view blocks need to have their keys prepended with the current locale.
@@ -84,42 +75,41 @@ All fragment cache view blocks need to have their keys prepended with the curren
   
 
-Date/Time localization is handled by using the I18n.l method. The format used will be :default (see next item for explanation). +Date/Time localization is handled by using the localize (l) method. The format used will be :default (see next item for explanation).
   
-    I18n.l(@user.joined_at)
+    l(@user.joined_at)
   
 
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.
   
-    I18n.l(@user.joined_at, :format => :default)
-    I18n.l(@user.joined_at, :format => :short)
-    I18n.l(@user.joined_at, :format => :espn_default)
+    l(@user.joined_at, :format => :default)
+    l(@user.joined_at, :format => :short)
+    l(@user.joined_at, :format => :espn_default)
   
 
Date/Time localization can take a custom format string as well.
   
-    I18n.l(@user.joined_at, :format => "%B %e, %Y")
+    l(@user.joined_at, :format => "%B %e, %Y")
   
 
Text stored in a database can be localized by tagging the text being stored and then localizing in the view etc.
   
-    I18n.tlt("is now friends with") => "^^is now friends with^^"
-    I18n.lt("shane ^^is now friends with^^ dylan") => "shane ahora es con amigos dylan"
+    tlt("is now friends with") => "^^is now friends with^^"
+    lt("shane ^^is now friends with^^ dylan") => "shane ahora es con amigos dylan"
   
 
Images can be translated with the I18n.ta tag
   
-    <%= image_tag(I18n.ta("logos/elc.gif"), :size => "134x75") %>
+    <%= image_tag(ta("logos/elc.gif"), :size => "134x75") %>
     
   
In this example, for a locale es, there should be an image: public/es/images/logos/elc.gif - diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..790708f --- /dev/null +++ b/Rakefile @@ -0,0 +1,31 @@ +#!/usr/bin/env rake +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end +begin + require 'rdoc/task' +rescue LoadError + require 'rdoc/rdoc' + require 'rake/rdoctask' + RDoc::Task = Rake::RDocTask +end + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'I18nBackendDatabase' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + + + +Bundler::GemHelper.install_tasks + +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new('spec') + +task :default => :spec \ No newline at end of file diff --git a/lib/controllers/locales_controller.rb b/app/controllers/locales_controller.rb similarity index 79% rename from lib/controllers/locales_controller.rb rename to app/controllers/locales_controller.rb index c9b35e1..cf92b78 100644 --- a/lib/controllers/locales_controller.rb +++ b/app/controllers/locales_controller.rb @@ -3,8 +3,7 @@ class LocalesController < ActionController::Base # GET /locales # GET /locales.xml def index - @locales = Locale.find(:all) - + @locales = I18n::Backend::Locale.all respond_to do |format| format.html # index.html.erb format.xml { render :xml => @locales } @@ -14,8 +13,7 @@ def index # GET /locales/1 # GET /locales/1.xml def show - @locale = Locale.find_by_code(params[:id]) - + @locale = I18n::Backend::Locale.find_by_code(params[:id]) respond_to do |format| format.html # show.html.erb format.xml { render :xml => @locale } @@ -25,8 +23,7 @@ def show # GET /locales/new # GET /locales/new.xml def new - @locale = Locale.new - + @locale = I18n::Backend::Locale.new respond_to do |format| format.html # new.html.erb format.xml { render :xml => @locale } @@ -35,20 +32,20 @@ def new # GET /locales/1/edit def edit - @locale = Locale.find_by_code(params[:id]) + @locale = I18n::Backend::Locale.find_by_code(params[:id]) end # POST /locales # POST /locales.xml def create - @locale = Locale.new(params[:locale]) - + @locale = I18n::Backend::Locale.new(params[:locale]) respond_to do |format| if @locale.save flash[:notice] = 'Locale was successfully created.' format.html { redirect_to(@locale) } format.xml { render :xml => @locale, :status => :created, :location => @locale } else + flash[:error] = @locale.errors.to_a format.html { render :action => "new" } format.xml { render :xml => @locale.errors, :status => :unprocessable_entity } end @@ -58,14 +55,14 @@ def create # PUT /locales/1 # PUT /locales/1.xml def update - @locale = Locale.find_by_code(params[:id]) - + @locale = I18n::Backend::Locale.find_by_code(params[:id]) respond_to do |format| if @locale.update_attributes(params[:locale]) flash[:notice] = 'Locale was successfully updated.' format.html { redirect_to(@locale) } format.xml { head :ok } else + flash[:error] = @locale.errors.to_a format.html { render :action => "edit" } format.xml { render :xml => @locale.errors, :status => :unprocessable_entity } end @@ -75,9 +72,8 @@ def update # DELETE /locales/1 # DELETE /locales/1.xml def destroy - @locale = Locale.find_by_code(params[:id]) + @locale = I18n::Backend::Locale.find_by_code(params[:id]) @locale.destroy - respond_to do |format| format.html { redirect_to(locales_url) } format.xml { head :ok } diff --git a/app/controllers/translations_controller.rb b/app/controllers/translations_controller.rb new file mode 100644 index 0000000..5c117cb --- /dev/null +++ b/app/controllers/translations_controller.rb @@ -0,0 +1,185 @@ +class TranslationsController < ActionController::Base + + layout 'translations' + before_filter :find_locale + + helper_method :translation_stats,:check_for_missing_params + + ## FIXME: you'll probably want add authorization to this controller! + + # GET /translations + # GET /translations.xml + def index + @translations = @locale.translations.find(:all, :order => "raw_key, pluralization_index") + respond_to do |format| + format.html # index.html.erb + format.xml { render :xml => @translations } + end + end + + # GET /translations + # GET /translations.xml + def translations + session[:translation_option] = params[:translation_option] if params[:translation_option] + session[:show_keys] = params[:show_keys] if params[:show_keys] + @translation_option = TranslationOption.find(session[:translation_option]) + case @translation_option + when TranslationOption.translated + @translations = @locale.translations.translated + when TranslationOption.unsourced + @translations = @locale.translations.unsourced + else + @translations = @locale.translations.untranslated + end + if params[:export] == 'csv' + csv_string = CSV.generate do |csv| + csv << ['Original','Translation','Plural','Key'] + @translations.each do |translation| + next unless default_value = translation.default_locale_value + line = [] + line << default_value + line << translation.value + line << translation.pluralization_index + line << translation.raw_key if translation.raw_key != default_value + csv << line + end + end + send_data csv_string, :type => 'text/plain', :filename=>"#{@locale.code}_#{@translation_option ? @translation_option.code : 'untranslated'}_#{Time.now.strftime('%Y%m%d%H%M%S')}.csv", :disposition => 'attachment' + else + respond_to do |format| + format.html # index.html.erb + format.xml { render :xml => @translations } + end + end + end + + # GET /asset_translations + # GET /asset_translations.xml + def asset_translations + @locale ||= I18n::Backend::Locale.default_locale + @translation_option = TranslationOption.find(params[:translation_option]) + @asset_translations = I18n.asset_translations + @untranslated_assets = I18n.untranslated_assets(@locale.code) + @percentage_translated = (((@asset_translations.size - @untranslated_assets.size).to_f / @asset_translations.size.to_f * 100).round) rescue 0 + case @translation_option + when TranslationOption.translated + @asset_translations = @asset_translations.reject{|e| @untranslated_assets.include?(e)} + else + @asset_translations = @untranslated_assets + end + respond_to do |format| + format.html # index.html.erb + format.xml { render :xml => @untranslated_assets } + end + end + + # GET /translations/1 + # GET /translations/1.xml + def show + @translation = @locale.translations.find(params[:id]) + respond_to do |format| + format.html # show.html.erb + format.xml { render :xml => @translation } + end + end + + # GET /translations/new + # GET /translations/new.xml + def new + @translation = Translation.new + respond_to do |format| + format.html # new.html.erb + format.xml { render :xml => @translation } + end + end + + # GET /translations/1/edit + def edit + @translation = @locale.translations.find(params[:id]) + end + + # POST /translations + # POST /translations.xml + def create + @translation = @locale.translations.build(params[:translation]) + + respond_to do |format| + if @translation.save + flash[:notice] = 'Translation was successfully created.' + format.html { redirect_to locale_translation_path(@locale, @translation) } + format.xml { render :xml => @translation, :status => :created, :location => @translation } + else + format.html { render :action => "new" } + format.xml { render :xml => @translation.errors, :status => :unprocessable_entity } + end + end + end + + # PUT /translations/1 + # PUT /translations/1.xml + def update + @translation = @locale.translations.find(params[:id]) + @first_time_translating = @translation.value.nil? + respond_to do |format| + params[:translation] ||= {} + params[:translation][:source_id] = nil + params[:translation][:value] = nil if params[:translation][:value].blank? + if @translation.update_attributes(params[:translation]) + format.html do + flash[:notice] = 'Translation was successfully updated.' + redirect_to :back + end + format.xml { head :ok } + format.js {} + else + format.html { render :action => "edit" } + format.xml { render :xml => @translation.errors, :status => :unprocessable_entity } + end + end + end + + # DELETE /translations/1 + # DELETE /translations/1.xml + def destroy + @translation = @locale.translations.find(params[:id]) + @translation.destroy + + respond_to do |format| + format.html { redirect_to(locale_translations_url) } + format.xml { head :ok } + end + end + +private + + def check_for_missing_params(default_value,target_value) + return if (default_params = default_value.scan(/\%\{(.*?)\}/).flatten).empty? + return default_params if target_value.nil? or (target_params = target_value.scan(/\%\{(.*?)\}/).flatten).empty? + return if (missing_params = default_params - target_params).empty? + missing_params + end + + def translation_stats + @stats ||= [].tap do |stats| + default_locale = I18n::Backend::Locale.default_locale + stats << collect_counts(default_locale) + I18n::Backend::Locale.non_defaults.order(:name).each{|locale| stats << collect_counts(locale)} + max_total = stats.collect{|stat| stat[:total]}.max + stats.each{|stat| stat[:missing] = max_total - stat[:total]} + end + end + + def collect_counts(locale) + total = locale.translations.count + translated = locale.translations.translated.count + untranslated = total - translated + unsourced = locale.translations.unsourced.count + sourced = total - unsourced + {:locale => locale, :total => total, :translated => translated, :untranslated => untranslated, :unsourced => unsourced, :sourced => sourced} + end + + def find_locale + session[:locale_id] = params[:locale_id] if params[:locale_id] + @locale = I18n::Backend::Locale.find_by_code(session[:locale_id]) || I18n::Backend::Locale.default_locale + end +end diff --git a/app/models/i18n/backend/locale.rb b/app/models/i18n/backend/locale.rb new file mode 100644 index 0000000..18b8594 --- /dev/null +++ b/app/models/i18n/backend/locale.rb @@ -0,0 +1,77 @@ +module I18n::Backend + class Locale < ActiveRecord::Base + validates_presence_of :code + validates_uniqueness_of :code + + attr_accessible :code,:name + + has_many :translations, :dependent => :destroy + scope :non_defaults, :conditions => ["code != ?", I18n.default_locale.to_s] + + # scope :english, lambda { |m| { return Hash.new if m.nil?; :conditions => "locales.locale = '#{m}'" } } + # scope :in_city, lambda { |m| { return {} if m.nil?; :joins => [cities], :conditions => "cities.name = '#{m}' } } + + def self.default_locale + @@default_locale ||= where(:code => I18n.default_locale.to_s).first + end + + def self.reset_default_locale + @@default_locale = nil + end + + def self.find_or_create!(params) + code = params.kind_of?(Hash) ? params[:code] : params.to_s + where(:code => code).first || create!(:code => code) + end + + def translation_from_key(key) + self.translations.where(:key => Translation.hk(key)).first + end + + def create_translation(key, value, pluralization_index=1) + conditions = {:key => key, :raw_key => key.to_s, :pluralization_index => pluralization_index} + + conditions[:source_id] = I18nUtil.current_load_source.to_param if Translation.column_names.include?('source_id') # TODO does this NEED to happen? occurs when adding "source_id" migration to legacy installs + + # set the key as the value if we're using the default locale and the key is a string + conditions.merge!({:value => value}) if (self.code == I18n.default_locale.to_s && key.is_a?(String)) + save # ensure the locale exists + translation = self.translations.create(conditions) + puts "...NEW #{self.code} : #{key} : #{pluralization_index}" if I18nUtil.verbose? + + # hackity hack. bug #922 maybe? + self.connection.commit_db_transaction unless Rails.env.test? + translation + end + + def find_translation_or_copy_from_default_locale(key, pluralization_index) + self.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) || copy_from_default(key, pluralization_index) + end + + def copy_from_default(key, pluralization_index) + if !self.default_locale? && I18n::Backend::Locale.default_locale.has_translation?(key, pluralization_index) + create_translation(key, key, pluralization_index) + end + end + + def has_translation?(key, pluralization_index=1) + self.translations.exists?(:key => Translation.hk(key), :pluralization_index => pluralization_index) + end + + def percentage_translated + (self.translations.translated.count.to_f / self.translations.count.to_f * 100).round rescue 100 + end + + def self.available_locales + all.map(&:code).map(&:to_sym) rescue [] + end + + def default_locale? + self == I18n::Backend::Locale.default_locale + end + + def to_param + self.code + end + end +end diff --git a/app/models/translation.rb b/app/models/translation.rb new file mode 100644 index 0000000..c343a72 --- /dev/null +++ b/app/models/translation.rb @@ -0,0 +1,50 @@ +require 'digest/md5' +class Translation < ActiveRecord::Base + belongs_to :locale, :class_name => 'I18n::Backend::Locale' + belongs_to :source, :class_name => 'TranslationSource' + validates_presence_of :key + before_validation :generate_hash_key, :on => :create + after_update :update_cache + + attr_accessible :locale_id,:source_id,:key,:raw_key,:pluralization_index,:value + + scope :untranslated, :conditions => {:value => nil}, :order => :raw_key + scope :unsourced, :conditions => {:source_id => nil}, :order => :raw_key + scope :translated, :conditions => 'value IS NOT NULL', :order => :raw_key + + def default_locale_value(rescue_value = 'No default locale value') + begin + I18n::Backend::Locale.default_locale.translations.find_by_key_and_pluralization_index(self.key, self.pluralization_index).value + rescue + rescue_value + end + end + + def value_or_default + value = self.value || self.default_locale_value(self.raw_key) + value =~ /^---(.*)\n/ ? YAML.load(value) : value # supports using YAML e.g. order: [ :year, :month, :day ] values are stored as Symbols "--- :year\n", "--- :month\n", "--- :day\n" + end + + # create hash key + def self.hk(key) + Base64.encode64(Digest::MD5.hexdigest(key.to_s)).gsub(/\n/, '') + end + + # create cache key + def self.ck(locale, key, hash=true) + key = self.hk(key) if hash + "#{locale.code}:#{key}" + end + +protected + def generate_hash_key + self.raw_key = key.to_s + self.key = self.class.hk(key) + end + + def update_cache + new_cache_key = self.class.ck(self.locale, self.key, false) + I18n.backend.cache_store.write(new_cache_key, self.value) + end +end + diff --git a/lib/models/translation_option.rb b/app/models/translation_option.rb similarity index 70% rename from lib/models/translation_option.rb rename to app/models/translation_option.rb index 8afa4f3..a3064ba 100644 --- a/lib/models/translation_option.rb +++ b/app/models/translation_option.rb @@ -1,14 +1,14 @@ class TranslationOption attr_accessor :code, :description - cattr_accessor :translated, :untranslated + cattr_accessor :translated, :untranslated, :unsourced def initialize(code, description) @code, @description = code, description end def self.all - [untranslated, translated] + [untranslated, translated, unsourced] end def self.translated @@ -19,6 +19,10 @@ def self.untranslated @@untranslated ||= TranslationOption.new('untranslated', 'Untranslated') end + def self.unsourced + @@unsourced ||= TranslationOption.new('unsourced', 'Unsourced') + end + def self.find(code) all.detect{|option| option.code == code} || untranslated end diff --git a/app/models/translation_source.rb b/app/models/translation_source.rb new file mode 100644 index 0000000..cb41831 --- /dev/null +++ b/app/models/translation_source.rb @@ -0,0 +1,12 @@ +class TranslationSource < ActiveRecord::Base + + attr_accessible :path + + def full_path + @full_path ||= self.path && (Rails.root + self.path) + end + + def path_not_found? + full_path && !full_path.exist? + end +end diff --git a/app/views/layouts/translations.html.erb b/app/views/layouts/translations.html.erb new file mode 100644 index 0000000..1b9011e --- /dev/null +++ b/app/views/layouts/translations.html.erb @@ -0,0 +1,15 @@ + + + + + Translation Administration: <%= controller.action_name %> + <%= javascript_include_tag 'jquery.min' %> + <%= javascript_include_tag 'rails' %> + + +

<%= link_to 'Textual Translations', translations_url %> · <%= link_to 'Asset Translations', asset_translations_url %>

+
+ <%= yield %> +
+ + diff --git a/app/views/locales/edit.html.erb b/app/views/locales/edit.html.erb new file mode 100644 index 0000000..950c04d --- /dev/null +++ b/app/views/locales/edit.html.erb @@ -0,0 +1,11 @@ +

Editing locale

+ +<%= display_flash %> + +<% form_for(@locale, :url => locales_path(@locale), :method => :put) do |f| %> +

<%= f.label :code, 'Code' %><%= f.text_field :code %>

+

<%= f.label :name, 'Name' %><%= f.text_field :name %>

+

<%= f.submit "Update" %>

+<% end %> + +

<%= link_to 'Show', locale_path(@locale) %> | <%= link_to 'Back', locales_path %>

diff --git a/app/views/locales/index.html.erb b/app/views/locales/index.html.erb new file mode 100644 index 0000000..c80c3a3 --- /dev/null +++ b/app/views/locales/index.html.erb @@ -0,0 +1,18 @@ +

Listing locales

+ + + + + + <% for locale in @locales %> + + + + + + + + <% end %> +
CodeName
<%=h locale.code %><%=h locale.name %><%= link_to 'Show', locale_path(locale) %><%= link_to 'Edit', edit_locale_path(locale) %><%= link_to 'Destroy', locale_path(locale), :confirm => 'Are you sure?', :method => :delete %>
+ +

<%= link_to 'New locale', new_locale_path %>

diff --git a/app/views/locales/new.html.erb b/app/views/locales/new.html.erb new file mode 100644 index 0000000..675d56c --- /dev/null +++ b/app/views/locales/new.html.erb @@ -0,0 +1,10 @@ +

New locale

+ +<%= form_for(@locale, :url => locales_path, :html => {:method => :post}) do |f| %> + <%= display_flash %> +

<%= f.label :code, 'Code' %><%= f.text_field :code %>

+

<%= f.label :name, 'Name' %><%= f.text_field :name %>

+

<%= f.submit "Create" %>

+<% end %> + +

<%= link_to 'Back', locales_path %>

diff --git a/app/views/locales/show.html.erb b/app/views/locales/show.html.erb new file mode 100644 index 0000000..b4cf949 --- /dev/null +++ b/app/views/locales/show.html.erb @@ -0,0 +1,9 @@ +

Code:<%= @locale.code %>

+ +

Name:<%= @locale.name %>

+ +

+ <%= link_to 'Edit', edit_locale_path(@locale) %> | + <%= link_to 'Translations', locale_translations_path(@locale) %> | + <%= link_to 'Back', locales_path %> +

diff --git a/app/views/translations/_stats.html.erb b/app/views/translations/_stats.html.erb new file mode 100644 index 0000000..747ea6c --- /dev/null +++ b/app/views/translations/_stats.html.erb @@ -0,0 +1,16 @@ +<% translation_stats.each do |stat| %> +
<%= stat[:locale].name %>
+
+
 
+
 
+
 
+
+
+
+
 
+
 
+
 
+
+
+
 
+<% end %> diff --git a/lib/views/translations/asset_translations.html.erb b/app/views/translations/asset_translations.html.erb similarity index 95% rename from lib/views/translations/asset_translations.html.erb rename to app/views/translations/asset_translations.html.erb index 3aff15a..1525a10 100644 --- a/lib/views/translations/asset_translations.html.erb +++ b/app/views/translations/asset_translations.html.erb @@ -1,5 +1,5 @@ <% form_tag asset_translations_url do -%> -<%= select_tag :locale_id, options_from_collection_for_select(Locale.all, :code, :name, @locale.code) -%> +<%= select_tag :locale_id, options_from_collection_for_select(I18n::Backend::Locale.all, :code, :name, @locale.code) -%> <%= select_tag :translation_option, options_from_collection_for_select(TranslationOption.all, :code, :description, @translation_option.code) -%> <%= submit_tag 'Go' %> <% end -%> diff --git a/app/views/translations/edit.html.erb b/app/views/translations/edit.html.erb new file mode 100644 index 0000000..27c093b --- /dev/null +++ b/app/views/translations/edit.html.erb @@ -0,0 +1,18 @@ +

Editing translation for <%= @locale.code %>

+ +

Key:<%=h @translation.key %>

+ +

Raw Key:<%=h @translation.raw_key %>

+ +

Pluralization Index:<%=h @translation.pluralization_index %>

+ +

Default Locale Value:<%=h @translation.default_locale_value %>

+ +<%= display_flash %> + +<%= form_for(@translation, :as => :translation, :url => locale_translation_path(@locale, @translation)) do |f| %> +

<%= f.label :value, 'Value' %><%= f.text_field :value %>

+

<%= f.submit "Update" %>

+<% end %> + +

<%= link_to 'Show', locale_translation_path(@locale, @translation) %> | <%= link_to 'Back', locale_translations_path %>

diff --git a/lib/views/translations/index.html.erb b/app/views/translations/index.html.erb similarity index 64% rename from lib/views/translations/index.html.erb rename to app/views/translations/index.html.erb index 4f07257..8bc77fd 100644 --- a/lib/views/translations/index.html.erb +++ b/app/views/translations/index.html.erb @@ -7,8 +7,7 @@ Value Default Locale Value - -<% for translation in @translations %> + <% for translation in @translations %> <%=h translation.key %> <%=h translation.raw_key %> @@ -16,12 +15,9 @@ <%=h translation.default_locale_value %> <%= link_to 'Show', locale_translation_path(@locale, translation) %> <%= link_to 'Edit', edit_locale_translation_path(@locale, translation) %> - <%= link_to 'Destroy', locale_translation_path(@locale, translation), :confirm => 'Are you sure?', :method => :delete %> + <%= button_to 'Destroy', locale_translation_path(@locale, translation), :confirm => 'Are you sure?', :method => :delete %> -<% end %> + <% end %> -
- -<%= link_to 'New translation', new_locale_translation_path %> | -<%= link_to 'Back', locales_path %> +

<%= link_to 'New translation', new_locale_translation_path %> | <%= link_to 'Back', locales_path %>

diff --git a/app/views/translations/new.html.erb b/app/views/translations/new.html.erb new file mode 100644 index 0000000..6c918cb --- /dev/null +++ b/app/views/translations/new.html.erb @@ -0,0 +1,11 @@ +

New translation

+ +<%= display_flash %> + +<%= form_for(@translation, :as => :translation, :url => locale_translations_path(@locale), :method => :post) do |f| %> +

<%= f.label :key, 'Key' %><%= f.text_field :key %>

+

<%= f.label :value, 'Value' %><%= f.text_field :value %>

+

<%= f.submit "Create" %>

+<% end %> + +

<%= link_to 'Back', locale_translations_path %>

diff --git a/app/views/translations/show.html.erb b/app/views/translations/show.html.erb new file mode 100644 index 0000000..c7f7bd4 --- /dev/null +++ b/app/views/translations/show.html.erb @@ -0,0 +1,13 @@ +

Locale:<%=h @locale.code %>

+ +

Key:<%=h @translation.key %>

+ +

Raw Key:<%=h @translation.raw_key %>

+ +

Pluralization Index:<%=h @translation.pluralization_index %>

+ +

Default Locale Value:<%=h @translation.default_locale_value %>

+ +

Value:<%=h @translation.value %>

+ +

<%= link_to 'Edit', edit_locale_translation_path(@locale, @translation) %> | <%= link_to 'Back', locale_translations_path %>

diff --git a/app/views/translations/translations.html.erb b/app/views/translations/translations.html.erb new file mode 100644 index 0000000..10088ca --- /dev/null +++ b/app/views/translations/translations.html.erb @@ -0,0 +1,41 @@ +<%= render :partial => 'stats' %> + +<%= form_tag translations_url do %> + <%= select_tag :locale_id, options_from_collection_for_select(I18n::Backend::Locale.all, :code, :name, @locale.code) %> + <%= select_tag :translation_option, + options_from_collection_for_select(TranslationOption.all, :code, :description, @translation_option.code) %> + <%= hidden_field_tag :show_keys,'off' %><%= check_box_tag :show_keys,'on',session[:show_keys] == 'on' %> Show keys + <%= submit_tag 'Go' %> +<% end %> + +

Textual Translation progress: <%= @locale.percentage_translated %>% <%= link_to 'Export to CSV',translations_url(:locale_id => @locale.code,:translation_option => @translation_option.code,:export => 'csv') %>

+ +

<%= @translation_option.description %>: <%= @locale.name %>

+ +<% if @translations.empty? %> +

No records for this criteria.

+<% end %> + +<% for translation in @translations %> +
"> + <%= form_for(translation, :as => :translation, :url => locale_translation_path(@locale, translation), :remote => true) do |f| %> + <% if session[:show_keys] == 'on' and translation.raw_key != translation.default_locale_value(nil) %> +

<%= translation.raw_key %>:

+ <% end %> +

<%= default_value = translation.default_locale_value(translation.raw_key) %>

+ <% if missing_params = check_for_missing_params(default_value,translation.value) %> + <% if translation.value.blank? %> +

+ NOTE: The translation below must include the following substitution parameter(s) from the original: + <% else %> +

+ WARNING: The translation below is missing following substitution parameter(s) from the original: + <% end %> + <%= missing_params.collect{|param| "%{#{param}}"}.join(' ') %> +

+ <% end %> +

<%= f.text_field :value, :style => 'width: 90%;' %> <%= f.submit "Update" %>

+ <% end %> +
+
+<% end %> diff --git a/lib/views/translations/update.rjs b/app/views/translations/update.rjs similarity index 100% rename from lib/views/translations/update.rjs rename to app/views/translations/update.rjs diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..a9675af --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,9 @@ +Rails.application.routes.draw do + unless Rails.env.production? + resources :locales do + resources :translations + end + match '/translations' => 'translations#translations', :as => 'translations' + match '/asset_translations' => 'translations#asset_translations', :as => 'asset_translations' + end +end diff --git a/data/default_localized_translations.yml b/data/default_localized_translations.yml new file mode 100644 index 0000000..7a533a0 --- /dev/null +++ b/data/default_localized_translations.yml @@ -0,0 +1,57 @@ +en: + date: + formats: + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + day_names: + - Sunday + - Monday + - Tuesday + - Wednesday + - Thursday + - Friday + - Saturday + abbr_day_names: + - Sun + - Mon + - Tue + - Wed + - Thu + - Fri + - Sat + month_names: + - ~ + - January + - February + - March + - April + - May + - June + - July + - August + - September + - October + - November + - December + abbr_month_names: + - ~ + - Jan + - Feb + - Mar + - Apr + - May + - Jun + - Jul + - Aug + - Sep + - Oct + - Nov + - Dec + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: am + pm: pm diff --git a/generators/i18n_backend_database/i18n_backend_database_generator.rb b/generators/i18n_backend_database/i18n_backend_database_generator.rb deleted file mode 100644 index 4943435..0000000 --- a/generators/i18n_backend_database/i18n_backend_database_generator.rb +++ /dev/null @@ -1,8 +0,0 @@ -class I18nBackendDatabaseGenerator < Rails::Generator::Base - def manifest - record { |m| - m.migration_template "migrate/create_i18n_tables.rb", "db/migrate", - :migration_file_name => "create_i18n_tables" - } - end -end diff --git a/i18n_backend_database.gemspec b/i18n_backend_database.gemspec new file mode 100644 index 0000000..314a263 --- /dev/null +++ b/i18n_backend_database.gemspec @@ -0,0 +1,23 @@ +$:.push File.expand_path('../lib', __FILE__) + +# Maintain your gem's version: +require 'i18n_backend_database/version' + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = 'i18n_backend_database' + s.version = I18nBackendDatabase::VERSION + s.authors = ['Dylan Stamat'] + s.email = ['dstamat@elctech.com'] + s.homepage = 'http://someplace.com' + s.summary = 'summary.' + s.description = 'description.' + + s.files = Dir['{app,config,db,lib}/**/*'] + ['LICENSE', 'Rakefile', 'README.textile'] + s.test_files = Dir['test/**/*'] + + s.add_dependency 'rails', '>= 3' + s.add_development_dependency 'rspec', '>= 2.0.0' + s.add_development_dependency 'rspec-rails', '>= 2.0.0' + s.add_development_dependency 'sqlite3', '>= 1.3.4' +end diff --git a/init.rb b/init.rb deleted file mode 100644 index 1b98b48..0000000 --- a/init.rb +++ /dev/null @@ -1 +0,0 @@ -require 'i18n_backend_database' \ No newline at end of file diff --git a/lib/controllers/translations_controller.rb b/lib/controllers/translations_controller.rb deleted file mode 100644 index 4aa9f91..0000000 --- a/lib/controllers/translations_controller.rb +++ /dev/null @@ -1,141 +0,0 @@ -class TranslationsController < ActionController::Base - prepend_view_path(File.join(File.dirname(__FILE__), "..", "views")) - layout 'translations' - before_filter :find_locale - - ## FIXME: you'll probably want add authorization to this controller! - - # GET /translations - # GET /translations.xml - def index - @translations = @locale.translations.find(:all, :order => "raw_key, pluralization_index") - - respond_to do |format| - format.html # index.html.erb - format.xml { render :xml => @translations } - end - end - - # GET /translations - # GET /translations.xml - def translations - @locale ||= Locale.default_locale - @translation_option = TranslationOption.find(params[:translation_option]) - - if @translation_option == TranslationOption.translated - @translations = @locale.translations.translated - else - @translations = @locale.translations.untranslated - end - - respond_to do |format| - format.html # index.html.erb - format.xml { render :xml => @translations } - end - end - - # GET /asset_translations - # GET /asset_translations.xml - def asset_translations - @locale ||= Locale.default_locale - @translation_option = TranslationOption.find(params[:translation_option]) - - @asset_translations = I18n.asset_translations - @untranslated_assets = I18n.untranslated_assets(@locale.code) - @percentage_translated = (((@asset_translations.size - @untranslated_assets.size).to_f / @asset_translations.size.to_f * 100).round) rescue 0 - - if @translation_option == TranslationOption.translated - @asset_translations = @asset_translations.reject{|e| @untranslated_assets.include?(e)} - else - @asset_translations = @untranslated_assets - end - - respond_to do |format| - format.html # index.html.erb - format.xml { render :xml => @untranslated_assets } - end - end - - # GET /translations/1 - # GET /translations/1.xml - def show - @translation = @locale.translations.find(params[:id]) - - respond_to do |format| - format.html # show.html.erb - format.xml { render :xml => @translation } - end - end - - # GET /translations/new - # GET /translations/new.xml - def new - @translation = Translation.new - - respond_to do |format| - format.html # new.html.erb - format.xml { render :xml => @translation } - end - end - - # GET /translations/1/edit - def edit - @translation = @locale.translations.find(params[:id]) - end - - # POST /translations - # POST /translations.xml - def create - @translation = @locale.translations.build(params[:translation]) - - respond_to do |format| - if @translation.save - flash[:notice] = 'Translation was successfully created.' - format.html { redirect_to locale_translation_path(@locale, @translation) } - format.xml { render :xml => @translation, :status => :created, :location => @translation } - else - format.html { render :action => "new" } - format.xml { render :xml => @translation.errors, :status => :unprocessable_entity } - end - end - end - - # PUT /translations/1 - # PUT /translations/1.xml - def update - @translation = @locale.translations.find(params[:id]) - @first_time_translating = @translation.value.nil? - - respond_to do |format| - if @translation.update_attributes(params[:translation]) - format.html do - flash[:notice] = 'Translation was successfully updated.' - redirect_to locale_translation_path(@locale, @translation) - end - format.xml { head :ok } - format.js {} - else - format.html { render :action => "edit" } - format.xml { render :xml => @translation.errors, :status => :unprocessable_entity } - end - end - end - - # DELETE /translations/1 - # DELETE /translations/1.xml - def destroy - @translation = @locale.translations.find(params[:id]) - @translation.destroy - - respond_to do |format| - format.html { redirect_to(locale_translations_url) } - format.xml { head :ok } - end - end - - private - - def find_locale - @locale = Locale.find_by_code(params[:locale_id]) - end -end diff --git a/lib/ext/i18n.rb b/lib/ext/i18n.rb index 018da00..8c886f6 100644 --- a/lib/ext/i18n.rb +++ b/lib/ext/i18n.rb @@ -1,8 +1,8 @@ module I18n - APP_DIRECTORY = 'app/views' + APP_DIRECTORY = 'app/views' + class << self - def locale_segment I18n.locale.to_s == I18n.default_locale.to_s ? "" : "/#{I18n.locale}" end @@ -63,6 +63,5 @@ def locale_asset(asset, locale) def asset_path(asset) asset[0] == ?/ ? asset : "/images/#{asset}" end - end -end \ No newline at end of file +end diff --git a/lib/generators/i18n_backend_database/i18n_backend_database_generator.rb b/lib/generators/i18n_backend_database/i18n_backend_database_generator.rb new file mode 100644 index 0000000..cb03c9e --- /dev/null +++ b/lib/generators/i18n_backend_database/i18n_backend_database_generator.rb @@ -0,0 +1,11 @@ +require 'rails/generators/active_record/migration' + +class I18nBackendDatabaseGenerator < Rails::Generators::Base + include Rails::Generators::Migration + extend ActiveRecord::Generators::Migration + source_root File.expand_path('../templates', __FILE__) + + def create_migration + migration_template "migrate/create_i18n_tables.rb", 'db/migrate/create_i18n_tables' + end +end diff --git a/generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb b/lib/generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb similarity index 79% rename from generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb rename to lib/generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb index dd84dad..5d33752 100644 --- a/generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb +++ b/lib/generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb @@ -12,14 +12,19 @@ def self.up t.text :value t.integer :pluralization_index, :default => 1 t.integer :locale_id + t.integer :source_id end add_index :translations, [:locale_id, :key, :pluralization_index] + create_table :translation_sources do |t| + t.string :path + end + end def self.down drop_table :locales drop_table :translations - drop_table :asset_translations + drop_table :translation_sources end end diff --git a/lib/i18n_backend_database.rb b/lib/i18n_backend_database.rb index 3ff9529..497b752 100644 --- a/lib/i18n_backend_database.rb +++ b/lib/i18n_backend_database.rb @@ -1,9 +1,7 @@ -require File.dirname(__FILE__) + '/models/locale' -require File.dirname(__FILE__) + '/models/translation' -require File.dirname(__FILE__) + '/models/translation_option' -require File.dirname(__FILE__) + '/routing' -require File.dirname(__FILE__) + '/controllers/locales_controller' -require File.dirname(__FILE__) + '/controllers/translations_controller' +Dir[File.dirname(__FILE__) + '/../app/**/*.rb'].each{|file| require file} require File.dirname(__FILE__) + '/i18n_backend_database/database' require File.dirname(__FILE__) + '/ext/i18n' -ActionController::Routing::RouteSet::Mapper.send(:include, I18n::BackendDatabase::Routing) +require File.dirname(__FILE__) + '/i18n_util' +require File.dirname(__FILE__) + '/i18n_backend_database' + +require 'i18n_backend_database/engine' diff --git a/lib/i18n_backend_database/database.rb b/lib/i18n_backend_database/database.rb index 350963c..7ac67fe 100644 --- a/lib/i18n_backend_database/database.rb +++ b/lib/i18n_backend_database/database.rb @@ -1,263 +1,283 @@ +require_relative '../i18n_util' + +module I18n::Backend + class Database + INTERPOLATION_RESERVED_KEYS = %w(scope default) + MATCH = /(\\\\)?%\{([^\}]+)\}/ + + attr_accessor :locale + attr_accessor :cache_store + attr_accessor :localize_text_tag + + def initialize(options = {}) + store = options.delete(:cache_store) + text_tag = options.delete(:localize_text_tag) + @cache_store = store ? ActiveSupport::Cache.lookup_store(store) : Rails.cache + @localize_text_tag = text_tag ? text_tag : '^^' + end -module I18n - module Backend - class Database - INTERPOLATION_RESERVED_KEYS = %w(scope default) - MATCH = /(\\\\)?\{\{([^\}]+)\}\}/ - - attr_accessor :locale - attr_accessor :cache_store - attr_accessor :localize_text_tag - - def initialize(options = {}) - store = options.delete(:cache_store) - text_tag = options.delete(:localize_text_tag) - @cache_store = store ? ActiveSupport::Cache.lookup_store(store) : Rails.cache - @localize_text_tag = text_tag ? text_tag : '^^' - end + def locale=(code) + @locale = I18n::Backend::Locale.find_by_code(code) + end - def locale=(code) - @locale = Locale.find_by_code(code) + def cache_store=(store) + @cache_store = ActiveSupport::Cache.lookup_store(store) + end + + # TODO RAILS3 not sure what this method should do or how it should act yet + def transliterate *args + args[1] + end + + # Handles the lookup and addition of translations to the database + # + # On an initial translation, the locale is checked to determine if + # this is the default locale. If it is, we'll create a complete + # translation record for this locale with both the key and value. + # + # If the current locale is checked, and it differs from the default + # locale, we'll create a translation record with a nil value. This + # allows for the lookup of untranslated records in a given locale. + def translate(locale, key, options = {}) + locale_in_context(locale) + + options[:scope] = [options[:scope]] unless options[:scope].is_a?(Array) || options[:scope].blank? + key = "#{options[:scope].join('.')}.#{key}".to_sym if options[:scope] + count = options[:count] + # pull out values for interpolation + values = options.reject { |name, value| [:scope, :default].include?(name) } + + entry = lookup(@locale, key) + cache_lookup = true unless entry.nil? + + # if no entry exists for the current locale and the current locale is not the default locale then lookup translations for the default locale for this key + unless entry || @locale.default_locale? + entry = use_and_copy_default_locale_translations_if_they_exist(@locale, key) end - def cache_store=(store) - @cache_store = ActiveSupport::Cache.lookup_store(store) + # TODO revisit this code -- for now consider the first non-Symbol default as the value + # if we have no entry and some defaults ... start looking them up + #unless entry || key.is_a?(String) || options[:default].blank? + # default = options[:default].is_a?(Array) ? options[:default].shift : options.delete(:default) + # return translate(@locale.code, default, options.dup) + #end + + # this needs to be folded into the above at some point. + # this handles the case where the default of the string key is a space + if !entry && key.is_a?(String) && options[:default] == " " + default = options[:default].is_a?(Array) ? options[:default].shift : options.delete(:default) + return translate(@locale.code, default, options.dup) end - # Handles the lookup and addition of translations to the database - # - # On an initial translation, the locale is checked to determine if - # this is the default locale. If it is, we'll create a complete - # translation record for this locale with both the key and value. - # - # If the current locale is checked, and it differs from the default - # locale, we'll create a translation record with a nil value. This - # allows for the lookup of untranslated records in a given locale. - def translate(locale, key, options = {}) - @locale = locale_in_context(locale) - - options[:scope] = [options[:scope]] unless options[:scope].is_a?(Array) || options[:scope].blank? - key = "#{options[:scope].join('.')}.#{key}".to_sym if options[:scope] && key.is_a?(Symbol) - count = options[:count] - # pull out values for interpolation - values = options.reject { |name, value| [:scope, :default].include?(name) } - - entry = lookup(@locale, key) - cache_lookup = true unless entry.nil? - - # if no entry exists for the current locale and the current locale is not the default locale then lookup translations for the default locale for this key - unless entry || @locale.default_locale? - entry = use_and_copy_default_locale_translations_if_they_exist(@locale, key) + # The requested key might not be a parent node in a hierarchy of keys instead of a regular 'leaf' node + # that would simply result in a string return. If so, check the database for possible children + # and return them in a nested hash if we find them. + # We can safely ignore pluralization indeces here since they should never apply to a hash return + if !entry && (key.is_a?(String) || key.is_a?(Symbol)) + #We need to escape % and \. Rails will handle the rest. + escaped_key = key.to_s.gsub('\\', '\\\\\\\\').gsub(/%/, '\%') + # Only taking those translations that in which the beggining of the raw_key is EXACTLY like the given case. This means it's not case sensitive. + # This allows to use Number as a normal key and number.whatever.whatever.. as the scoped key. + children = @locale.translations.where(["raw_key like ?", "#{escaped_key}.%"]).select{|child| child.raw_key.starts_with?(key.to_s)} + if children.size > 0 and (entry = hashify_record_array(key.to_s, children)) + @cache_store.write(Translation.ck(@locale, key), entry) unless cache_lookup == true + return entry end + end - # if we have no entry and some defaults ... start looking them up - unless entry || key.is_a?(String) || options[:default].blank? - default = options[:default].is_a?(Array) ? options[:default].shift : options.delete(:default) - return translate(@locale.code, default, options.dup) + # we check the database before creating a translation as we can have translations with nil values + # if we still have no blasted translation just go and create one for the current locale! + unless entry + pluralization_index = (options[:count].nil? || options[:count] == 1) ? 1 : 0 + key = key.to_s + key.gsub!('.one', '') if key.ends_with?('.one') + if (translation = @locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index)) and not translation.raw_key.starts_with?(key.to_s) + translation = nil end - - # this needs to be folded into the above at some point. - # this handles the case where the default of the string key is a space - if !entry && key.is_a?(String) && options[:default] == " " - default = options[:default].is_a?(Array) ? options[:default].shift : options.delete(:default) - return translate(@locale.code, default, options.dup) + unless translation + first_string_default = Array(options[:default]).detect{|option| option.is_a?(String)} + translation = @locale.create_translation(key, first_string_default || key, pluralization_index) end + entry = translation.value_or_default + end - # The requested key might not be a parent node in a hierarchy of keys instead of a regular 'leaf' node - # that would simply result in a string return. If so, check the database for possible children - # and return them in a nested hash if we find them. - # We can safely ignore pluralization indeces here since they should never apply to a hash return - if !entry && (key.is_a?(String) || key.is_a?(Symbol)) - #We need to escape % and \. Rails will handle the rest. - escaped_key = key.to_s.gsub('\\', '\\\\\\\\').gsub(/%/, '\%') - children = @locale.translations.find :all, :conditions => ["raw_key like ?", "#{escaped_key}.%"] - if children.size > 0 - entry = hashify_record_array(key.to_s, children) - @cache_store.write(Translation.ck(@locale, key), entry) unless cache_lookup == true - return entry - end - end + # write to cache unless we've already had a successful cache hit + @cache_store.write(Translation.ck(@locale, key), entry) unless cache_lookup == true - # we check the database before creating a translation as we can have translations with nil values - # if we still have no blasted translation just go and create one for the current locale! - unless entry - pluralization_index = (options[:count].nil? || options[:count] == 1) ? 1 : 0 - translation = @locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) || - @locale.create_translation(key, key, pluralization_index) - entry = translation.value_or_default - end + entry = pluralize(@locale, entry, count) + entry = interpolate(@locale.code, entry, values) + entry.is_a?(Array) ? entry.dup : entry # array's can get frozen with cache writes + end - # write to cache unless we've already had a successful cache hit - @cache_store.write(Translation.ck(@locale, key), entry) unless cache_lookup == true + # Acts the same as +strftime+, but returns a localized version of the + # formatted date string. Takes a key from the date/time formats + # translations as a format argument (e.g., :short in :'date.formats'). + def localize(locale, object, format = :default, options = {}) + raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime) - entry = pluralize(@locale, entry, count) - entry = interpolate(@locale.code, entry, values) - entry.is_a?(Array) ? entry.dup : entry # array's can get frozen with cache writes - end + locale_in_context(locale) - # Acts the same as +strftime+, but returns a localized version of the - # formatted date string. Takes a key from the date/time formats - # translations as a format argument (e.g., :short in :'date.formats'). - def localize(locale, object, format = :default) - raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime) - + if format.to_s.index('%') + format = format.dup # ensure that the original format (possibly a constant) is not modified + else # Unless a custom format is passed type = object.respond_to?(:sec) ? 'time' : 'date' - format = translate(locale, "#{type}.formats.#{format.to_s}") unless format.to_s.index('%') # lookup keyed formats unless a custom format is passed - - format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday]) - format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday]) - format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon]) - format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon]) - format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour - - object.strftime(format) - end - - # Returns the text string with the text within the localize text tags translated. - def localize_text(locale, text) - text_tag = Regexp.escape(localize_text_tag).to_s - expression = Regexp.new(text_tag + "(.*?)" + text_tag) - tagged_text = text[expression, 1] - while tagged_text do - text = text.sub(expression, translate(locale, tagged_text)) - tagged_text = text[expression, 1] + if lookup(@locale, "#{type}.formats.#{format.to_s}") # Translation is in the database + format = translate(locale, "#{type}.formats.#{format.to_s}") # lookup keyed formats + else # There is no localization translations on the database, loading the default ones + I18nUtil.load_default_localizations + locale = I18n::Backend::Locale.default_locale.code + format = translate(locale, "#{type}.formats.#{format.to_s}") end - return text end - def available_locales - Locale.available_locales - end + format = format.dup + format.gsub!(/%a/, translate(locale, "date.abbr_day_names")[object.wday]) + format.gsub!(/%A/, translate(locale, "date.day_names")[object.wday]) + format.gsub!(/%b/, translate(locale, "date.abbr_month_names")[object.mon]) + format.gsub!(/%B/, translate(locale, "date.month_names")[object.mon]) + format.gsub!(/%p/, translate(locale, "time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour - def reload! - # get's called on initialization - # let's not do anything yet + object.strftime(format) + end + + # Returns the text string with the text within the localize text tags translated. + def localize_text(locale, text) + text_tag = Regexp.escape(localize_text_tag).to_s + expression = Regexp.new(text_tag + "(.*?)" + text_tag) + tagged_text = text[expression, 1] + while tagged_text do + text = text.sub(expression, translate(locale, tagged_text)) + tagged_text = text[expression, 1] end + return text + end - protected - # keep a local copy of the locale in context for use within the translation - # routine, and also accept an arbitrary locale for one time locale lookups - def locale_in_context(locale) - return @locale if @locale && @locale.code == locale.to_s - #Locale.find_by_code(locale.to_s) rescue nil && (raise InvalidLocale.new(locale)) - locale = Locale.find_by_code(locale.to_s) - raise InvalidLocale.new(locale) unless locale - locale - end + def available_locales + I18n::Backend::Locale.available_locales + end - # lookup key in cache and db, if the db is hit the value is cached - def lookup(locale, key) - cache_key = Translation.ck(locale, key) - if @cache_store.exist?(cache_key) && value = @cache_store.read(cache_key) - return value - else - translations = locale.translations.find_all_by_key(Translation.hk(key)) - case translations.size - when 0 - value = nil - when 1 - value = translations.first.value_or_default - else - value = translations.inject([]) do |values, t| - values[t.pluralization_index] = t.value_or_default - values - end - end - - @cache_store.write(cache_key, (value.nil? ? nil : value)) - return value + def reload! + # get's called on initialization + # let's not do anything yet + end + + # lookup key in cache and db, if the db is hit the value is cached + def lookup(locale, key) + cache_key = Translation.ck(locale, key) + if @cache_store.exist?(cache_key) && value = @cache_store.read(cache_key) + return value + else + translations = locale.translations.find_all_by_key(Translation.hk(key)).select{|translation| translation.raw_key.starts_with?(key.to_s)} + case translations.size + when 0 + value = nil + when 1 + value = translations.first.value_or_default + else + value = translations.inject([]) do |values, t| + values[t.pluralization_index] = t.value_or_default + values end end + @cache_store.write(cache_key, (value.nil? ? nil : value)) + return value + end + end - # looks up translations for the default locale, and if they exist untranslated records are created for the locale and the default locale values are returned - def use_and_copy_default_locale_translations_if_they_exist(locale, key) - default_locale_entry = lookup(Locale.default_locale, key) - return unless default_locale_entry - - if default_locale_entry.is_a?(Array) - default_locale_entry.each_with_index do |entry, index| - locale.create_translation(key, nil, index) if entry - end - else - locale.create_translation(key, nil) - end + protected + # keep a local copy of the locale in context for use within the translation + # routine, and also accept an arbitrary locale for one time locale lookups + def locale_in_context(locale) + return @locale if @locale && @locale.code == locale.to_s + return @locale unless locale.class == String || locale.class == Symbol + @locale = I18n::Backend::Locale.find_or_create_by_code(locale.to_s) + raise I18n::InvalidLocale.new(locale) unless @locale + end - return default_locale_entry - end + # looks up translations for the default locale, and if they exist untranslated records are created for the locale and the default locale values are returned + def use_and_copy_default_locale_translations_if_they_exist(locale, key) + default_locale_entry = lookup(I18n::Backend::Locale.default_locale, key) + return unless default_locale_entry - def pluralize(locale, entry, count) - return entry unless entry.is_a?(Array) and count - count = count == 1 ? 1 : 0 - entry.compact[count] + if default_locale_entry.is_a?(Array) + default_locale_entry.each_with_index do |entry, index| + locale.create_translation(key, nil, index) if entry end + else + locale.create_translation(key, nil) + end - # Interpolates values into a given string. - # - # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X' - # # => "file test.txt opened by {{user}}" - # - # Note that you have to double escape the \\ when you want to escape - # the {{...}} key in a string (once for the string and once for the - # interpolation). - def interpolate(locale, string, values = {}) - return string unless string.is_a?(String) - - if string.respond_to?(:force_encoding) - original_encoding = string.encoding - string.force_encoding(Encoding::BINARY) - end + return default_locale_entry + end - result = string.gsub(MATCH) do - escaped, pattern, key = $1, $2, $2.to_sym - - if escaped - pattern - elsif INTERPOLATION_RESERVED_KEYS.include?(pattern) - raise ReservedInterpolationKey.new(pattern, string) - elsif !values.include?(key) - raise MissingInterpolationArgument.new(pattern, string) - else - values[key].to_s - end - end + def pluralize(locale, entry, count) + return entry unless entry.is_a?(Array) and count + count = count == 1 ? 1 : 0 + entry.compact[count] + end - result.force_encoding(original_encoding) if original_encoding - result + # Interpolates values into a given string. + # + # interpolate "file %{file} opened by \\%{user}", :file => 'test.txt', :user => 'Mr. X' + # # => "file test.txt opened by %{user}" + # + # Note that you have to double escape the \\ when you want to escape + # the %{...} key in a string (once for the string and once for the + # interpolation). + def interpolate(locale, string, values = {}) + return string unless string.is_a?(String) + + string = string.dup # It returns an error if not duplicated + original_encoding = string.encoding + + result = string.gsub(MATCH) do + escaped, pattern, key = $1, $2, $2.to_sym + if escaped + pattern + elsif INTERPOLATION_RESERVED_KEYS.include?(pattern) + raise ReservedInterpolationKey.new(pattern, string) + elsif !values.include?(key) + raise MissingInterpolationArgument.new(pattern, string) + else + values[key].to_s.force_encoding(original_encoding) end + end - def strip_root_key(root_key, key) - return nil if key.nil? - return key.gsub(/^#{root_key}\./, '') - end + result + end - def hashify_record_array(root_key, record_array) - return nil if record_array.nil? || record_array.empty? - - #Make sure that all of our records have raw_keys - record_array.reject! {|record| record.raw_key.nil?} - - # Start building our return hash - result = {} - record_array.each { |record| - key = strip_root_key(root_key, record.raw_key) - next unless key.present? - - # If we contain a period delimiter, we need to add a sub-hash. - # Otherwise, we just insert the value at this level. - if key.index(".") - internal_node = key.slice(0, key.index('.')) - new_root = root_key + '.' + internal_node - new_record_array = record_array.select {|record| record.raw_key.starts_with? new_root} - result[internal_node.to_sym] = hashify_record_array(new_root, new_record_array) - else - value = record.value - value = value.to_i if value == "0" || value.to_i != 0 #simple integer cast - result[key.to_sym] = value - end - } - result - end + def strip_root_key(root_key, key) + return nil if key.nil? + return key.gsub(/^#{root_key}\./, '') + end + def hashify_record_array(root_key, record_array) + return nil if record_array.nil? || record_array.empty? + + #Make sure that all of our records have raw_keys + record_array.reject! {|record| record.raw_key.nil?} + + # Start building our return hash + result = {} + record_array.each do |record| + key = strip_root_key(root_key, record.raw_key) + next unless key.present? + + # If we contain a period delimiter, we need to add a sub-hash. + # Otherwise, we just insert the value at this level. + if key.index(".") + next if (internal_node = key.slice(0, key.index('.'))).blank? + + new_root = root_key + '.' + internal_node + new_record_array = record_array.select {|record| record.raw_key.starts_with? new_root} + result[internal_node.to_sym] = hashify_record_array(new_root, new_record_array) + else + value = record.value + value = value.to_i if value == "0" || value.to_i != 0 #simple integer cast + result[key.to_sym] = value + end + end + result unless result.empty? end end end diff --git a/lib/i18n_backend_database/engine.rb b/lib/i18n_backend_database/engine.rb new file mode 100644 index 0000000..2cd1548 --- /dev/null +++ b/lib/i18n_backend_database/engine.rb @@ -0,0 +1,16 @@ +module I18nBackendDatabase + class Engine < Rails::Engine + + initializer "i18n_backend_database.initialize" do |app| + I18n.backend = I18n::Backend::Database.new + end + + initializer "common.init" do |app| + # Publish #{root}/public path so it can be included at the app level + if app.config.serve_static_assets + app.config.middleware.use ::ActionDispatch::Static, "#{root}/public" + end + end + + end +end diff --git a/lib/i18n_backend_database/version.rb b/lib/i18n_backend_database/version.rb new file mode 100644 index 0000000..2061748 --- /dev/null +++ b/lib/i18n_backend_database/version.rb @@ -0,0 +1,3 @@ +module I18nBackendDatabase + VERSION = '0.1.0' +end diff --git a/lib/i18n_util.rb b/lib/i18n_util.rb index 42fb3c6..77944a2 100644 --- a/lib/i18n_util.rb +++ b/lib/i18n_util.rb @@ -1,92 +1,158 @@ class I18nUtil - # Create tanslation records from the YAML file. Will create the required locales if they do not exist. - def self.load_from_yml(file_name) - data = YAML::load(IO.read(file_name)) - data.each do |code, translations| - locale = Locale.find_or_create_by_code(code) - backend = I18n::Backend::Simple.new - keys = extract_i18n_keys(translations) - keys.each do |key| - value = backend.send(:lookup, code, key) - - pluralization_index = 1 - - if key.ends_with?('.one') - key.gsub!('.one', '') - end + DEFAULT_TRANSLATION_PATH = 'config/translations' - if key.ends_with?('.other') - key.gsub!('.other', '') - pluralization_index = 0 - end + @@verbose,@@current_load_source = nil,nil + + def self.verbose? + @@verbose + end + + def self.verbose=(value) + @@verbose = value + end - if value.is_a?(Array) - value.each_with_index do |v, index| - create_translation(locale, "#{key}", index, v) unless v.nil? + def self.current_load_source(ensure_saved = true) + @@current_load_source.save! if @@current_load_source and ensure_saved + @@current_load_source + end + + def self.set_current_load_source(path) + path = path.to_s[(Rails.root.to_s.length + 1)..-1] if path.to_s.index(Rails.root.to_s) == 0 + @@current_load_source = TranslationSource.find_by_path(path) || TranslationSource.new(:path => path) unless @@current_load_source and @@current_load_source.path == path + if block_given? + yield + @@current_load_source = nil + end + @@current_load_source + end + + def self.load_default_locales(path = nil) + path ||= 'config/locales.yml' + puts "LOAD LOCALES: #{path}" if verbose? + raise 'Locales file not found' unless (full_path = Rails.root + path).exist? + + YAML::load_file(full_path).each do |code, options| + raise "name for locale code #{code} is blank!" if (name = options['name']).blank? + + if (locale = I18n::Backend::Locale.where(:code => code).first).nil? + puts "...CREATE - #{code} - #{name}" if verbose? + I18n::Backend::Locale.create!(:code => code, :name => name) + elsif locale.name == name + puts "...EXISTS - #{code} - #{name}" if verbose? + else + puts "...UPDATE - #{code} - #{name}" if verbose? + locale.update_attributes!(:name => name) + end + end + end + + def self.load_default_localizations + I18nUtil.load_from_yml File.join(File.dirname(__FILE__), "../data", "default_localized_translations.yml") + end + + # Create tanslation records from the YAML file. Will create the required locales if they do not exist. + def self.load_from_yml(file_name) + set_current_load_source(file_name) do + puts "LOAD YAML: #{file_name}" if verbose? + data = YAML::load(IO.read(file_name)) + data.each do |code, translations| + if locale = I18n::Backend::Locale.find_by_code(code) + translations_array = extract_translations_from_hash(translations) + translations_array.each do |key, value| + pluralization_index = 1 + key.gsub!('.one', '') if key.ends_with?('.one') + if key.ends_with?('.other') + key.gsub!('.other', '') + pluralization_index = 0 + end + if value.is_a?(Array) + value.each_with_index do |v, index| + create_translation(locale, key, index, v) unless v.nil? + end + else + create_translation(locale, key, pluralization_index, value) + end end - else - create_translation(locale, key, pluralization_index, value) end - end end end # Finds or creates a translation record and updates the value def self.create_translation(locale, key, pluralization_index, value) - translation = locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) # find existing record by hash key - unless translation # or build new one with raw key - translation = locale.translations.build(:key =>key, :pluralization_index => pluralization_index) - puts "from yaml create translation for #{locale.code} : #{key} : #{pluralization_index}" unless RAILS_ENV['test'] + if translation = locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) # find existing record by hash key + puts "...UPDATE #{locale.code} : #{key} : #{pluralization_index}" if verbose? + elsif locale != (default_locale = I18n::Backend::Locale.default_locale) and not default_locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) + puts "...SKIP #{locale.code} : #{key} : #{pluralization_index}" if verbose? + else + translation = locale.translations.build(:key => key, :pluralization_index => pluralization_index) + puts "...ADD #{locale.code} : #{key} : #{pluralization_index}" if verbose? + end + if translation + translation.value = value + translation.source = current_load_source + translation.save! end - translation.value = value - translation.save! end - def self.extract_i18n_keys(hash, parent_keys = []) - hash.inject([]) do |keys, (key, value)| + def self.extract_translations_from_hash(hash, parent_keys = []) + (hash || {}).inject([]) do |keys, (key, value)| full_key = parent_keys + [key] if value.is_a?(Hash) # Nested hash - keys += extract_i18n_keys(value, full_key) + keys += extract_translations_from_hash(value, full_key) elsif !value.nil? # String leaf node - keys << full_key.join(".") + keys << [full_key.join("."), value] end keys end end - # Create translation records for all existing locales from translation calls with the application. Ignores errors from tranlations that require objects. + # Create translation records for all existing locales from translation calls with the application. + # Ignores errors from tranlations that require objects. def self.seed_application_translations(dir='app') - translated_objects(dir).each do |object| - interpolation_arguments= object.scan(/\{\{(.*?)\}\}/).flatten - object = object[/'(.*?)'/, 1] || object[/"(.*?)"/, 1] - options = {} - interpolation_arguments.each { |arg| options[arg.to_sym] = nil } - next if object.nil? - - puts "translating for #{object} with options #{options.inspect}" unless RAILS_ENV['test'] - I18n.t(object, options) # default locale first - locales = Locale.available_locales - locales.delete(I18n.default_locale) - # translate for other locales - locales.each do |locale| - I18n.t(object, options.merge(:locale => locale)) + last_source = nil + translated_objects(dir).each do |match,source| + if match =~ /#\{/ # skip any strings that have substitution patterns + puts "WARNING: MATCH:#{match} -- CONTAINS A RUBY SUBSTITUTION IN #{source.path}" + next + end + next if I18n::Backend::Locale.default_locale.translations.find_by_key_and_pluralization_index(Translation.hk(match),1) + + begin + interpolation_arguments= match.scan(/\%\{(.*?)\}/).flatten + options = interpolation_arguments.inject({}) { |options,arg| options[arg.to_sym] = nil; options } + + puts "SOURCE: #{source.path}" if verbose? and source != last_source + set_current_load_source((last_source = source).full_path.to_s) + I18n.t(match, options) # default locale first + rescue + puts "WARNING:#{$!} MATCH:#{match} OPTIONS:#{options} ARGS:#{interpolation_arguments} IN #{source.path}" end end end - def self.translated_objects(dir='app') + def self.translated_objects(dir=nil) assets = [] + dir ||= 'app' Dir.glob("#{dir}/*").each do |item| if File.directory?(item) assets += translated_objects(item) unless item.ends_with?('i18n_backend_database') # ignore self - else - File.readlines(item).each do |l| - assets += l.scan(/I18n.t\((.*?)\)/).flatten + elsif item.ends_with?('.rb') || item.ends_with?('.erb') || item.ends_with?('.rhtml') # TODO what about things like HAML? + set_current_load_source(item) do + File.readlines(item).each_with_index do |line,index| + begin + assets += line.scan(/(I18n\.t|\Wt)(\(|\s*)'([^']*)'/).collect{|pair| [pair.last,current_load_source(false)]} + assets += line.scan(/(I18n\.t|\Wt)(\(|\s*)"([^"]*)"/).collect{|pair| [pair.last,current_load_source(false)]} + assets += line.scan(/(I18n\.t|\Wt)(\(|\s*)%\(([^\)]*)\)/).collect{|pair| [pair.last,current_load_source(false)]} + assets += line.scan(/(I18n\.t|\Wt)(\(|\s*)%\[([^\]]*)\]/).collect{|pair| [pair.last,current_load_source(false)]} + rescue + puts "WARNING:#{$!} in file #{item} with line '#{line}'" + end + end end end end @@ -94,20 +160,25 @@ def self.translated_objects(dir='app') end # Populate translation records from the default locale to other locales if no record exists. - def self.synchronize_translations - non_default_locales = Locale.non_defaults - Locale.default_locale.translations.each do |t| + def self.synchronize_translations(exclusions) + set_current_load_source(nil) + non_default_locales = I18n::Backend::Locale.non_defaults + puts "CHECKING FOR MISSES - #{non_default_locales.collect{|locale| locale.code}}" if verbose? + I18n::Backend::Locale.default_locale.translations.each do |translation| + next if exclusions.detect{|pattern| translation.raw_key =~ pattern} + non_default_locales.each do |locale| - unless locale.translations.exists?(:key => t.key, :pluralization_index => t.pluralization_index) - value = t.value =~ /^---(.*)\n/ ? t.value : nil # well will copy across YAML, like symbols - locale.translations.create!(:key => t.raw_key, :value => value, :pluralization_index => t.pluralization_index) - puts "synchronizing has created translation for #{locale.code} : #{t.raw_key} : #{t.pluralization_index}" unless RAILS_ENV['test'] + unless locale.translations.exists?(:key => translation.key, :pluralization_index => translation.pluralization_index) + value = translation.value =~ /^---(.*)\n/ ? translation.value : nil # well will copy across YAML, like symbols + locale.translations.create!(:key => translation.raw_key, :value => value, :pluralization_index => translation.pluralization_index) + puts "...MISSING #{locale.code} : #{translation.raw_key} : #{translation.pluralization_index}" if verbose? end end end end def self.google_translate + set_current_load_source(nil) Locale.non_defaults.each do |locale| locale.translations.untranslated.each do |translation| default_locale_value = translation.default_locale_value @@ -116,14 +187,15 @@ def self.google_translate if interpolation_arguments.empty? translation.value = GoogleLanguage.translate(default_locale_value, locale.code, Locale.default_locale.code) + translation.source = current_load_source translation.save! else placeholder_value = 990 # at least in :es it seems to leave a 3 digit number in the postion on the string placeholders = {} - # replace {{interpolation_arguments}} with a numeric place holder + # replace %{interpolation_arguments} with a numeric place holder interpolation_arguments.each do |interpolation_argument| - default_locale_value.gsub!("{{#{interpolation_argument}}}", "#{placeholder_value}") + default_locale_value.gsub!("%{#{interpolation_argument}}", "#{placeholder_value}") placeholders[placeholder_value] = interpolation_argument placeholder_value += 1 end @@ -131,9 +203,10 @@ def self.google_translate # translate string translated_value = GoogleLanguage.translate(default_locale_value, locale.code, Locale.default_locale.code) - # replace numeric place holders with {{interpolation_arguments}} - placeholders.each {|placeholder_value,interpolation_argument| translated_value.gsub!("#{placeholder_value}", "{{#{interpolation_argument}}}") } + # replace numeric place holders with %{interpolation_arguments} + placeholders.each {|placeholder_value,interpolation_argument| translated_value.gsub!("#{placeholder_value}", "%{#{interpolation_argument}}") } translation.value = translated_value + translation.source = current_load_source translation.save! end end @@ -145,4 +218,67 @@ def self.needs_human_eyes?(value) return true if value.index('%') # date formats return true if value =~ /^---(.*)\n/ # YAML end -end \ No newline at end of file + + def self.process_translation_locales(locales_codes, &action) + if locales_codes.empty? + puts 'Nothing to do.' + else + locales_codes.each do | locale_code | + raise "Locale '#{locale_code}' not found" unless locale = I18n::Backend::Locale.find_by_code(locale_code) + action.call(locale) + end + end + end + + def self.export_translations(locale,suffix = '.yml') + puts "EXPORTING - #{locale.code}" if verbose? + translation_path = "#{DEFAULT_TRANSLATION_PATH}/#{locale.code}#{suffix}" + source_options = ['source_id is null'] + if previous_translations_source = TranslationSource.find_by_path(translation_path) + source_options << "source_id = #{previous_translations_source.id}" + end + + set_current_load_source(full_path = Rails.root + translation_path) do + full_path.dirname.mkdir unless full_path.dirname.exist? + + translations = Translation.where(:locale_id => locale.id).where(source_options.join(' or ')) + return puts "No translations found for '#{locale.code}'" if translations.empty? + + exports,blank_count = [],0 + translations.each do |translation| + if translation.value.blank? + blank_count += 1 + else + puts "...EXPORT - #{translation.raw_key} - #{translation.pluralization_index}" if verbose? + exports << {'key' => translation.raw_key,'value' => translation.value,'pluralization_index' => translation.pluralization_index} + translation.source = current_load_source + translation.save! + end + end + + puts "#{translations.length - blank_count} EXPORTED..." if verbose? + puts "WARNING: #{blank_count} BLANKS FOUND!" if verbose? + + `cp #{full_path} #{full_path}.bak-#{Time.now.strftime('%Y%m%d%H%M%S')}` if File.exists?(full_path) + File.open(full_path,'w'){|file| file.write exports.to_yaml} + end + + end + + def self.import_translations(locale) + puts "IMPORTING - #{locale.code}" if verbose? + full_path = Rails.root + "#{DEFAULT_TRANSLATION_PATH}/#{locale.code}.yml" + return puts "No translation file found for '#{locale.code}'" unless full_path.exist? + + set_current_load_source(full_path) do + return puts "No translations found for '#{locale.code}'" unless translations = YAML::load_file(full_path) + + translations.each do |translation| + create_translation(locale,translation['key'],translation['pluralization_index'],translation['value'].blank? ? nil : translation['value']) + end + + puts "#{translations.length} IMPORTED..." if verbose? + end + end + +end diff --git a/lib/models/locale.rb b/lib/models/locale.rb deleted file mode 100644 index 5f4929a..0000000 --- a/lib/models/locale.rb +++ /dev/null @@ -1,67 +0,0 @@ -class Locale < ActiveRecord::Base - validates_presence_of :code - validates_uniqueness_of :code - - has_many :translations, :dependent => :destroy - named_scope :non_defaults, :conditions => ["code != ?", I18n.default_locale.to_s] - - #named_scope :english, lambda { |m| { return Hash.new if m.nil?; :conditions => "locales.locale = '#{m}'" } } -# named_scope :in_city, lambda { |m| { return {} if m.nil?; :joins => [cities], :conditions => "cities.name = '#{m}' } } - - - @@default_locale = nil - - def self.default_locale - @@default_locale ||= self.find(:first, :conditions => {:code => I18n.default_locale.to_s}) - end - - def self.reset_default_locale - @@default_locale = nil - end - - def translation_from_key(key) - self.translations.find(:first, :conditions => {:key => Translation.hk(key)}) - end - - def create_translation(key, value, pluralization_index=1) - conditions = {:key => key, :raw_key => key.to_s, :pluralization_index => pluralization_index} - - # set the key as the value if we're using the default locale and the key is a string - conditions.merge!({:value => value}) if (self.code == I18n.default_locale.to_s && key.is_a?(String)) - translation = self.translations.create(conditions) - - # hackity hack. bug #922 maybe? - self.connection.commit_db_transaction unless RAILS_ENV['test'] - translation - end - - def find_translation_or_copy_from_default_locale(key, pluralization_index) - self.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) || copy_from_default(key, pluralization_index) - end - - def copy_from_default(key, pluralization_index) - if !self.default_locale? && Locale.default_locale.has_translation?(key, pluralization_index) - create_translation(key, key, pluralization_index) - end - end - - def has_translation?(key, pluralization_index=1) - self.translations.exists?(:key => Translation.hk(key), :pluralization_index => pluralization_index) - end - - def percentage_translated - (self.translations.translated.count.to_f / self.translations.count.to_f * 100).round rescue 100 - end - - def self.available_locales - all.map(&:code).map(&:to_sym) rescue [] - end - - def default_locale? - self == Locale.default_locale - end - - def to_param - self.code - end -end diff --git a/lib/models/translation.rb b/lib/models/translation.rb deleted file mode 100644 index 4817341..0000000 --- a/lib/models/translation.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'digest/md5' - -class Translation < ActiveRecord::Base - belongs_to :locale - validates_presence_of :key - before_validation_on_create :generate_hash_key - after_update :update_cache - - named_scope :untranslated, :conditions => {:value => nil}, :order => :raw_key - named_scope :translated, :conditions => "value IS NOT NULL", :order => :raw_key - - def default_locale_value(rescue_value='No default locale value') - begin - Locale.default_locale.translations.find_by_key_and_pluralization_index(self.key, self.pluralization_index).value - rescue - rescue_value - end - end - - def value_or_default - value = self.value || self.default_locale_value(self.raw_key) - value =~ /^---(.*)\n/ ? YAML.load(value) : value # supports using YAML e.g. order: [ :year, :month, :day ] values are stored as Symbols "--- :year\n", "--- :month\n", "--- :day\n" - end - - # create hash key - def self.hk(key) - Base64.encode64(Digest::MD5.hexdigest(key.to_s)).gsub(/\n/, '') - end - - # create cache key - def self.ck(locale, key, hash=true) - key = self.hk(key) if hash - "#{locale.code}:#{key}" - end - - protected - def generate_hash_key - self.raw_key = key.to_s - self.key = Translation.hk(key) - end - - def update_cache - new_cache_key = Translation.ck(self.locale, self.key, false) - I18n.backend.cache_store.write(new_cache_key, self.value) - end -end diff --git a/lib/public/images/custom1_bar.gif b/lib/public/images/custom1_bar.gif deleted file mode 100755 index 57d6309..0000000 Binary files a/lib/public/images/custom1_bar.gif and /dev/null differ diff --git a/lib/public/images/custom1_box.gif b/lib/public/images/custom1_box.gif deleted file mode 100755 index 30cbe1c..0000000 Binary files a/lib/public/images/custom1_box.gif and /dev/null differ diff --git a/lib/public/images/percentImage.png b/lib/public/images/percentImage.png deleted file mode 100755 index c528d65..0000000 Binary files a/lib/public/images/percentImage.png and /dev/null differ diff --git a/lib/public/images/percentImage_back.png b/lib/public/images/percentImage_back.png deleted file mode 100755 index 30e17a9..0000000 Binary files a/lib/public/images/percentImage_back.png and /dev/null differ diff --git a/lib/public/images/percentImage_back1.png b/lib/public/images/percentImage_back1.png deleted file mode 100755 index 794cb49..0000000 Binary files a/lib/public/images/percentImage_back1.png and /dev/null differ diff --git a/lib/public/images/percentImage_back2.png b/lib/public/images/percentImage_back2.png deleted file mode 100755 index 6ff3467..0000000 Binary files a/lib/public/images/percentImage_back2.png and /dev/null differ diff --git a/lib/public/images/percentImage_back3.png b/lib/public/images/percentImage_back3.png deleted file mode 100755 index 389a777..0000000 Binary files a/lib/public/images/percentImage_back3.png and /dev/null differ diff --git a/lib/public/images/percentImage_back4.png b/lib/public/images/percentImage_back4.png deleted file mode 100755 index 76b2874..0000000 Binary files a/lib/public/images/percentImage_back4.png and /dev/null differ diff --git a/lib/public/javascripts/jsProgressBarHandler.js b/lib/public/javascripts/jsProgressBarHandler.js deleted file mode 100755 index 534e3b5..0000000 --- a/lib/public/javascripts/jsProgressBarHandler.js +++ /dev/null @@ -1,509 +0,0 @@ -/***************************************************************** - * - * jsProgressBarHandler 0.3.3 - by Bramus! - http://www.bram.us/ - * - * v 0.3.3 - 2008.11.10 - UPD: fixed IE compatibility issue (thanks Kevin - Sep 19 2008 / 6pm) - * - UPD: setPercentage now parses the targetPercentage to an Integer to avoid infinite loop (thanks Jack - Sep 07 2008 / 9pm) - * - UPD: Moved from Event.Observe(window, 'load', fn) to document.observe('dom:loaded', fn) in order to force people to use an up to date Prototype release. - * - UPD: setPercentage now takes an overrideQueue param. If set the current queue is cleared. - * - ADD: Added onTick callback event which gets called when the percentage is updated. - * - ADD: Added stable (as in "non-crashing") versions of the additions which first surfaced in the (unreleased) 0.3.2 release - * Preloading support partially implemented in IE as all versions (IE6,7&8) are quite hard to tame (one time they work, the next reload they don't anymore) - * v 0.3.2 - 2008.04.09 (*UNRELEASED*) - * - ADD: implemented preloading of images to avoid slight flicker when switching images (BUGGY!) - * - ADD: percentage image now has class percentImage and percentage Text now has class percentText; This allows you to style the output easily. - * v 0.3.1 - 2008.02.20 - UPD: fixed queue bug when animate was set to false (thanks Jamie Chong) - * - UPD: update Prototype to version 1.6.0.2 - * v 0.3.0 - 2008.02.01 - ADD: animation queue, prevents from the progressbar getting stuck when multiple calls are made during an animation - * - UPD: multiple barImages now work properly in Safari - * v 0.2.1 - 2007.12.20 - ADD: option : set boxImage - * ADD: option : set barImage (one or more) - * ADD: option : showText - * v 0.2 - 2007.12.13 - SYS: rewrite in 2 classs including optimisations - * ADD: Config options - * v 0.1 - 2007.08.02 - initial release - * - * @see http://www.barenakedapp.com/the-design/displaying-percentages on how to create a progressBar Background Image! - * - * Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/ - * - *****************************************************************/ - - - /** - * CONFIG - * ------------------------------------------------------------- - */ - - // Should jsProgressBarHandler hook itself to all span.progressBar elements? - default : true - var autoHook = true; - - // Default Options - var defaultOptions = { - animate : true, // Animate the progress? - default: true - showText : true, // show text with percentage in next to the progressbar? - default : true - width : 120, // Width of the progressbar - don't forget to adjust your image too!!! - barImage : Array( - 'images/percentImage_back4.png', - 'images/percentImage_back3.png', - 'images/percentImage_back2.png', - 'images/percentImage_back1.png' - ), - boxImage : 'images/percentImage.png', // boxImage : image around the progress bar - /*barImage : 'images/percentImage_back.png',*/ // Image to use in the progressbar. Can be an array of images too. - height : 12, // Height of the progressbar - don't forget to adjust your image too!!! - onTick : function(pbObj) { return true } - } - - /** - * NO NEED TO CHANGE ANYTHING BENEATH THIS LINE - * ------------------------------------------------------------- - */ - - /** - * JS_BRAMUS Object - * ------------------------------------------------------------- - */ - - if (!JS_BRAMUS) { var JS_BRAMUS = new Object(); } - - - /** - * ProgressBar Class - * ------------------------------------------------------------- - */ - - JS_BRAMUS.jsProgressBar = Class.create(); - - JS_BRAMUS.jsProgressBar.prototype = { - - - /** - * Datamembers - * ------------------------------------------------------------- - */ - - el : null, // Element where to render the progressBar in - id : null, // Unique ID of the progressbar - percentage : null, // Percentage of the progressbar - - options : null, // The options - - initialPos : null, // Initial postion of the background in the progressbar - initialPerc : null, // Initial percentage the progressbar should hold - pxPerPercent : null, // Number of pixels per 1 percent - - backIndex : null, // index in the array of background images currently used - numPreloaded : null, // number of images preloaded - - running : null, // is this one running (being animated) or not? - - queue : false, // queue of percentages to set to - - - /** - * Constructor - * - * @param HTMLElement el - * @param string id - * @param int percentage - * @return void - * ------------------------------------------------------------- - */ - - initialize : function(el, percentage, options) { - - // get the options - this.options = Object.clone(defaultOptions); - Object.extend(this.options, options || {}); - - // datamembers from arguments - this.el = $(el); - this.id = $(el).id; - this.percentage = 0; // Set to 0 intially, we'll change this later. - this.backIndex = 0; // Set to 0 initially - this.numPreloaded = 0; // Set to 0 initially - this.running = false; // Set to false initially - this.queue = Array(); // Set to empty Array initially - - // datamembers which are calculatef - this.imgWidth = this.options.width * 2; // define the width of the image (twice the width of the progressbar) - this.initialPos = this.options.width * (-1); // Initial postion of the background in the progressbar (0% is the middle of our image!) - this.pxPerPercent = this.options.width / 100; // Define how much pixels go into 1% - this.initialPerc = percentage; // Store this, we'll need it later. - - // enfore backimage array - if (this.options.barImage.constructor != Array) { // used to be (but doesn't work in Safari): if (this.options.barImage.constructor.toString().indexOf("Array") == -1) { - this.options.barImage = Array(this.options.barImage); - } - - // preload Images - this.preloadImages(); - - }, - - - /** - * Preloads the images needed for the progressbar - * - * @return void - * ------------------------------------------------------------- - */ - - preloadImages : function() { - - // loop all barimages - for (i = 0; i < this.options.barImage.length; i++) { - - // create new image ref - var newImage = null; - newImage = new Image(); - - // set onload, onerror and onabort functions - newImage.onload = function() { this.numPreloaded++; }.bind(this); - newImage.onerror = function() { this.numPreloaded++; }.bind(this); - newImage.onabort = function() { this.numPreloaded++; }.bind(this); - - // set image source (preload it!) - newImage.src = this.options.barImage[i]; - - // image is in cache - if (newImage.complete) { - this.numPreloaded++; - } - - } - - // if not IE, check if they're loaded - if (!Prototype.Browser.IE) { - this.checkPreloadedImages(); - - // if IE, just init the visuals as it's quite hard to tame all IE's - } else { - this.initVisuals(); - } - - }, - - - /** - * Check whether all images are preloaded and loads the percentage if so - * - * @return void - * ------------------------------------------------------------- - */ - - checkPreloadedImages : function() { - - // all images are loaded, go init the visuals - if (parseInt(this.numPreloaded,10) >= parseInt(this.options.barImage.length,10) ) { - - // initVisuals - this.initVisuals(); - - // not all images are loaded ... wait a little and then retry - } else { - - if ( parseInt(this.numPreloaded,10) <= parseInt(this.options.barImage.length,10) ) { - // $(this.el).update(this.id + ' : ' + this.numPreloaded + '/' + this.options.barImage.length); - setTimeout(function() { this.checkPreloadedImages(); }.bind(this), 100); - } - - } - - }, - - - /** - * Intializes the visual output and sets the percentage - * - * @return void - * ------------------------------------------------------------- - */ - - initVisuals : function () { - - // create the visual aspect of the progressBar - $(this.el).update( - '0%' + - ((this.options.showText == true)?'0%':'')); - - // set the percentage - this.setPercentage(this.initialPerc); - }, - - - /** - * Sets the percentage of the progressbar - * - * @param string targetPercentage - * @param boolen clearQueue - * @return void - * ------------------------------------------------------------- - */ - setPercentage : function(targetPercentage, clearQueue) { - - // if clearQueue is set, empty the queue and then set the percentage - if (clearQueue) { - - this.percentage = (this.queue.length != 0) ? this.queue[0] : targetPercentage; - this.timer = null; - this.queue = []; - - setTimeout(function() { this.setPercentage(targetPercentage); }.bind(this), 10); - - // no clearQueue defined, set the percentage - } else { - - // add the percentage on the queue - this.queue.push(targetPercentage); - - // process the queue (if not running already) - if (this.running == false) { - this.processQueue(); - } - } - - }, - - - /** - * Processes the queue - * - * @return void - * ------------------------------------------------------------- - */ - - processQueue : function() { - - // stuff on queue? - if (this.queue.length > 0) { - - // tell the world that we're busy - this.running = true; - - // process the entry - this.processQueueEntry(this.queue[0]); - - // no stuff on queue - } else { - - // return; - return; - - } - - }, - - - /** - * Processes an entry from the queue (viz. animates it) - * - * @param string targetPercentage - * @return void - * ------------------------------------------------------------- - */ - - processQueueEntry : function(targetPercentage) { - - // get the current percentage - var curPercentage = parseInt(this.percentage,10); - - // define the new percentage - if ((targetPercentage.toString().substring(0,1) == "+") || (targetPercentage.toString().substring(0,1) == "-")) { - targetPercentage = curPercentage + parseInt(targetPercentage); - } - - // min and max percentages - if (targetPercentage < 0) targetPercentage = 0; - if (targetPercentage > 100) targetPercentage = 100; - - // if we don't need to animate, just change the background position right now and return - if (this.options.animate == false) { - - // remove the entry from the queue - this.queue.splice(0,1); // @see: http://www.bram.us/projects/js_bramus/jsprogressbarhandler/#comment-174878 - - // Change the background position (and update this.percentage) - this._setBgPosition(targetPercentage); - - // call onTick - if (!this.options.onTick(this)) { - return; - } - - // we're not running anymore - this.running = false; - - // continue processing the queue - this.processQueue(); - - // we're done! - return; - } - - // define if we need to add/subtract something to the current percentage in order to reach the target percentage - if (targetPercentage != curPercentage) { - if (curPercentage < targetPercentage) { - newPercentage = curPercentage + 1; - } else { - newPercentage = curPercentage - 1; - } - callTick = true; - } else { - newPercentage = curPercentage; - callTick = false; - } - - // Change the background position (and update this.percentage) - this._setBgPosition(newPercentage); - - // call onTick - if (callTick && !this.options.onTick(this)) { - return; - } - - // Percentage not reached yet : continue processing entry - if (curPercentage != newPercentage) { - - this.timer = setTimeout(function() { this.processQueueEntry(targetPercentage); }.bind(this), 10); - - // Percentage reached! - } else { - - // remove the entry from the queue - this.queue.splice(0,1); - - // we're not running anymore - this.running = false; - - // unset timer - this.timer = null; - - // process the rest of the queue - this.processQueue(); - - // we're done! - return; - } - - }, - - - /** - * Gets the percentage of the progressbar - * - * @return int - */ - getPercentage : function(id) { - return this.percentage; - }, - - - /** - * Set the background position - * - * @param int percentage - */ - _setBgPosition : function(percentage) { - // adjust the background position - $(this.id + "_percentImage").style.backgroundPosition = (this.initialPos + (percentage * this.pxPerPercent)) + "px 50%"; - - // adjust the background image and backIndex - var newBackIndex = Math.floor((percentage-1) / (100/this.options.barImage.length)); - - if ((newBackIndex != this.backIndex) && (this.options.barImage[newBackIndex] != undefined)) { - $(this.id + "_percentImage").style.backgroundImage = "url(" + this.options.barImage[newBackIndex] + ")"; - } - - this.backIndex = newBackIndex; - - // Adjust the alt & title of the image - $(this.id + "_percentImage").alt = percentage + "%"; - $(this.id + "_percentImage").title = percentage + "%"; - - // Update the text - if (this.options.showText == true) { - $(this.id + "_percentText").update("" + percentage + "%"); - } - - // adjust datamember to stock the percentage - this.percentage = percentage; - } - } - - - /** - * ProgressHandlerBar Class - automatically create ProgressBar instances - * ------------------------------------------------------------- - */ - - JS_BRAMUS.jsProgressBarHandler = Class.create(); - - - JS_BRAMUS.jsProgressBarHandler.prototype = { - - - /** - * Datamembers - * ------------------------------------------------------------- - */ - - pbArray : new Array(), // Array of progressBars - - - /** - * Constructor - * - * @return void - * ------------------------------------------------------------- - */ - - initialize : function() { - - // get all span.progressBar elements - $$('span.progressBar').each(function(el) { - - // create a progressBar for each element - this.pbArray[el.id] = new JS_BRAMUS.jsProgressBar(el, parseInt(el.innerHTML.replace("%",""))); - - }.bind(this)); - }, - - - /** - * Set the percentage of a progressbar - * - * @param string el - * @param string percentage - * @return void - * ------------------------------------------------------------- - */ - setPercentage : function(el, percentage, clearQueue) { - this.pbArray[el].setPercentage(percentage, clearQueue); - }, - - - /** - * Get the percentage of a progressbar - * - * @param string el - * @return int percentage - * ------------------------------------------------------------- - */ - getPercentage : function(el) { - return this.pbArray[el].getPercentage(); - } - - } - - - /** - * ProgressHandlerBar Class - hook me or not? - * ------------------------------------------------------------- - */ - - if (autoHook == true) { - function initProgressBarHandler() { myJsProgressBarHandler = new JS_BRAMUS.jsProgressBarHandler(); } - document.observe('dom:loaded', initProgressBarHandler, false); - } diff --git a/lib/routing.rb b/lib/routing.rb deleted file mode 100644 index 36e0ebd..0000000 --- a/lib/routing.rb +++ /dev/null @@ -1,15 +0,0 @@ -module I18n - module BackendDatabase - module Routing - # Loads the set of routes from within a plugin and - # evaluates them at this point within an application’s - # main routes.rb file. - def from_plugin(name) - map = self # to make 'map' available within the plugin route file - plugin_root = File.join(RAILS_ROOT, 'vendor', 'plugins') - routes_path = File.join(plugin_root, name.to_s, 'routes.rb') - eval(IO.read(routes_path), binding, routes_path) if File.file?(routes_path) - end - end - end -end diff --git a/lib/tasks/i18n.rake b/lib/tasks/i18n.rake new file mode 100644 index 0000000..d8667d0 --- /dev/null +++ b/lib/tasks/i18n.rake @@ -0,0 +1,96 @@ +namespace :i18n do + desc 'Clear cache' + task :clear_cache => :environment do + I18n.backend.cache_store.clear + end + + desc 'Clear all translations' + task :clear_all_translations => :environment do + puts "REMOVING #{Translation.count}" if I18nUtil.verbose? + Translation.delete_all + end + + desc 'Clear translations that have no source' + task :clear_no_source_translations => :environment do + puts "REMOVING #{Translation.count(:conditions => {:source_id => nil})}" if I18nUtil.verbose? + Translation.delete_all(:source_id => nil) + end + + desc 'Clear translations whose source does not exist' + task :clear_translations_with_missing_source => :environment do + TranslationSource.all.each do |source| + next unless source.path_not_found? + + puts "REMOVING #{Translation.count(:conditions => {:source_id => source.id})} FOR #{source.path}" if I18nUtil.verbose? + Translation.delete_all(:source_id => source.id) + end + end + + desc 'Extracts non-default translation data from database' + task :export_translations => :environment do + locale_codes = ENV['LOCALE_CODES'] || I18n::Backend::Locale.non_defaults.collect{|locale| locale.code}.join(',') + I18nUtil.process_translation_locales(locale_codes.split(',')) do |locale| + I18nUtil.export_translations(locale) + end + end + + desc 'Backup translation data from database for all locales' + task :backup_translations => :environment do + locale_codes = ENV['LOCALE_CODES'] || I18n::Backend::Locale.all.collect{|locale| locale.code}.join(',') + I18nUtil.process_translation_locales(locale_codes.split(',')) do |locale| + I18nUtil.export_translations(locale,'.bak') + end + end + + desc 'Load translation data from fixtures into database for a locale' + task :import_translations => :environment do + locale_codes = ENV['LOCALE_CODES'] || I18n::Backend::Locale.non_defaults.collect{|locale| locale.code}.join(',') + I18nUtil.process_translation_locales(locale_codes.split(',')) do |locale| + I18nUtil.import_translations(locale) + end + end + + desc 'Reset locales and translations from original sources and ready for 3rd-party translations' + task :reset_locales_and_translations => %w(clear_all_translations populate:load_default_locales populate:from_rails populate:from_application clear_no_source_translations populate:synchronize_translations import_translations clear_cache) + + namespace :populate do + I18nUtil.verbose = ENV['VERBOSE'] == 'true' + + desc 'Populate the locales and translations tables from all Rails Locale YAML files. Can set LOCALE_YAML_FILES to comma separated list of files to overide' + task :from_rails => :environment do + exclusions = (ENV['RAILS_EXCLUDE'] || '').split(',').collect{|pattern| Regexp.new(pattern)} + all_yaml_files = (ENV['LOCALE_YAML_FILES'] ? ENV['LOCALE_YAML_FILES'].split(',') : I18n.load_path).select{|path| path =~ /\.yml$/} + default_locale_pattern = /(#{I18n.default_locale}\.yml$)|(locale\/#{I18n.default_locale})/ + default_yaml_files = all_yaml_files.inject([]){|matches,file| matches << file if file =~ default_locale_pattern; matches} + (default_yaml_files + (all_yaml_files - default_yaml_files)).each do |file| + I18nUtil.load_from_yml file unless exclusions.detect{|exclusion| file =~ exclusion} + end + end + + desc 'Populate the translation tables from translation calls within the application. This only works on basic text translations. Can set DIR to override starting directory.' + task :from_application => :environment do + I18nUtil.seed_application_translations(ENV['DIR']) + end + + desc 'Create translation records from all default locale translations if none exists.' + task :synchronize_translations => :environment do + exclusions = (ENV['SYNC_EXCLUDE'] || '').split(',').collect{|pattern| Regexp.new(pattern)} + I18nUtil.synchronize_translations(exclusions) + end + + desc 'Populate default locales' + task :load_default_locales => :environment do + I18nUtil.load_default_locales(ENV['LOCALE_FILE']) + end + + desc 'Runs all populate methods in this order: load_default_locales, from_rails, from_application, synchronize_translations' + task :all => %w(load_default_locales from_rails from_application synchronize_translations) + end + + namespace :translate do + desc 'Translate all untranslated values using Google Language Translation API. Does not translate interpolated strings, date formats, or YAML' + task :google => :environment do + I18nUtil.google_translate + end + end +end diff --git a/lib/views/layouts/translations.html.erb b/lib/views/layouts/translations.html.erb deleted file mode 100644 index 62e1455..0000000 --- a/lib/views/layouts/translations.html.erb +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - Translation Administration: <%= controller.action_name %> - <%= stylesheet_link_tag 'scaffold' %> - <%= javascript_include_tag :defaults %> - <%= javascript_include_tag 'jsProgressBarHandler.js' %> - - -

- <%= link_to 'Textual Translations', translations_url %> · <%= link_to 'Asset Translations', asset_translations_url %> -

- -
- <%= yield %> -
- - - - diff --git a/lib/views/locales/edit.html.erb b/lib/views/locales/edit.html.erb deleted file mode 100644 index 108396b..0000000 --- a/lib/views/locales/edit.html.erb +++ /dev/null @@ -1,20 +0,0 @@ -

Editing locale

- -<% form_for(@locale) do |f| %> - <%= f.error_messages %> - -

- <%= f.label :code %>
- <%= f.text_field :code %> -

-

- <%= f.label :name %>
- <%= f.text_field :name %> -

-

- <%= f.submit "Update" %> -

-<% end %> - -<%= link_to 'Show', @locale %> | -<%= link_to 'Back', locales_path %> \ No newline at end of file diff --git a/lib/views/locales/index.html.erb b/lib/views/locales/index.html.erb deleted file mode 100644 index f0ae32b..0000000 --- a/lib/views/locales/index.html.erb +++ /dev/null @@ -1,22 +0,0 @@ -

Listing locales

- - - - - - - -<% for locale in @locales %> - - - - - - - -<% end %> -
CodeName
<%=h locale.code %><%=h locale.name %><%= link_to 'Show', locale %><%= link_to 'Edit', edit_locale_path(locale) %><%= link_to 'Destroy', locale, :confirm => 'Are you sure?', :method => :delete %>
- -
- -<%= link_to 'New locale', new_locale_path %> \ No newline at end of file diff --git a/lib/views/locales/new.html.erb b/lib/views/locales/new.html.erb deleted file mode 100644 index 3be2400..0000000 --- a/lib/views/locales/new.html.erb +++ /dev/null @@ -1,19 +0,0 @@ -

New locale

- -<% form_for(@locale) do |f| %> - <%= f.error_messages %> - -

- <%= f.label :code %>
- <%= f.text_field :code %> -

-

- <%= f.label :name %>
- <%= f.text_field :name %> -

-

- <%= f.submit "Create" %> -

-<% end %> - -<%= link_to 'Back', locales_path %> \ No newline at end of file diff --git a/lib/views/locales/show.html.erb b/lib/views/locales/show.html.erb deleted file mode 100644 index 1859c5e..0000000 --- a/lib/views/locales/show.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -

- Code: - <%=h @locale.code %> -

- -

- Name: - <%=h @locale.name %> -

- - -<%= link_to 'Edit', edit_locale_path(@locale) %> | -<%= link_to 'Translations', locale_translations_path(@locale) %> | -<%= link_to 'Back', locales_path %> \ No newline at end of file diff --git a/lib/views/translations/edit.html.erb b/lib/views/translations/edit.html.erb deleted file mode 100644 index 588c1d4..0000000 --- a/lib/views/translations/edit.html.erb +++ /dev/null @@ -1,35 +0,0 @@ -

Editing translation for <%= @locale.code %>

- -

- Key: - <%=h @translation.key %> -

- -

- Raw Key: - <%=h @translation.raw_key %> -

- -

- Pluralization Index: - <%=h @translation.pluralization_index %> -

- -

- Default Locale Value: - <%=h @translation.default_locale_value %> -

- -<% form_for([@locale, @translation]) do |f| %> - <%= f.error_messages %> - - <%= f.label :value %>
- <%= f.text_field :value %> -

-

- <%= f.submit "Update" %> -

-<% end %> - -<%= link_to 'Show', locale_translation_path(@locale, @translation) %> | -<%= link_to 'Back', locale_translations_path %> diff --git a/lib/views/translations/new.html.erb b/lib/views/translations/new.html.erb deleted file mode 100644 index 22ed0f1..0000000 --- a/lib/views/translations/new.html.erb +++ /dev/null @@ -1,19 +0,0 @@ -

New translation

- -<% form_for([@locale, @translation]) do |f| %> - <%= f.error_messages %> - -

- <%= f.label :key %>
- <%= f.text_field :key %> -

-

- <%= f.label :value %>
- <%= f.text_field :value %> -

-

- <%= f.submit "Create" %> -

-<% end %> - -<%= link_to 'Back', locale_translations_path %> diff --git a/lib/views/translations/show.html.erb b/lib/views/translations/show.html.erb deleted file mode 100644 index 6e4e905..0000000 --- a/lib/views/translations/show.html.erb +++ /dev/null @@ -1,32 +0,0 @@ -

- Locale: - <%=h @locale.code %> -

- -

- Key: - <%=h @translation.key %> -

- -

- Raw Key: - <%=h @translation.raw_key %> -

- -

- Pluralization Index: - <%=h @translation.pluralization_index %> -

- -

- Default Locale Value: - <%=h @translation.default_locale_value %> -

- -

- Value: - <%=h @translation.value %> -

- -<%= link_to 'Edit', edit_locale_translation_path(@locale, @translation) %> | -<%= link_to 'Back', locale_translations_path %> diff --git a/lib/views/translations/translations.html.erb b/lib/views/translations/translations.html.erb deleted file mode 100644 index 085bbb5..0000000 --- a/lib/views/translations/translations.html.erb +++ /dev/null @@ -1,28 +0,0 @@ -<% form_tag translations_url do -%> -<%= select_tag :locale_id, options_from_collection_for_select(Locale.all, :code, :name, @locale.code) -%> -<%= select_tag :translation_option, options_from_collection_for_select(TranslationOption.all, :code, :description, @translation_option.code) -%> -<%= submit_tag 'Go' %> -<% end -%> - -

- Textual Translation progress: <%= @locale.percentage_translated %> -

- -

<%= @translation_option.description %>: <%= @locale.name %>

- -<% if @translations.empty? -%> -

No records for this criteria.

-<% end -%> - -<% for translation in @translations %> -
"> - <% remote_form_for([@locale, translation]) do |f| %> -

<%=h translation.default_locale_value || translation.raw_key %>

-

- <%= f.text_field :value, :size => 128 %> - <%= f.submit "Update" %> -

- <% end %> -
-
-<% end %> diff --git a/routes.rb b/routes.rb deleted file mode 100644 index 7b368a8..0000000 --- a/routes.rb +++ /dev/null @@ -1,3 +0,0 @@ -map.resources :locales, :has_many => :translations -map.translations '/translations', :controller => 'translations', :action => 'translations' -map.asset_translations '/asset_translations', :controller => 'translations', :action => 'asset_translations' \ No newline at end of file diff --git a/spec/caching_spec.rb b/spec/caching_spec.rb index e98ec52..cbac54d 100644 --- a/spec/caching_spec.rb +++ b/spec/caching_spec.rb @@ -13,7 +13,7 @@ describe "with default locale en" do before(:each) do I18n.default_locale = "en" - @english_locale = Locale.create!(:code => "en") + @english_locale = I18n::Backend::Locale.find_or_create!(:code => "en") end describe "and locale en" do @@ -24,7 +24,7 @@ it "should cache translations" do @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("en", :"models.translation.attributes.locale.blank", options).should == "is blank moron!" @@ -55,13 +55,13 @@ describe "and locale es" do before(:each) do I18n.locale = "es" - @spanish_locale = Locale.create!(:code => 'es') + @spanish_locale = I18n::Backend::Locale.find_or_create!(:code => 'es') end it "should cache translations" do @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("es", :"models.translation.attributes.locale.blank", options).should == "is blank moron!" diff --git a/spec/database_spec.rb b/spec/database_spec.rb index ba466f1..1764dfe 100644 --- a/spec/database_spec.rb +++ b/spec/database_spec.rb @@ -5,9 +5,9 @@ before { I18n.locale = "es" @locales = [:en, :es, :it] - @locale = mock_model(Locale, { :code => "es" }) - Locale.stub!(:available_locales).and_return(@locales) - Locale.stub!(:find_by_code).and_return(@locale) + @locale = mock_model(I18n::Backend::Locale, { :code => "es" }) + I18n::Backend::Locale.stub!(:available_locales).and_return(@locales) + I18n::Backend::Locale.stub!(:find_by_code).and_return(@locale) @database = I18n::Backend::Database.new } @@ -29,8 +29,8 @@ @database.localize_text_tag.should == '##' end - it "should delegate the call to available_locales to the Locale class" do - Locale.should_receive(:available_locales) + it "should delegate the call to available_locales to the I18n::Backend::Locale class" do + I18n::Backend::Locale.should_receive(:available_locales) @database.available_locales end @@ -62,35 +62,35 @@ # describe "omg an aminal" do # # before(:each) do - # Locale.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - # Locale.create!(:code => "en") + # I18n::Backend::Locale.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + # I18n::Backend::Locale.find_or_create!(:code => "en") # end # # it "should contain one 'blank' key in the database" do - # Locale.validates_presence_of :code - # l = Locale.new + # I18n::Backend::Locale.validates_presence_of :code + # l = I18n::Backend::Locale.new # l.valid? # Translation.find_by_value("can't be blank").should_not be_nil # end # # it "should contain one 'blank' key and one custom 'blank' key in the database" do - # Locale.validates_presence_of :code, :message => "ain't blank sucka" - # l = Locale.new + # I18n::Backend::Locale.validates_presence_of :code, :message => "ain't blank sucka" + # l = I18n::Backend::Locale.new # l.valid? # Translation.find_by_value("ain't blank sucka").should_not be_nil # Translation.find_by_value("can't be blank").should be_nil # end # # it "should use the blank code if a custom code is present, but not enabled" do - # Locale.validates_presence_of :code, :message => "ain't blank sucka" + # I18n::Backend::Locale.validates_presence_of :code, :message => "ain't blank sucka" # - # l = Locale.new + # l = I18n::Backend::Locale.new # l.valid? # l.errors_on(:code).should include("ain't blank sucka") # - # Locale.validates_presence_of :code + # I18n::Backend::Locale.validates_presence_of :code # - # l = Locale.new + # l = I18n::Backend::Locale.new # l.valid? # l.errors_on(:code).should include("can't be blank") # end @@ -101,7 +101,7 @@ before { I18n.locale = "en" I18n.default_locale = "en" - Locale.create({:code => "en", :name => "English"}) + I18n::Backend::Locale.create({:code => "en", :name => "English"}) @database = I18n::Backend::Database.new @database.translate(:en, "dog") } @@ -115,8 +115,8 @@ before { I18n.locale = "es" I18n.default_locale = "en" - Locale.create({:code => "en", :name => "English"}) - Locale.create({:code => "es", :name => "Spanish"}) + I18n::Backend::Locale.create({:code => "en", :name => "English"}) + I18n::Backend::Locale.create({:code => "es", :name => "Spanish"}) @database = I18n::Backend::Database.new @database.translate(:es, "dog") } @@ -130,29 +130,29 @@ describe "setting a locale in context" do before { I18n.locale = "es" - @locale = mock_model(Locale, { :code => "es" }) + @locale = mock_model(I18n::Backend::Locale, { :code => "es" }) @database = I18n::Backend::Database.new } describe "on a new instance when the cache locale is nil" do before { - Locale.stub!(:find_by_code).and_return(@locale) + I18n::Backend::Locale.stub!(:find_by_code).and_return(@locale) } it "should return a locale record for the current locale in context" do - Locale.should_receive(:find_by_code).with(I18n.locale) + I18n::Backend::Locale.should_receive(:find_by_code).with(I18n.locale) @database.send(:locale_in_context, I18n.locale) end end describe "when passing in a temporary locale that's different from the local cache" do before { - Locale.stub!(:find_by_code).with("it").and_return(@locale) + I18n::Backend::Locale.stub!(:find_by_code).with("it").and_return(@locale) @database.locale = "it" } it "should return a locale record for the temporary locale" do - Locale.should_receive(:find_by_code).with("it") + I18n::Backend::Locale.should_receive(:find_by_code).with("it") @database.send(:locale_in_context, "it") end @@ -163,7 +163,7 @@ describe "when passing in a temporary locale that's the same as the local cache" do before { - Locale.stub!(:find_by_code).with("es").and_return(@locale) + I18n::Backend::Locale.stub!(:find_by_code).with("es").and_return(@locale) @database.locale = "es" } @@ -174,7 +174,7 @@ describe "when the locale is the same as the cache" do before { - Locale.stub!(:find_by_code).with("es").and_return(@locale) + I18n::Backend::Locale.stub!(:find_by_code).with("es").and_return(@locale) } it "should update the locale cache with the new locale" do @@ -185,13 +185,13 @@ describe "when the locale is different than the cache" do before { - Locale.stub!(:find_by_code).with("es").and_return(@locale) + I18n::Backend::Locale.stub!(:find_by_code).with("es").and_return(@locale) I18n.locale = "it" } it "should update the locale cache with the new locale" do @database.locale = "es" - Locale.should_receive(:find_by_code).with("it") + I18n::Backend::Locale.should_receive(:find_by_code).with("it") @database.send(:locale_in_context, "it") end end diff --git a/spec/localize_spec.rb b/spec/localize_spec.rb index d845db2..0e038a9 100644 --- a/spec/localize_spec.rb +++ b/spec/localize_spec.rb @@ -58,7 +58,7 @@ describe "and locale es" do before(:each) do I18n.locale = "es" - @spanish_locale = Locale.create!(:code => 'es') + @spanish_locale = I18n::Backend::Locale.find_or_create!(:code => 'es') end end diff --git a/spec/localize_text_spec.rb b/spec/localize_text_spec.rb index 734ed43..cd6fc75 100644 --- a/spec/localize_text_spec.rb +++ b/spec/localize_text_spec.rb @@ -12,7 +12,7 @@ describe "with default locale en" do before(:each) do I18n.default_locale = "en" - @english_locale = Locale.create!(:code => "en") + @english_locale = I18n::Backend::Locale.find_or_create!(:code => "en") end describe "and locale en" do @@ -63,7 +63,7 @@ describe "and locale es" do before(:each) do I18n.locale = "es" - @spanish_locale = Locale.create!(:code => 'es') + @spanish_locale = I18n::Backend::Locale.find_or_create!(:code => 'es') end it "should localize tagged text" do diff --git a/spec/migrations/1_create_i18n_tables.rb b/spec/migrations/1_create_i18n_tables.rb new file mode 100644 index 0000000..c5364f3 --- /dev/null +++ b/spec/migrations/1_create_i18n_tables.rb @@ -0,0 +1 @@ +load File.dirname(__FILE__) + '/../../lib/generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb' \ No newline at end of file diff --git a/spec/models/locale_spec.rb b/spec/models/locale_spec.rb index 3c71c58..b3fbbaf 100644 --- a/spec/models/locale_spec.rb +++ b/spec/models/locale_spec.rb @@ -1,6 +1,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') -describe Locale do +describe I18n::Backend::Locale do before(:each) do @valid_attributes = { :code => "en", @@ -9,26 +9,27 @@ end it "should create a new instance given valid attributes" do - Locale.create!(@valid_attributes) + I18n::Backend::Locale.find_or_create!(@valid_attributes) end it "should return code as to_param" do - Locale.new(@valid_attributes).to_param.should == 'en' + I18n::Backend::Locale.new(@valid_attributes).to_param.should == 'en' end it "should be invalid with no code" do - Locale.create!(:code => "en") - locale = Locale.new + I18n::Backend::Locale.find_or_create!(:code => "en") + locale = I18n::Backend::Locale.new locale.should_not be_valid end end -describe "English and Spanish Locales with I18n default locale set to English" do +describe "English and Spanish I18n::Backend::Locales with I18n default locale set to English" do before(:each) do I18n.default_locale = "en" - @english_locale = Locale.create!(:code => "en") - @spanish_locale = Locale.create!(:code => "es") + I18n::Backend::Locale.delete_all + @english_locale = I18n::Backend::Locale.find_or_create!(:code => "en") + @spanish_locale = I18n::Backend::Locale.find_or_create!(:code => "es") end it "should create a translated translation using english locale" do @@ -44,14 +45,14 @@ end it "should return default locale of English" do - Locale.default_locale.should == @english_locale + I18n::Backend::Locale.default_locale.should == @english_locale end it "should return list of non-default locales" do - @geek_locale = Locale.create!(:code => "gk") - Locale.non_defaults.should include(@spanish_locale) - Locale.non_defaults.should include(@geek_locale) - Locale.non_defaults.should_not include(@english_locale) + @geek_locale = I18n::Backend::Locale.find_or_create!(:code => "gk") + I18n::Backend::Locale.non_defaults.should include(@spanish_locale) + I18n::Backend::Locale.non_defaults.should include(@geek_locale) + I18n::Backend::Locale.non_defaults.should_not include(@english_locale) end it "should know that the english_locale is the default" do @@ -81,11 +82,12 @@ end -describe "Locale with translations" do +describe "I18n::Backend::Locale with translations" do before(:each) do I18n.default_locale = "en" - @english_locale = Locale.create!(:code => "en") - @spanish_locale = Locale.create!(:code => "es") + I18n::Backend::Locale.delete_all + @english_locale = I18n::Backend::Locale.find_or_create!(:code => "en") + @spanish_locale = I18n::Backend::Locale.find_or_create!(:code => "es") @spanish_locale.translations.create!(:key => 'key1', :value => 'translated1') @spanish_locale.translations.create!(:key => 'key2', :value => 'translated2') diff --git a/spec/models/translation_spec.rb b/spec/models/translation_spec.rb index 3412712..b95001c 100644 --- a/spec/models/translation_spec.rb +++ b/spec/models/translation_spec.rb @@ -13,11 +13,12 @@ end end -describe "English and Spanish Locales with I18n default locale set to English" do +describe "English and Spanish I18n::Backend::Locales with I18n default locale set to English" do before(:each) do I18n.default_locale = "en" - @english_locale = Locale.create!(:code => "en") - @spanish_locale = Locale.create!(:code => "es") + I18n::Backend::Locale.delete_all + @english_locale = I18n::Backend::Locale.create!(:code => "en") + @spanish_locale = I18n::Backend::Locale.create!(:code => "es") end describe "with no English translation" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0180465..fb0e4f4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,16 +1,36 @@ -ENV["RAILS_ENV"] = "test" -require File.join(File.dirname(__FILE__), "/../../../../config/environment") -require 'spec/rails' +ENV['RAILS_ENV'] = 'test' + +require 'active_record' +require 'action_view' +require 'action_controller' +require 'rspec/rails' +require 'rails' + +ActiveRecord::Base.establish_connection(:adapter => 'sqlite3',:database => ':memory:') +ActiveRecord::Migrator.up File.dirname(__FILE__) + '/migrations' + +class Application < Rails::Application +end + +Application.configure do + config.cache_store = :memory_store + config.active_support.deprecation = :log +end + +Application.initialize! + require 'i18n_backend_database' -Spec::Runner.configure do |config| - config.use_transactional_fixtures = true +I18n.backend = I18n::Backend::Database.new + +RSpec.configure do |config| + config.use_transactional_fixtures = false config.use_instantiated_fixtures = false config.after(:each) do - Locale.reset_default_locale - I18n.locale = "en" - I18n.default_locale = "en" + I18n::Backend::Locale.reset_default_locale + I18n.locale = 'en' + I18n.default_locale = 'en' I18n.backend.cache_store.clear end end diff --git a/spec/translate_spec.rb b/spec/translate_spec.rb index 3b22098..9bdda26 100644 --- a/spec/translate_spec.rb +++ b/spec/translate_spec.rb @@ -26,7 +26,7 @@ def write(key, value, options = nil) describe "returning hashes for non-leaves" do before do #Set up a translation hierarchy - @locale = Locale.create!(:code => "en") + @locale = I18n::Backend::Locale.find_or_create!(:code => "en") I18n.default_locale = "en" end @@ -63,7 +63,7 @@ def write(key, value, options = nil) describe "with default locale en" do before(:each) do I18n.default_locale = "en" - @english_locale = Locale.create!(:code => "en") + @english_locale = I18n::Backend::Locale.find_or_create!(:code => "en") end describe "and locale en" do @@ -186,31 +186,31 @@ def write(key, value, options = nil) it "should be able to handle interpolated values" do options = {:some_value => 'INTERPOLATED'} - @english_locale.translations.create!(:key => 'Fred', :value => 'Fred has been {{some_value}}!!') + @english_locale.translations.create!(:key => 'Fred', :value => 'Fred has been %{some_value}!!') @backend.translate("en", 'Fred', options).should == 'Fred has been INTERPOLATED!!' end - it "should be able to handle interpolated values with 'Fred {{some_value}}' also as the key" do + it "should be able to handle interpolated values with 'Fred %{some_value}' also as the key" do options = {:some_value => 'INTERPOLATED'} - @english_locale.translations.create!(:key => 'Fred {{some_value}}', :value => 'Fred {{some_value}}!!') - @backend.translate("en", 'Fred {{some_value}}', options).should == 'Fred INTERPOLATED!!' + @english_locale.translations.create!(:key => 'Fred %{some_value}', :value => 'Fred %{some_value}!!') + @backend.translate("en", 'Fred %{some_value}', options).should == 'Fred INTERPOLATED!!' end it "should be able to handle interpolated count values" do - options = {:count=>1, :model => ["Cheese"], :scope=>[:activerecord, :errors], :default=>["Locale"]} - @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => '{{count}} errors prohibited this {{model}} from being saved') + options = {:count=>1, :model => ["Cheese"], :scope=>[:activerecord, :errors], :default=>["I18n::Backend::Locale"]} + @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => '%{count} errors prohibited this %{model} from being saved') @backend.translate("en", :"messages.blank", options).should == '1 errors prohibited this Cheese from being saved' end it "should be able to handle the case of scope being passed in as something other than an array" do - options = {:count=>1, :model => ["Cheese"], :scope=> :activerecord, :default=>["Locale"]} + options = {:count=>1, :model => ["Cheese"], :scope=> :activerecord, :default=>["I18n::Backend::Locale"]} @english_locale.translations.create!(:key => 'activerecord.messages.blank', :value => 'dude') @backend.translate("en", :"messages.blank", options).should == 'dude' end it "should be able to handle pluralization" do - @english_locale.translations.create!(:key => 'activerecord.errors.template.header', :value => '1 error prohibited this {{model}} from being saved', :pluralization_index => 1) - @english_locale.translations.create!(:key => 'activerecord.errors.template.header', :value => '{{count}} errors prohibited this {{model}} from being saved', :pluralization_index => 0) + @english_locale.translations.create!(:key => 'activerecord.errors.template.header', :value => '1 error prohibited this %{model} from being saved', :pluralization_index => 1) + @english_locale.translations.create!(:key => 'activerecord.errors.template.header', :value => '%{count} errors prohibited this %{model} from being saved', :pluralization_index => 0) options = {:count=>1, :model=>"translation", :scope=>[:activerecord, :errors, :template]} @backend.translate("en", :"header", options).should == "1 error prohibited this translation from being saved" @@ -224,7 +224,7 @@ def write(key, value, options = nil) it "should find lowest level translation" do @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("en", :"models.translation.attributes.locale.blank", options).should == "is blank moron!" @@ -234,7 +234,7 @@ def write(key, value, options = nil) @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') @english_locale.translations.create!(:key => 'activerecord.errors.models.translation.blank', :value => 'translation blank') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("en", :"models.translation.attributes.locale.blank", options).should == "translation blank" @@ -245,7 +245,7 @@ def write(key, value, options = nil) @english_locale.translations.create!(:key => 'activerecord.errors.models.translation.blank', :value => 'translation blank') @english_locale.translations.create!(:key => 'activerecord.errors.models.translation.attributes.locale.blank', :value => 'translation locale blank') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("en", :"models.translation.attributes.locale.blank", options).should == "translation locale blank" @@ -254,7 +254,7 @@ def write(key, value, options = nil) it "should create the translation for custom message" do @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", "This is a custom message!", :"messages.blank"], :model=>"Translation"} @backend.translate("en", :"models.translation.attributes.locale.blank", options).should == "This is a custom message!" @@ -266,7 +266,7 @@ def write(key, value, options = nil) @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') @english_locale.translations.create!(:key => 'This is a custom message!', :value => 'This is a custom message!') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", "This is a custom message!", :"messages.blank"], :model=>"Translation"} @backend.translate("en", :"models.translation.attributes.locale.blank", options).should == "This is a custom message!" @@ -276,7 +276,7 @@ def write(key, value, options = nil) @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') @english_locale.translations.create!(:key => 'This is a custom message!', :value => 'This is a custom message!') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("en", :"models.translation.attributes.locale.blank", options).should == "is blank moron!" @@ -322,7 +322,7 @@ def write(key, value, options = nil) describe "and locale es" do before(:each) do I18n.locale = "es" - @spanish_locale = Locale.create!(:code => 'es') + @spanish_locale = I18n::Backend::Locale.find_or_create!(:code => 'es') end it "should create a record with a nil value when the key is a string" do @@ -387,20 +387,20 @@ def write(key, value, options = nil) it "should be able to handle interpolated values" do options = {:some_value => 'INTERPOLATED'} - @english_locale.translations.create!(:key => 'Fred', :value => 'Fred has been {{some_value}}!!') - @spanish_locale.translations.create!(:key => 'Fred', :value => 'Fred ha sido {{some_value}}!!') + @english_locale.translations.create!(:key => 'Fred', :value => 'Fred has been %{some_value}!!') + @spanish_locale.translations.create!(:key => 'Fred', :value => 'Fred ha sido %{some_value}!!') @backend.translate("es", 'Fred', options).should == 'Fred ha sido INTERPOLATED!!' end it "should be able to handle interpolated count values" do - options = {:count=>1, :model => ["Cheese"], :scope=>[:activerecord, :errors], :default=>["Locale"]} - @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => '{{count}} error prohibited this {{model}} from being saved') + options = {:count=>1, :model => ["Cheese"], :scope=>[:activerecord, :errors], :default=>["I18n::Backend::Locale"]} + @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => '%{count} error prohibited this %{model} from being saved') @backend.translate("es", :"messages.blank", options).should == '1 error prohibited this Cheese from being saved' end it "should be able to handle pluralization" do - @english_locale.translations.create!(:key => 'activerecord.errors.template.header', :value => '1 error prohibited this {{model}} from being saved', :pluralization_index => 1) - @english_locale.translations.create!(:key => 'activerecord.errors.template.header', :value => '{{count}} errors prohibited this {{model}} from being saved', :pluralization_index => 0) + @english_locale.translations.create!(:key => 'activerecord.errors.template.header', :value => '1 error prohibited this %{model} from being saved', :pluralization_index => 1) + @english_locale.translations.create!(:key => 'activerecord.errors.template.header', :value => '%{count} errors prohibited this %{model} from being saved', :pluralization_index => 0) options = {:count=>1, :model=>"translation", :scope=>[:activerecord, :errors, :template]} @backend.translate("es", :"header", options).should == "1 error prohibited this translation from being saved" @spanish_locale.should have(2).translations @@ -413,7 +413,7 @@ def write(key, value, options = nil) it "should find lowest level translation" do @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("es", :"models.translation.attributes.locale.blank", options).should == "is blank moron!" @@ -427,7 +427,7 @@ def write(key, value, options = nil) @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') @english_locale.translations.create!(:key => 'activerecord.errors.models.translation.blank', :value => 'translation blank') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("es", :"models.translation.attributes.locale.blank", options).should == "translation blank" @@ -442,7 +442,7 @@ def write(key, value, options = nil) @english_locale.translations.create!(:key => 'activerecord.errors.models.translation.blank', :value => 'translation blank') @english_locale.translations.create!(:key => 'activerecord.errors.models.translation.attributes.locale.blank', :value => 'translation locale blank') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", :"messages.blank"], :model=>"Translation"} @backend.translate("es", :"models.translation.attributes.locale.blank", options).should == "translation locale blank" @@ -455,7 +455,7 @@ def write(key, value, options = nil) @english_locale.translations.create!(:key => 'activerecord.errors.messages.blank', :value => 'is blank moron!') @english_locale.translations.create!(:key => 'This is a custom message!', :value => 'This is a custom message!') - options = {:attribute=>"Locale", :value=>nil, + options = {:attribute=>"I18n::Backend::Locale", :value=>nil, :scope=>[:activerecord, :errors], :default=>[:"models.translation.blank", "This is a custom message!", :"messages.blank"], :model=>"Translation"} @backend.translate("es", :"models.translation.attributes.locale.blank", options).should == "This is a custom message!" diff --git a/tasks/i18n.rake b/tasks/i18n.rake deleted file mode 100644 index 9a49608..0000000 --- a/tasks/i18n.rake +++ /dev/null @@ -1,60 +0,0 @@ -def load_default_locales(path_to_file=nil) - path_to_file ||= File.join(File.dirname(__FILE__), "../data", "locales.yml") - data = YAML::load(IO.read(path_to_file)) - data.each do |code, y| - Locale.create({:code => code, :name => y["name"]}) unless Locale.exists?(:code => code) - end -end - -namespace :i18n do - desc 'Clear cache' - task :clear_cache => :environment do - I18n.backend.cache_store.clear - end - - desc 'Install admin panel assets' - task :install_admin_assets => :environment do - images_dir = Rails.root + '/public/images/' - javascripts_dir = Rails.root + '/public/javascripts/' - images = Dir[File.join(File.dirname(__FILE__), '..') + '/lib/public/images/*.*'] - scripts = Dir[File.join(File.dirname(__FILE__), '..') + '/lib/public/javascripts/*.*'] - FileUtils.cp(images, images_dir) - FileUtils.cp(scripts, javascripts_dir) - end - - namespace :populate do - desc 'Populate the locales and translations tables from all Rails Locale YAML files. Can set LOCALE_YAML_FILES to comma separated list of files to overide' - task :from_rails => :environment do - yaml_files = ENV['LOCALE_YAML_FILES'] ? ENV['LOCALE_YAML_FILES'].split(',') : I18n.load_path - yaml_files.each do |file| - I18nUtil.load_from_yml file - end - end - - desc 'Populate the translation tables from translation calls within the application. This only works on basic text translations. Can set DIR to override starting directory.' - task :from_application => :environment do - dir = ENV['DIR'] ? ENV['DIR'] : "." - I18nUtil.seed_application_translations(dir) - end - - desc 'Create translation records from all default locale translations if none exists.' - task :synchronize_translations => :environment do - I18nUtil.synchronize_translations - end - - desc 'Populate default locales' - task :load_default_locales => :environment do - load_default_locales(ENV['LOCALE_FILE']) - end - - desc 'Runs all populate methods in this order: load_default_locales, from_rails, from_application, synchronize_translations' - task :all => ["load_default_locales", "from_rails", "from_application", "synchronize_translations"] - end - - namespace :translate do - desc 'Translate all untranslated values using Google Language Translation API. Does not translate interpolated strings, date formats, or YAML' - task :google => :environment do - I18nUtil.google_translate - end - end -end