translation.rb 5.2 KB

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