translation.rb 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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. version = Version.get
  168. directory = Rails.root.join('config/translations')
  169. locals_to_sync(dedicated_locale).each { |locale|
  170. file = Rails.root.join("#{directory}/#{locale}-#{version}.yml")
  171. return false if !File.exist?(file)
  172. data = YAML.load_file(file)
  173. to_database(locale, data)
  174. }
  175. true
  176. end
  177. =begin
  178. fetch translation from remote and store them in local file system
  179. all:
  180. Translation.fetch
  181. or
  182. Translation.fetch(locale) # e. g. 'en-us' or 'de-de'
  183. =end
  184. def self.fetch(dedicated_locale = nil)
  185. version = Version.get
  186. locals_to_sync(dedicated_locale).each { |locale|
  187. url = "https://i18n.zammad.com/api/v1/translations/#{locale}"
  188. if !UserInfo.current_user_id
  189. UserInfo.current_user_id = 1
  190. end
  191. result = UserAgent.get(
  192. url,
  193. {
  194. version: version,
  195. },
  196. {
  197. json: true,
  198. open_timeout: 8,
  199. read_timeout: 24,
  200. }
  201. )
  202. raise "Can't load translations from #{url}: #{result.error}" if !result.success?
  203. directory = Rails.root.join('config/translations')
  204. if !File.directory?(directory)
  205. Dir.mkdir(directory, 0o755)
  206. end
  207. file = Rails.root.join("#{directory}/#{locale}-#{version}.yml")
  208. File.open(file, 'w') do |out|
  209. YAML.dump(result.data, out)
  210. end
  211. }
  212. true
  213. end
  214. private_class_method def self.to_database(locale, data)
  215. translations = Translation.where(locale: locale).all
  216. ActiveRecord::Base.transaction do
  217. data.each { |translation_raw|
  218. # handle case insensitive sql
  219. translation = nil
  220. translations.each { |item|
  221. next if item.format != translation_raw['format']
  222. next if item.source != translation_raw['source']
  223. translation = item
  224. break
  225. }
  226. if translation
  227. # verify if update is needed
  228. update_needed = false
  229. translation_raw.each { |key, _value|
  230. # if translation target has changes
  231. next unless translation_raw[key] != translation.target
  232. # do not update translations which are already changed by user
  233. if translation.target == translation.target_initial
  234. update_needed = true
  235. break
  236. end
  237. }
  238. if update_needed
  239. translation.update_attributes(translation_raw.symbolize_keys!)
  240. translation.save
  241. end
  242. else
  243. if !UserInfo.current_user_id
  244. translation_raw['updated_by_id'] = 1
  245. translation_raw['created_by_id'] = 1
  246. end
  247. Translation.create(translation_raw.symbolize_keys!)
  248. end
  249. }
  250. end
  251. end
  252. private_class_method def self.locals_to_sync(dedicated_locale = nil)
  253. locales_list = []
  254. if !dedicated_locale
  255. locales = Locale.to_sync
  256. locales.each { |locale|
  257. locales_list.push locale.locale
  258. }
  259. else
  260. locales_list = [dedicated_locale]
  261. end
  262. locales_list
  263. end
  264. private
  265. def set_initial
  266. return if target_initial
  267. return if target_initial == ''
  268. self.target_initial = target
  269. end
  270. def cache_clear
  271. Cache.delete('TranslationMapOnlyContent::' + locale.downcase)
  272. end
  273. def self.cache_set(locale, data)
  274. Cache.write('TranslationMapOnlyContent::' + locale.downcase, data)
  275. end
  276. private_class_method :cache_set
  277. def self.cache_get(locale)
  278. Cache.get('TranslationMapOnlyContent::' + locale.downcase)
  279. end
  280. private_class_method :cache_get
  281. end