translation.rb 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Translation < ApplicationModel
  3. include Translation::SynchronizesFromPo
  4. before_create :set_initial
  5. validates :locale, presence: true
  6. scope :details, -> { select(:id, :locale, :source, :target, :target_initial, :is_synchronized_from_codebase) }
  7. scope :customized, -> { where('target_initial != target OR is_synchronized_from_codebase = false').reorder(locale: :asc, source: :asc) }
  8. scope :not_customized, -> { where('target_initial = target AND is_synchronized_from_codebase = true').reorder(source: :asc) }
  9. =begin
  10. reset translations to origin
  11. Translation.reset(locale)
  12. =end
  13. def self.reset(locale)
  14. # only push changed translations
  15. translations = Translation.where(locale: locale)
  16. translations.each do |translation|
  17. if translation.target_initial.blank?
  18. translation.destroy
  19. elsif translation.target != translation.target_initial
  20. translation.target = translation.target_initial
  21. translation.save
  22. end
  23. end
  24. true
  25. end
  26. =begin
  27. get list of translations
  28. list = Translation.lang('de-de')
  29. =end
  30. def self.lang(locale)
  31. locale = locale.downcase
  32. Rails.cache.fetch("#{self}/#{latest_change}/lang/#{locale}") do
  33. list = []
  34. translations = Translation.where(locale: locale).where.not(target: '').reorder(:source)
  35. translations.each do |item|
  36. list.push [
  37. item.id,
  38. item.source,
  39. item.target,
  40. ]
  41. end
  42. {
  43. 'total' => Translation.where(locale: locale).count,
  44. 'list' => list
  45. }
  46. end
  47. end
  48. =begin
  49. translate strings in Ruby context, e. g. for notifications
  50. translated = Translation.translate('de-de', 'New')
  51. =end
  52. def self.translate(locale, string, *args)
  53. translated = find_source(locale, string)&.target.presence || string
  54. translated %= args if args.any?
  55. translated
  56. end
  57. =begin
  58. find a translation record for a given locale and source string. 'find_by' might not be enough,
  59. because it could produce wrong matches on case insensitive MySQL databases.
  60. =end
  61. def self.find_source(locale, string)
  62. if ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2'
  63. # MySQL might find the wrong record with find_by with case insensitive locales, so use a direct comparison.
  64. where(locale: locale, source: string).find { |t| t.source.eql? string }
  65. else
  66. find_by(locale: locale, source: string)
  67. end
  68. end
  69. =begin
  70. translate timestampes in ruby context, e. g. for notifications
  71. translated = Translation.timestamp('de-de', 'Europe/Berlin', '2018-10-10T10:00:00Z0')
  72. or
  73. translated = Translation.timestamp('de-de', 'Europe/Berlin', Time.zone.parse('2018-10-10T10:00:00Z0'))
  74. =end
  75. def self.timestamp(locale, timezone, timestamp, append_timezone: true)
  76. if timestamp.instance_of?(String)
  77. begin
  78. timestamp_parsed = Time.zone.parse(timestamp)
  79. return timestamp.to_s if !timestamp_parsed
  80. timestamp = timestamp_parsed
  81. rescue
  82. return timestamp.to_s
  83. end
  84. end
  85. begin
  86. timestamp = timestamp.in_time_zone(timezone)
  87. rescue
  88. return timestamp.to_s
  89. end
  90. record = Translation.where(locale: locale, source: 'FORMAT_DATETIME').pick(:target)
  91. return timestamp.to_s if !record
  92. record.sub!('dd', format('%<day>02d', day: timestamp.day))
  93. record.sub!('d', timestamp.day.to_s)
  94. record.sub!('mm', format('%<month>02d', month: timestamp.month))
  95. record.sub!('m', timestamp.month.to_s)
  96. record.sub!('yyyy', timestamp.year.to_s)
  97. record.sub!('yy', timestamp.year.to_s.last(2))
  98. record.sub!('SS', format('%<second>02d', second: timestamp.sec.to_s))
  99. record.sub!('MM', format('%<minute>02d', minute: timestamp.min.to_s))
  100. record.sub!('HH', format('%<hour>02d', hour: timestamp.hour.to_s))
  101. record.sub!('l', timestamp.strftime('%l'))
  102. record.sub!('P', timestamp.strftime('%P'))
  103. record += " (#{timezone})" if append_timezone
  104. record
  105. end
  106. =begin
  107. translate date in ruby context, e. g. for notifications
  108. translated = Translation.date('de-de', '2018-10-10')
  109. or
  110. translated = Translation.date('de-de', Date.parse('2018-10-10'))
  111. =end
  112. def self.date(locale, date)
  113. if date.instance_of?(String)
  114. begin
  115. date_parsed = Date.parse(date)
  116. return date.to_s if !date_parsed
  117. date = date_parsed
  118. rescue
  119. return date.to_s
  120. end
  121. end
  122. return date.to_s if date.class != Date
  123. record = Translation.where(locale: locale, source: 'FORMAT_DATE').pick(:target)
  124. return date.to_s if !record
  125. record.sub!('dd', format('%<day>02d', day: date.day))
  126. record.sub!('d', date.day.to_s)
  127. record.sub!('mm', format('%<month>02d', month: date.month))
  128. record.sub!('m', date.month.to_s)
  129. record.sub!('yyyy', date.year.to_s)
  130. record.sub!('yy', date.year.to_s.last(2))
  131. record
  132. end
  133. def self.remote_translation_need_update?(raw, translations)
  134. translations.each do |row|
  135. next if row[1] != raw['locale']
  136. next if row[2] != raw['source']
  137. return false if row[3] == raw['target'] # no update if target is still the same
  138. return false if row[3] != row[4] # no update if translation has already changed
  139. return [true, Translation.find(row[0])]
  140. end
  141. [true, nil]
  142. end
  143. def reset
  144. return if !is_synchronized_from_codebase || target_initial == target
  145. self.target = target_initial
  146. save!
  147. end
  148. private
  149. def set_initial
  150. return true if target_initial.present?
  151. return true if target_initial == ''
  152. self.target_initial = target
  153. true
  154. end
  155. end