2021-06-01 12:20:20 +00:00
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
2017-06-26 13:16:03 +00:00
require 'csv'
2013-06-12 15:59:58 +00:00
2012-09-20 12:08:02 +00:00
class Translation < ApplicationModel
2012-05-18 14:24:00 +00:00
before_create :set_initial
2013-08-06 14:17:30 +00:00
after_create :cache_clear
after_update :cache_clear
after_destroy :cache_clear
2012-05-18 14:24:00 +00:00
2015-04-27 06:20:52 +00:00
= begin
2016-12-03 19:37:37 +00:00
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 )
2018-10-09 06:17:41 +00:00
2016-12-03 19:37:37 +00:00
load
end
= begin
2015-04-27 06:20:52 +00:00
load translations from online
2015-09-21 12:20:36 +00:00
all :
2015-04-27 06:20:52 +00:00
Translation . load
2015-09-21 12:20:36 +00:00
dedicated :
2016-01-19 06:58:58 +00:00
Translation . load ( locale ) # e. g. 'en-us' or 'de-de'
2015-09-21 12:20:36 +00:00
2015-04-27 06:20:52 +00:00
= end
2015-09-21 12:20:36 +00:00
def self . load ( dedicated_locale = nil )
2017-10-01 12:25:52 +00:00
locals_to_sync ( dedicated_locale ) . each do | locale |
2016-12-03 19:37:37 +00:00
fetch ( locale )
load_from_file ( locale )
2017-10-01 12:25:52 +00:00
end
2015-04-27 06:20:52 +00:00
true
end
= begin
push translations to online
Translation . push ( locale )
= end
def self . push ( locale )
2015-04-27 11:47:48 +00:00
# only push changed translations
2015-04-27 13:42:53 +00:00
translations = Translation . where ( locale : locale )
2015-04-27 06:20:52 +00:00
translations_to_push = [ ]
2017-10-01 12:25:52 +00:00
translations . each do | translation |
2015-04-27 06:20:52 +00:00
if translation . target != translation . target_initial
translations_to_push . push translation
end
2017-10-01 12:25:52 +00:00
end
2015-04-27 06:20:52 +00:00
2017-11-15 14:06:53 +00:00
return true if translations_to_push . blank?
2015-06-28 00:16:47 +00:00
2016-07-25 20:13:38 +00:00
url = 'https://i18n.zammad.com/api/v1/translations/thanks_for_your_support'
2015-04-27 06:20:52 +00:00
2015-06-28 00:16:47 +00:00
translator_key = Setting . get ( 'translator_key' )
2015-04-27 06:20:52 +00:00
result = UserAgent . post (
url ,
{
2018-12-19 17:31:51 +00:00
locale : locale ,
translations : translations_to_push ,
fqdn : Setting . get ( 'fqdn' ) ,
2015-06-28 00:16:47 +00:00
translator_key : translator_key ,
2015-04-27 06:20:52 +00:00
} ,
{
2018-12-19 17:31:51 +00:00
json : true ,
2017-09-23 12:31:16 +00:00
open_timeout : 8 ,
read_timeout : 24 ,
2015-04-27 06:20:52 +00:00
}
)
2016-03-01 14:26:46 +00:00
raise " Can't push translations to #{ url } : #{ result . error } " if ! result . success?
2015-06-28 00:16:47 +00:00
# set new translator_key if given
if result . data [ 'translator_key' ]
2019-06-28 11:38:49 +00:00
Setting . set ( 'translator_key' , result . data [ 'translator_key' ] )
2015-06-28 00:16:47 +00:00
end
true
end
= begin
reset translations to origin
Translation . reset ( locale )
= end
def self . reset ( locale )
# only push changed translations
translations = Translation . where ( locale : locale )
2017-10-01 12:25:52 +00:00
translations . each do | translation |
2017-11-15 14:06:53 +00:00
if translation . target_initial . blank?
2015-06-28 00:16:47 +00:00
translation . destroy
elsif translation . target != translation . target_initial
translation . target = translation . target_initial
translation . save
end
2017-10-01 12:25:52 +00:00
end
2015-06-28 00:16:47 +00:00
2015-04-27 06:20:52 +00:00
true
end
= begin
get list of translations
2015-11-18 13:27:46 +00:00
list = Translation . lang ( 'de-de' )
2015-04-27 06:20:52 +00:00
= end
2015-11-18 13:27:46 +00:00
def self . lang ( locale , admin = false )
2013-08-06 14:17:30 +00:00
2015-06-28 00:16:47 +00:00
# use cache if not admin page is requested
2015-04-12 08:41:59 +00:00
if ! admin
2015-06-28 00:16:47 +00:00
data = cache_get ( locale )
2015-11-18 13:27:46 +00:00
return data if data
2015-04-12 08:41:59 +00:00
end
2015-06-28 00:16:47 +00:00
2015-11-18 13:27:46 +00:00
# show total translations as reference count
data = {
'total' = > Translation . where ( locale : 'de-de' ) . count ,
}
list = [ ]
2016-01-15 17:22:57 +00:00
translations = if admin
Translation . where ( locale : locale . downcase ) . order ( :source )
else
Translation . where ( locale : locale . downcase ) . where . not ( target : '' ) . order ( :source )
end
2017-10-01 12:25:52 +00:00
translations . each do | item |
2016-01-15 17:22:57 +00:00
translation_item = if admin
[
item . id ,
item . source ,
item . target ,
item . target_initial ,
item . format ,
]
else
[
item . id ,
item . source ,
item . target ,
item . format ,
]
end
list . push translation_item
2017-10-01 12:25:52 +00:00
end
2016-03-07 01:25:52 +00:00
# add presorted on top
presorted_list = [ ]
2017-11-23 08:09:44 +00:00
%w[ yes no or Year Years Month Months Day Days Hour Hours Minute Minutes Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec January February March April May June July August September October November December Mon Tue Wed Thu Fri Sat Sun Monday Tuesday Wednesday Thursday Friday Saturday Sunday ] . each do | presort |
2017-10-01 12:25:52 +00:00
list . each do | item |
2016-03-07 01:25:52 +00:00
next if item [ 1 ] != presort
2018-10-09 06:17:41 +00:00
2016-03-07 01:25:52 +00:00
presorted_list . push item
list . delete item
#list.unshift presort
2017-10-01 12:25:52 +00:00
end
end
2016-03-07 01:25:52 +00:00
data [ 'list' ] = presorted_list . concat list
2015-11-18 13:27:46 +00:00
# set cache
if ! admin
cache_set ( locale , data )
2013-08-06 14:17:30 +00:00
end
2013-08-06 08:18:50 +00:00
2015-06-28 00:16:47 +00:00
data
2013-08-06 08:18:50 +00:00
end
2015-04-27 06:20:52 +00:00
= begin
translate strings in ruby context , e . g . for notifications
2015-04-27 13:06:37 +00:00
translated = Translation . translate ( 'de-de' , 'New' )
2015-04-27 06:20:52 +00:00
= end
2013-01-04 13:14:20 +00:00
def self . translate ( locale , string )
# translate string
2015-11-18 13:27:46 +00:00
records = Translation . where ( locale : locale , source : string )
2017-10-01 12:25:52 +00:00
records . each do | record |
2013-01-04 14:28:55 +00:00
return record . target if record . source == string
2017-10-01 12:25:52 +00:00
end
2013-01-04 13:14:20 +00:00
# fallback lookup in en
2015-11-18 13:27:46 +00:00
records = Translation . where ( locale : 'en' , source : string )
2017-10-01 12:25:52 +00:00
records . each do | record |
2013-01-04 14:28:55 +00:00
return record . target if record . source == string
2017-10-01 12:25:52 +00:00
end
2013-01-04 13:14:20 +00:00
2015-04-30 17:20:27 +00:00
string
2013-01-04 13:14:20 +00:00
end
2016-12-03 19:37:37 +00:00
= begin
2019-02-10 11:01:38 +00:00
translate timestampes in ruby context , e . g . for notifications
translated = Translation . timestamp ( 'de-de' , 'Europe/Berlin' , '2018-10-10T10:00:00Z0' )
or
translated = Translation . timestamp ( 'de-de' , 'Europe/Berlin' , Time . zone . parse ( '2018-10-10T10:00:00Z0' ) )
= end
def self . timestamp ( locale , timezone , timestamp )
2020-10-22 13:57:01 +00:00
if timestamp . instance_of? ( String )
2019-02-10 11:01:38 +00:00
begin
timestamp_parsed = Time . zone . parse ( timestamp )
return timestamp . to_s if ! timestamp_parsed
timestamp = timestamp_parsed
rescue
return timestamp . to_s
end
end
begin
timestamp = timestamp . in_time_zone ( timezone )
rescue
return timestamp . to_s
end
2020-01-27 09:28:17 +00:00
2021-06-23 11:35:27 +00:00
record = Translation . where ( locale : locale , source : 'timestamp' , format : 'time' ) . pick ( :target )
2020-01-27 09:28:17 +00:00
return timestamp . to_s if ! record
2020-02-18 19:51:31 +00:00
record . sub! ( 'dd' , format ( '%<day>02d' , day : timestamp . day ) )
2019-02-10 11:01:38 +00:00
record . sub! ( 'd' , timestamp . day . to_s )
2020-02-18 19:51:31 +00:00
record . sub! ( 'mm' , format ( '%<month>02d' , month : timestamp . month ) )
2019-02-10 11:01:38 +00:00
record . sub! ( 'm' , timestamp . month . to_s )
record . sub! ( 'yyyy' , timestamp . year . to_s )
record . sub! ( 'yy' , timestamp . year . to_s . last ( 2 ) )
2020-02-18 19:51:31 +00:00
record . sub! ( 'SS' , format ( '%<second>02d' , second : timestamp . sec . to_s ) )
record . sub! ( 'MM' , format ( '%<minute>02d' , minute : timestamp . min . to_s ) )
record . sub! ( 'HH' , format ( '%<hour>02d' , hour : timestamp . hour . to_s ) )
2019-02-10 11:01:38 +00:00
" #{ record } ( #{ timezone } ) "
end
= begin
translate date in ruby context , e . g . for notifications
translated = Translation . date ( 'de-de' , '2018-10-10' )
or
translated = Translation . date ( 'de-de' , Date . parse ( '2018-10-10' ) )
= end
def self . date ( locale , date )
2020-10-22 13:57:01 +00:00
if date . instance_of? ( String )
2019-02-10 11:01:38 +00:00
begin
date_parsed = Date . parse ( date )
return date . to_s if ! date_parsed
date = date_parsed
rescue
return date . to_s
end
end
return date . to_s if date . class != Date
2021-06-23 11:35:27 +00:00
record = Translation . where ( locale : locale , source : 'date' , format : 'time' ) . pick ( :target )
2019-02-10 11:01:38 +00:00
return date . to_s if ! record
2020-02-18 19:51:31 +00:00
record . sub! ( 'dd' , format ( '%<day>02d' , day : date . day ) )
2019-02-10 11:01:38 +00:00
record . sub! ( 'd' , date . day . to_s )
2020-02-18 19:51:31 +00:00
record . sub! ( 'mm' , format ( '%<month>02d' , month : date . month ) )
2019-02-10 11:01:38 +00:00
record . sub! ( 'm' , date . month . to_s )
record . sub! ( 'yyyy' , date . year . to_s )
record . sub! ( 'yy' , date . year . to_s . last ( 2 ) )
record
end
= begin
2017-06-26 13:16:03 +00:00
load translations from local
2016-12-03 19:37:37 +00:00
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 )
2017-02-10 14:06:15 +00:00
version = Version . get
2020-02-18 19:51:31 +00:00
directory = Rails . root . join ( 'config/translations' )
2017-10-01 12:25:52 +00:00
locals_to_sync ( dedicated_locale ) . each do | locale |
2017-11-23 08:09:44 +00:00
file = Rails . root . join ( directory , " #{ locale } - #{ version } .yml " )
2016-12-03 19:37:37 +00:00
return false if ! File . exist? ( file )
2018-10-09 06:17:41 +00:00
2016-12-03 19:37:37 +00:00
data = YAML . load_file ( file )
to_database ( locale , data )
2017-10-01 12:25:52 +00:00
end
2016-12-03 19:37:37 +00:00
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 )
2017-02-10 14:06:15 +00:00
version = Version . get
2017-10-01 12:25:52 +00:00
locals_to_sync ( dedicated_locale ) . each do | locale |
2016-12-03 19:37:37 +00:00
url = " https://i18n.zammad.com/api/v1/translations/ #{ locale } "
if ! UserInfo . current_user_id
UserInfo . current_user_id = 1
end
result = UserAgent . get (
url ,
2017-02-02 14:37:49 +00:00
{
2017-02-10 14:06:15 +00:00
version : version ,
2017-02-02 14:37:49 +00:00
} ,
2016-12-03 19:37:37 +00:00
{
2018-12-19 17:31:51 +00:00
json : true ,
2016-12-03 19:37:37 +00:00
open_timeout : 8 ,
read_timeout : 24 ,
}
)
raise " Can't load translations from #{ url } : #{ result . error } " if ! result . success?
2020-02-18 19:51:31 +00:00
directory = Rails . root . join ( 'config/translations' )
2016-12-03 19:37:37 +00:00
if ! File . directory? ( directory )
Dir . mkdir ( directory , 0 o755 )
end
2017-11-23 08:09:44 +00:00
file = Rails . root . join ( directory , " #{ locale } - #{ version } .yml " )
2016-12-03 19:37:37 +00:00
File . open ( file , 'w' ) do | out |
YAML . dump ( result . data , out )
end
2017-10-01 12:25:52 +00:00
end
2016-12-03 19:37:37 +00:00
true
end
2017-06-26 13:16:03 +00:00
= begin
load translations from csv file
all :
Translation . load_from_csv
or
Translation . load_from_csv ( locale , file_location , file_charset ) # e. g. 'en-us' or 'de-de' and /path/to/translation_list.csv
e . g .
Translation . load_from_csv ( 'he-il' , '/Users/me/Downloads/Hebrew_translation_list-1.csv' , 'Windows-1255' )
Get source file at https : / /i 18 n . zammad . com / api / v1 / translations_empty_translation_list
= end
def self . load_from_csv ( locale_name , location , charset = 'UTF8' )
locale = Locale . find_by ( locale : locale_name )
if ! locale
raise " No such locale: #{ locale_name } "
end
if ! :: File . exist? ( location )
raise " No such file: #{ location } "
end
content = :: File . open ( location , " r: #{ charset } " ) . read
params = {
col_sep : ',' ,
}
rows = :: CSV . parse ( content , params )
2020-06-22 09:57:45 +00:00
rows . shift # remove header
2017-06-26 13:16:03 +00:00
translation_raw = [ ]
2017-10-01 12:25:52 +00:00
rows . each do | row |
2017-06-26 13:16:03 +00:00
raise " Can't import translation, source is missing " if row [ 0 ] . blank?
2018-10-09 06:17:41 +00:00
2017-06-26 13:16:03 +00:00
if row [ 1 ] . blank?
warn " Skipped #{ row [ 0 ] } , because translation is blank "
next
end
raise " Can't import translation, format is missing " if row [ 2 ] . blank?
2021-05-12 11:37:44 +00:00
raise " Can't import translation, format is invalid ( #{ row [ 2 ] } ) " if ! row [ 2 ] . match? ( %r{ ^(time|string)$ } )
2018-10-09 06:17:41 +00:00
2017-06-26 13:16:03 +00:00
item = {
'locale' = > locale . locale ,
'source' = > row [ 0 ] ,
'target' = > row [ 1 ] ,
'target_initial' = > '' ,
'format' = > row [ 2 ] ,
}
translation_raw . push item
2017-10-01 12:25:52 +00:00
end
2017-06-26 13:16:03 +00:00
to_database ( locale . name , translation_raw )
true
end
2018-09-04 11:37:51 +00:00
def self . remote_translation_need_update? ( raw , translations )
translations . each do | row |
next if row [ 1 ] != raw [ 'locale' ]
next if row [ 2 ] != raw [ 'source' ]
next if row [ 3 ] != raw [ 'format' ]
return false if row [ 4 ] == raw [ 'target' ] # no update if target is still the same
return false if row [ 4 ] != row [ 5 ] # no update if translation has already changed
2018-10-09 06:17:41 +00:00
2018-09-04 11:37:51 +00:00
return [ true , Translation . find ( row [ 0 ] ) ]
end
[ true , nil ]
end
2016-12-03 19:37:37 +00:00
private_class_method def self . to_database ( locale , data )
2018-09-04 11:37:51 +00:00
translations = Translation . where ( locale : locale ) . pluck ( :id , :locale , :source , :format , :target , :target_initial ) . to_a
2016-12-03 19:37:37 +00:00
ActiveRecord :: Base . transaction do
2018-09-04 11:37:51 +00:00
translations_to_import = [ ]
2017-10-01 12:25:52 +00:00
data . each do | translation_raw |
2018-09-04 11:37:51 +00:00
result = Translation . remote_translation_need_update? ( translation_raw , translations )
next if result == false
next if result . class != Array
2018-10-09 06:17:41 +00:00
2018-09-04 11:37:51 +00:00
if result [ 1 ]
result [ 1 ] . update! ( translation_raw . symbolize_keys! )
result [ 1 ] . save
2016-12-03 19:37:37 +00:00
else
2018-09-04 11:37:51 +00:00
translation_raw [ 'updated_by_id' ] = UserInfo . current_user_id || 1
translation_raw [ 'created_by_id' ] = UserInfo . current_user_id || 1
translations_to_import . push Translation . new ( translation_raw . symbolize_keys! )
2016-12-03 19:37:37 +00:00
end
2017-10-01 12:25:52 +00:00
end
2018-09-04 11:37:51 +00:00
if translations_to_import . present?
Translation . import translations_to_import
end
2016-12-03 19:37:37 +00:00
end
end
private_class_method def self . locals_to_sync ( dedicated_locale = nil )
locales_list = [ ]
2020-11-05 16:31:00 +00:00
if dedicated_locale
locales_list = [ dedicated_locale ]
else
2016-12-03 19:37:37 +00:00
locales = Locale . to_sync
2017-10-01 12:25:52 +00:00
locales . each do | locale |
2016-12-03 19:37:37 +00:00
locales_list . push locale . locale
2017-10-01 12:25:52 +00:00
end
2016-12-03 19:37:37 +00:00
end
locales_list
end
2012-05-18 14:24:00 +00:00
private
2015-05-01 12:31:46 +00:00
2013-06-12 15:59:58 +00:00
def set_initial
2018-09-04 11:37:51 +00:00
return true if target_initial . present?
2017-06-16 22:53:20 +00:00
return true if target_initial == ''
2018-10-09 06:17:41 +00:00
2015-05-07 12:10:38 +00:00
self . target_initial = target
2017-06-16 22:53:20 +00:00
true
2013-06-12 15:59:58 +00:00
end
2015-05-07 10:27:12 +00:00
2013-08-06 14:17:30 +00:00
def cache_clear
2020-09-30 09:07:01 +00:00
Cache . delete ( " TranslationMapOnlyContent:: #{ locale . downcase } " )
2017-06-16 22:53:20 +00:00
true
2013-08-06 14:17:30 +00:00
end
2015-09-09 18:16:20 +00:00
2013-08-06 14:17:30 +00:00
def self . cache_set ( locale , data )
2020-09-30 09:07:01 +00:00
Cache . write ( " TranslationMapOnlyContent:: #{ locale . downcase } " , data )
2013-08-06 14:17:30 +00:00
end
2016-01-15 17:22:57 +00:00
private_class_method :cache_set
2015-09-09 18:16:20 +00:00
2013-08-06 14:17:30 +00:00
def self . cache_get ( locale )
2021-05-31 13:05:54 +00:00
Cache . read ( " TranslationMapOnlyContent:: #{ locale . downcase } " )
2013-08-06 14:17:30 +00:00
end
2016-01-15 17:22:57 +00:00
private_class_method :cache_get
2012-05-18 14:24:00 +00:00
end