translation.rb 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class Translation < ApplicationModel
  3. before_create :set_initial
  4. after_create :cache_clear
  5. after_update :cache_clear
  6. after_destroy :cache_clear
  7. =begin
  8. sync translations from local if exists, otherwise from online
  9. all:
  10. Translation.sync
  11. Translation.sync(locale) # e. g. 'en-us' or 'de-de'
  12. =end
  13. def self.sync(dedicated_locale = nil)
  14. return true if load_from_file(dedicated_locale)
  15. load
  16. end
  17. =begin
  18. load translations from online
  19. all:
  20. Translation.load
  21. dedicated:
  22. Translation.load(locale) # e. g. 'en-us' or 'de-de'
  23. =end
  24. def self.load(dedicated_locale = nil)
  25. locals_to_sync(dedicated_locale).each { |locale|
  26. fetch(locale)
  27. load_from_file(locale)
  28. }
  29. true
  30. end
  31. =begin
  32. push translations to online
  33. Translation.push(locale)
  34. =end
  35. def self.push(locale)
  36. # only push changed translations
  37. translations = Translation.where(locale: locale)
  38. translations_to_push = []
  39. translations.each { |translation|
  40. if translation.target != translation.target_initial
  41. translations_to_push.push translation
  42. end
  43. }
  44. return true if translations_to_push.empty?
  45. url = 'https://i18n.zammad.com/api/v1/translations/thanks_for_your_support'
  46. translator_key = Setting.get('translator_key')
  47. result = UserAgent.post(
  48. url,
  49. {
  50. locale: locale,
  51. translations: translations_to_push,
  52. fqdn: Setting.get('fqdn'),
  53. translator_key: translator_key,
  54. },
  55. {
  56. json: true,
  57. open_timeout: 6,
  58. read_timeout: 16,
  59. }
  60. )
  61. raise "Can't push translations to #{url}: #{result.error}" if !result.success?
  62. # set new translator_key if given
  63. if result.data['translator_key']
  64. translator_key = Setting.set('translator_key', result.data['translator_key'])
  65. end
  66. true
  67. end
  68. =begin
  69. reset translations to origin
  70. Translation.reset(locale)
  71. =end
  72. def self.reset(locale)
  73. # only push changed translations
  74. translations = Translation.where(locale: locale)
  75. translations.each { |translation|
  76. if !translation.target_initial || translation.target_initial.empty?
  77. translation.destroy
  78. elsif translation.target != translation.target_initial
  79. translation.target = translation.target_initial
  80. translation.save
  81. end
  82. }
  83. true
  84. end
  85. =begin
  86. get list of translations
  87. list = Translation.lang('de-de')
  88. =end
  89. def self.lang(locale, admin = false)
  90. # use cache if not admin page is requested
  91. if !admin
  92. data = cache_get(locale)
  93. return data if data
  94. end
  95. # show total translations as reference count
  96. data = {
  97. 'total' => Translation.where(locale: 'de-de').count,
  98. }
  99. list = []
  100. translations = if admin
  101. Translation.where(locale: locale.downcase).order(:source)
  102. else
  103. Translation.where(locale: locale.downcase).where.not(target: '').order(:source)
  104. end
  105. translations.each { |item|
  106. translation_item = []
  107. translation_item = if admin
  108. [
  109. item.id,
  110. item.source,
  111. item.target,
  112. item.target_initial,
  113. item.format,
  114. ]
  115. else
  116. [
  117. item.id,
  118. item.source,
  119. item.target,
  120. item.format,
  121. ]
  122. end
  123. list.push translation_item
  124. }
  125. # add presorted on top
  126. presorted_list = []
  127. %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 { |presort|
  128. list.each { |item|
  129. next if item[1] != presort
  130. presorted_list.push item
  131. list.delete item
  132. #list.unshift presort
  133. }
  134. }
  135. data['list'] = presorted_list.concat list
  136. # set cache
  137. if !admin
  138. cache_set(locale, data)
  139. end
  140. data
  141. end
  142. =begin
  143. translate strings in ruby context, e. g. for notifications
  144. translated = Translation.translate('de-de', 'New')
  145. =end
  146. def self.translate(locale, string)
  147. # translate string
  148. records = Translation.where(locale: locale, source: string)
  149. records.each { |record|
  150. return record.target if record.source == string
  151. }
  152. # fallback lookup in en
  153. records = Translation.where(locale: 'en', source: string)
  154. records.each { |record|
  155. return record.target if record.source == string
  156. }
  157. string
  158. end
  159. =begin
  160. load locales from local
  161. all:
  162. Translation.load_from_file
  163. or
  164. Translation.load_from_file(locale) # e. g. 'en-us' or 'de-de'
  165. =end
  166. def self.load_from_file(dedicated_locale = nil)
  167. directory = Rails.root.join('config/translations')
  168. locals_to_sync(dedicated_locale).each { |locale|
  169. file = Rails.root.join("#{directory}/#{locale}.yml")
  170. return false if !File.exist?(file)
  171. data = YAML.load_file(file)
  172. to_database(locale, data)
  173. }
  174. true
  175. end
  176. =begin
  177. fetch translation from remote and store them in local file system
  178. all:
  179. Translation.fetch
  180. or
  181. Translation.fetch(locale) # e. g. 'en-us' or 'de-de'
  182. =end
  183. def self.fetch(dedicated_locale = nil)
  184. locals_to_sync(dedicated_locale).each { |locale|
  185. url = "https://i18n.zammad.com/api/v1/translations/#{locale}"
  186. if !UserInfo.current_user_id
  187. UserInfo.current_user_id = 1
  188. end
  189. result = UserAgent.get(
  190. url,
  191. {},
  192. {
  193. json: true,
  194. open_timeout: 8,
  195. read_timeout: 24,
  196. }
  197. )
  198. raise "Can't load translations from #{url}: #{result.error}" if !result.success?
  199. directory = Rails.root.join('config/translations')
  200. if !File.directory?(directory)
  201. Dir.mkdir(directory, 0o755)
  202. end
  203. file = Rails.root.join("#{directory}/#{locale}.yml")
  204. File.open(file, 'w') do |out|
  205. YAML.dump(result.data, out)
  206. end
  207. }
  208. true
  209. end
  210. private_class_method def self.to_database(locale, data)
  211. translations = Translation.where(locale: locale).all
  212. ActiveRecord::Base.transaction do
  213. data.each { |translation_raw|
  214. # handle case insensitive sql
  215. translation = nil
  216. translations.each { |item|
  217. next if item.format != translation_raw['format']
  218. next if item.source != translation_raw['source']
  219. translation = item
  220. break
  221. }
  222. if translation
  223. # verify if update is needed
  224. update_needed = false
  225. translation_raw.each { |key, _value|
  226. # if translation target has changes
  227. next unless translation_raw[key] != translation.target
  228. # do not update translations which are already changed by user
  229. if translation.target == translation.target_initial
  230. update_needed = true
  231. break
  232. end
  233. }
  234. if update_needed
  235. translation.update_attributes(translation_raw.symbolize_keys!)
  236. translation.save
  237. end
  238. else
  239. if !UserInfo.current_user_id
  240. translation_raw['updated_by_id'] = 1
  241. translation_raw['created_by_id'] = 1
  242. end
  243. Translation.create(translation_raw.symbolize_keys!)
  244. end
  245. }
  246. end
  247. end
  248. private_class_method def self.locals_to_sync(dedicated_locale = nil)
  249. locales_list = []
  250. if !dedicated_locale
  251. locales = Locale.to_sync
  252. locales.each { |locale|
  253. locales_list.push locale.locale
  254. }
  255. else
  256. locales_list = [dedicated_locale]
  257. end
  258. locales_list
  259. end
  260. private
  261. def set_initial
  262. return if target_initial
  263. return if target_initial == ''
  264. self.target_initial = target
  265. end
  266. def cache_clear
  267. Cache.delete('TranslationMapOnlyContent::' + locale.downcase)
  268. end
  269. def self.cache_set(locale, data)
  270. Cache.write('TranslationMapOnlyContent::' + locale.downcase, data)
  271. end
  272. private_class_method :cache_set
  273. def self.cache_get(locale)
  274. Cache.get('TranslationMapOnlyContent::' + locale.downcase)
  275. end
  276. private_class_method :cache_get
  277. end