Moved to offline translation files (improve speed of Zammad installation and possibility to need no network connection).

This commit is contained in:
Martin Edenhofer 2016-12-03 20:37:37 +01:00
parent de302fde09
commit 5856dd7c06
5 changed files with 296 additions and 65 deletions

View file

@ -25,6 +25,9 @@ before:
- env - env
- "cat Gemfile.lock" - "cat Gemfile.lock"
- contrib/cleanup.sh - contrib/cleanup.sh
after:
- rails r 'Locale.fetch'
- rails r 'Translation.fetch'
env: env:
- RAILS_ENV=production - RAILS_ENV=production
- PORT=3000 - PORT=3000

View file

@ -8,7 +8,7 @@ get locals to sync
all: all:
Locale.sync Locale.to_sync
returns returns
@ -31,6 +31,21 @@ returns
=begin =begin
sync locales from local if exists, otherwise from online
all:
Locale.sync
=end
def self.sync
return true if load_from_file
load
end
=begin
load locales from online load locales from online
all: all:
@ -40,6 +55,39 @@ all:
=end =end
def self.load def self.load
data = fetch
to_database(data)
end
=begin
load locales from local
all:
Locale.load_from_file
=end
def self.load_from_file
file = Rails.root.join('config/locales.yml')
return false if !File.exist?(file)
data = YAML.load_file(file)
to_database(data)
true
end
=begin
fetch locales from remote and store them in local file system
all:
Locale.fetch
=end
def self.fetch
url = 'https://i18n.zammad.com/api/v1/locales' url = 'https://i18n.zammad.com/api/v1/locales'
result = UserAgent.get( result = UserAgent.get(
@ -53,8 +101,16 @@ all:
raise "Can't load locales from #{url}" if !result raise "Can't load locales from #{url}" if !result
raise "Can't load locales from #{url}: #{result.error}" if !result.success? raise "Can't load locales from #{url}: #{result.error}" if !result.success?
file = Rails.root.join('config/locales.yml')
File.open(file, 'w') do |out|
YAML.dump(result.data, out)
end
result.data
end
private_class_method def self.to_database(data)
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
result.data.each { |locale| data.each { |locale|
exists = Locale.find_by(locale: locale['locale']) exists = Locale.find_by(locale: locale['locale'])
if exists if exists
exists.update(locale.symbolize_keys!) exists.update(locale.symbolize_keys!)
@ -63,7 +119,6 @@ all:
end end
} }
end end
true
end end
end end

View file

@ -8,6 +8,23 @@ class Translation < ApplicationModel
=begin =begin
sync translations from local if exists, otherwise from online
all:
Translation.sync
Translation.sync(locale) # e. g. 'en-us' or 'de-de'
=end
def self.sync(dedicated_locale = nil)
return true if load_from_file(dedicated_locale)
load
end
=begin
load translations from online load translations from online
all: all:
@ -21,62 +38,9 @@ dedicated:
=end =end
def self.load(dedicated_locale = nil) def self.load(dedicated_locale = nil)
locales_list = [] locals_to_sync(dedicated_locale).each { |locale|
if !dedicated_locale fetch(locale)
locales = Locale.to_sync load_from_file(locale)
locales.each { |locale|
locales_list.push locale.locale
}
else
locales_list = [dedicated_locale]
end
locales_list.each { |locale|
url = "https://i18n.zammad.com/api/v1/translations/#{locale}"
if !UserInfo.current_user_id
UserInfo.current_user_id = 1
end
result = UserAgent.get(
url,
{},
{
json: true,
open_timeout: 6,
read_timeout: 16,
}
)
raise "Can't load translations from #{url}: #{result.error}" if !result.success?
translations = Translation.where(locale: locale).all
ActiveRecord::Base.transaction do
result.data.each { |translation_raw|
# handle case insensitive sql
translation = nil
translations.each { |item|
next if item.format != translation_raw['format']
next if item.source != translation_raw['source']
translation = item
break
}
if translation
# verify if update is needed
update_needed = false
translation_raw.each { |key, _value|
if translation_raw[key] != translation[key]
update_needed = true
break
end
}
if update_needed
translation.update_attributes(translation_raw.symbolize_keys!)
translation.save
end
else
Translation.create(translation_raw.symbolize_keys!)
end
}
end
} }
true true
end end
@ -246,6 +210,126 @@ translate strings in ruby context, e. g. for notifications
string string
end end
=begin
load locales from local
all:
Translation.load_from_file
or
Translation.load_from_file(locale) # e. g. 'en-us' or 'de-de'
=end
def self.load_from_file(dedicated_locale = nil)
directory = Rails.root.join('config/translations')
locals_to_sync(dedicated_locale).each { |locale|
file = Rails.root.join("#{directory}/#{locale}.yml")
return false if !File.exist?(file)
data = YAML.load_file(file)
to_database(locale, data)
}
true
end
=begin
fetch translation from remote and store them in local file system
all:
Translation.fetch
or
Translation.fetch(locale) # e. g. 'en-us' or 'de-de'
=end
def self.fetch(dedicated_locale = nil)
locals_to_sync(dedicated_locale).each { |locale|
url = "https://i18n.zammad.com/api/v1/translations/#{locale}"
if !UserInfo.current_user_id
UserInfo.current_user_id = 1
end
result = UserAgent.get(
url,
{},
{
json: true,
open_timeout: 8,
read_timeout: 24,
}
)
raise "Can't load translations from #{url}: #{result.error}" if !result.success?
directory = Rails.root.join('config/translations')
if !File.directory?(directory)
Dir.mkdir(directory, 0o755)
end
file = Rails.root.join("#{directory}/#{locale}.yml")
File.open(file, 'w') do |out|
YAML.dump(result.data, out)
end
}
true
end
private_class_method def self.to_database(locale, data)
translations = Translation.where(locale: locale).all
ActiveRecord::Base.transaction do
data.each { |translation_raw|
# handle case insensitive sql
translation = nil
translations.each { |item|
next if item.format != translation_raw['format']
next if item.source != translation_raw['source']
translation = item
break
}
if translation
# verify if update is needed
update_needed = false
translation_raw.each { |key, _value|
# if translation target has changes
next unless translation_raw[key] != translation.target
# do not update translations which are already changed by user
if translation.target == translation.target_initial
update_needed = true
break
end
}
if update_needed
translation.update_attributes(translation_raw.symbolize_keys!)
translation.save
end
else
Translation.create(translation_raw.symbolize_keys!)
end
}
end
end
private_class_method def self.locals_to_sync(dedicated_locale = nil)
locales_list = []
if !dedicated_locale
locales = Locale.to_sync
locales.each { |locale|
locales_list.push locale.locale
}
else
locales_list = [dedicated_locale]
end
locales_list
end
private private
def set_initial def set_initial

View file

@ -5455,8 +5455,8 @@ Locale.create_if_not_exists(
alias: 'en', alias: 'en',
name: 'English (United States)', name: 'English (United States)',
) )
Locale.load Locale.sync
Translation.load Translation.sync
Calendar.init_setup Calendar.init_setup
# install all packages in auto_install # install all packages in auto_install

View file

@ -3,9 +3,12 @@ require 'test_helper'
class TranslationTest < ActiveSupport::TestCase class TranslationTest < ActiveSupport::TestCase
Translation.load('de-de') test 'setup' do
Translation.reset('de-de')
Translation.load('de-de')
end
test 'translation' do test 'basics' do
tests = [ tests = [
{ {
locale: 'en', locale: 'en',
@ -29,8 +32,94 @@ class TranslationTest < ActiveSupport::TestCase
}, },
] ]
tests.each { |test| tests.each { |test|
result = Translation.translate( test[:locale], test[:string] ) result = Translation.translate(test[:locale], test[:string])
assert_equal( result, test[:result], 'verify result' ) assert_equal(result, test[:result], 'verify result')
} }
end end
test 'own translation tests' do
locale = 'de-de'
# check for custom changes
list = Translation.lang(locale)
list['list'].each { |item|
translation = Translation.find_by(source: item[1], locale: locale)
assert(translation)
assert_equal(locale, translation.locale)
assert_equal(translation.target, translation.target_initial)
}
# add custom changes
translation = Translation.find_by(locale: locale, source: 'open')
assert_equal('offen', translation.target)
assert_equal('offen', translation.target_initial)
translation.target = 'offen2'
translation.save!
list = Translation.lang(locale)
list['list'].each { |item|
translation = Translation.find_by(source: item[1], locale: locale)
assert(translation)
assert_equal(locale, translation.locale)
if translation.source == 'open'
assert_equal('offen2', translation.target)
assert_equal('offen', translation.target_initial)
else
assert_equal(translation.target, translation.target_initial)
end
}
Translation.load(locale)
list = Translation.lang(locale)
list['list'].each { |item|
translation = Translation.find_by(source: item[1], locale: locale)
assert(translation)
assert_equal(locale, translation.locale)
if translation.source == 'open'
p translation
assert_equal('offen2', translation.target)
assert_equal('offen', translation.target_initial)
else
assert_equal(translation.target, translation.target_initial)
end
}
Translation.reset(locale)
list = Translation.lang(locale)
list['list'].each { |item|
translation = Translation.find_by(source: item[1], locale: locale)
assert(translation)
assert_equal(locale, translation.locale)
assert_equal(translation.target, translation.target_initial)
}
end
test 'file based import' do
# locales
directory = Rails.root.join('config')
file = Rails.root.join("#{directory}/locales.yml")
if File.exist?(file)
File.delete(file)
end
assert_not(File.exist?(file))
Locale.fetch
assert(File.exist?(file))
# translations
locale = 'de-de'
directory = Rails.root.join('config/translations')
if File.directory?(directory)
FileUtils.rm_rf(directory)
end
file = Rails.root.join("#{directory}/#{locale}.yml")
assert_not(File.exist?(file))
Translation.fetch(locale)
assert(File.exist?(file))
end
end end