Moved to offline translation files (improve speed of Zammad installation and possibility to need no network connection).
This commit is contained in:
parent
de302fde09
commit
5856dd7c06
5 changed files with 296 additions and 65 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue