translation_spec.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Translation do
  4. before(:all) do # rubocop:disable RSpec/BeforeAfterAll
  5. described_class.where(locale: 'de-de').destroy_all
  6. described_class.sync_locale_from_po('de-de')
  7. end
  8. context 'default string translations' do
  9. it 'en with existing word' do
  10. expect(described_class.translate('en', 'New')).to eq('New')
  11. end
  12. it 'en-us with existing word' do
  13. expect(described_class.translate('en-us', 'New')).to eq('New')
  14. end
  15. it 'en with not existing word' do
  16. expect(described_class.translate('en', 'Some Not Existing Word')).to eq('Some Not Existing Word')
  17. end
  18. it 'de-de with existing word' do
  19. expect(described_class.translate('de-de', 'New')).to eq('Neu')
  20. end
  21. it 'de-de with not existing word' do
  22. expect(described_class.translate('de-de', 'Some Not Existing Word')).to eq('Some Not Existing Word')
  23. end
  24. it 'format string with given arguments' do
  25. expect(described_class.translate('en', 'a %s string', 'given')).to eq('a given string')
  26. end
  27. end
  28. context 'default string translations with fallback' do
  29. before do
  30. create(:translation, locale: 'de-de', source: 'dummy message', target: '', target_initial: '')
  31. described_class.sync_locale_from_po('de-de')
  32. end
  33. it 'fallbacks to provided message/string when de-de is empty' do
  34. expect(described_class.translate('de-de', 'dummy message')).to eq('dummy message')
  35. end
  36. end
  37. context 'when using find_source' do
  38. it 'de-de with existing title case word' do
  39. expect(described_class.find_source('de-de', 'New')).to have_attributes(source: 'New', target_initial: 'Neu', target: 'Neu')
  40. end
  41. it 'de-de with existing lower case word' do
  42. expect(described_class.find_source('de-de', 'new')).to have_attributes(source: 'new', target_initial: 'neu', target: 'neu')
  43. end
  44. it 'de-de with nonexisting existing source' do
  45. expect(described_class.find_source('de-de', 'nonexisting-string')).to be_nil
  46. end
  47. end
  48. context 'default timestamp translations' do
  49. it 'de-de with array' do
  50. expect(described_class.timestamp('de-de', 'Europe/Berlin', ['some value'])).to eq('["some value"]')
  51. end
  52. it 'not_existing with timestamp as string' do
  53. expect(described_class.timestamp('not_existing', 'Europe/Berlin', '2018-10-10T10:00:00Z0')).to eq('2018-10-10 12:00:00 +0200')
  54. end
  55. it 'not_existing with time object' do
  56. expect(described_class.timestamp('not_existing', 'Europe/Berlin', Time.zone.parse('2018-10-10T10:00:00Z0'))).to eq('2018-10-10 12:00:00 +0200')
  57. end
  58. it 'not_existing with invalid timestamp string' do
  59. expect(described_class.timestamp('not_existing', 'Europe/Berlin', 'something')).to eq('something')
  60. end
  61. it 'en-us with invalid time zone' do
  62. expect(described_class.timestamp('en-us', 'Invalid/TimeZone', '2018-10-10T10:00:00Z0')).to eq(Time.zone.parse('2018-10-10T10:00:00Z0').to_s)
  63. end
  64. it 'en-us with timestamp as string (am)' do
  65. expect(described_class.timestamp('en-us', 'Europe/Berlin', '2018-10-10T01:00:00Z0')).to eq('10/10/2018 3:00 am (Europe/Berlin)')
  66. end
  67. it 'en-us with timestamp as string (pm)' do
  68. expect(described_class.timestamp('en-us', 'Europe/Berlin', '2018-10-10T10:00:00Z0')).to eq('10/10/2018 12:00 pm (Europe/Berlin)')
  69. end
  70. it 'en-us with time object (am)' do
  71. expect(described_class.timestamp('en-us', 'Europe/Berlin', Time.zone.parse('2018-10-10T01:00:00Z0'))).to eq('10/10/2018 3:00 am (Europe/Berlin)')
  72. end
  73. it 'en-us with time object (pm)' do
  74. expect(described_class.timestamp('en-us', 'Europe/Berlin', Time.zone.parse('2018-10-10T10:00:00Z0'))).to eq('10/10/2018 12:00 pm (Europe/Berlin)')
  75. end
  76. it 'de-de with timestamp as string' do
  77. expect(described_class.timestamp('de-de', 'Europe/Berlin', '2018-10-10T10:00:00Z0')).to eq('10.10.2018 12:00 (Europe/Berlin)')
  78. end
  79. it 'de-de with time object' do
  80. expect(described_class.timestamp('de-de', 'Europe/Berlin', Time.zone.parse('2018-10-10T10:00:00Z0'))).to eq('10.10.2018 12:00 (Europe/Berlin)')
  81. end
  82. end
  83. context 'default date translations' do
  84. it 'de-de with array' do
  85. expect(described_class.date('de-de', ['some value'])).to eq('["some value"]')
  86. end
  87. it 'not_existing with date as string' do
  88. expect(described_class.date('not_existing', '2018-10-10')).to eq('2018-10-10')
  89. end
  90. it 'not_existing with date object' do
  91. expect(described_class.date('not_existing', Date.parse('2018-10-10'))).to eq('2018-10-10')
  92. end
  93. it 'not_existing with invalid data as string' do
  94. expect(described_class.date('not_existing', 'something')).to eq('something')
  95. end
  96. it 'en-us with date as string' do
  97. expect(described_class.date('en-us', '2018-10-10')).to eq('10/10/2018')
  98. end
  99. it 'en-us with date object' do
  100. expect(described_class.date('en-us', Date.parse('2018-10-10'))).to eq('10/10/2018')
  101. end
  102. it 'de-de with date as string' do
  103. expect(described_class.date('de-de', '2018-10-10')).to eq('10.10.2018')
  104. end
  105. it 'de-de with date object' do
  106. expect(described_class.date('de-de', Date.parse('2018-10-10'))).to eq('10.10.2018')
  107. end
  108. end
  109. context 'remote_translation_need_update? tests' do
  110. it 'translation is still the same' do
  111. translation = described_class.where(locale: 'de-de').last
  112. translations = described_class.where(locale: 'de-de').pluck(:id, :locale, :source, :target, :target_initial).to_a
  113. expect(
  114. described_class.remote_translation_need_update?(
  115. {
  116. 'source' => translation.source,
  117. 'locale' => translation.locale,
  118. 'target' => translation.target,
  119. 'target_initial' => translation.target_initial,
  120. }, translations
  121. )
  122. ).to be false
  123. end
  124. it 'translation target has locally changed' do
  125. translation = described_class.where(locale: 'de-de').last
  126. translation.target = 'some new translation'
  127. translation.save!
  128. translations = described_class.where(locale: 'de-de').pluck(:id, :locale, :source, :target, :target_initial).to_a
  129. expect(
  130. described_class.remote_translation_need_update?(
  131. {
  132. 'source' => translation.source,
  133. 'locale' => translation.locale,
  134. 'target' => translation.target,
  135. 'target_initial' => translation.target_initial,
  136. }, translations
  137. )
  138. ).to be false
  139. end
  140. it 'translation target has remotely changed' do
  141. translation = described_class.where(locale: 'de-de').last
  142. translations = described_class.where(locale: 'de-de').pluck(:id, :locale, :source, :target, :target_initial).to_a
  143. (result, translation_result) = described_class.remote_translation_need_update?(
  144. {
  145. 'source' => translation.source,
  146. 'locale' => translation.locale,
  147. 'target' => 'some new translation by remote',
  148. 'target_initial' => 'some new translation by remote',
  149. }, translations
  150. )
  151. expect(result).to be true
  152. expect(translation_result.attributes).to eq translation.attributes
  153. end
  154. it 'translation target has remotely and locally changed' do
  155. translation = described_class.where(locale: 'de-de').last
  156. translation.target = 'some new translation'
  157. translation.save!
  158. translations = described_class.where(locale: 'de-de').pluck(:id, :locale, :source, :target, :target_initial).to_a
  159. expect(
  160. described_class.remote_translation_need_update?(
  161. {
  162. 'source' => translation.source,
  163. 'locale' => translation.locale,
  164. 'target' => 'some new translation by remote',
  165. 'target_initial' => 'some new translation by remote',
  166. }, translations
  167. )
  168. ).to be false
  169. end
  170. end
  171. context 'custom translation tests' do
  172. it 'cycle of change and reload translation' do
  173. locale = 'de-de'
  174. # check for non existing custom changes
  175. list = described_class.lang(locale)
  176. list['list'].each do |item|
  177. translation = described_class.find_source(locale, item[1])
  178. expect(translation.class).to be(described_class)
  179. expect(locale).to eq(translation.locale)
  180. expect(translation.target).to eq(translation.target_initial)
  181. end
  182. # add custom changes
  183. translation = described_class.find_source(locale, 'open')
  184. expect(translation.class).to be(described_class)
  185. expect(translation.target).to eq('offen')
  186. expect(translation.target_initial).to eq('offen')
  187. translation.target = 'offen2'
  188. translation.save!
  189. list = described_class.lang(locale)
  190. list['list'].each do |item|
  191. translation = described_class.find_source(locale, item[1])
  192. expect(translation.class).to be(described_class)
  193. expect(locale).to eq(translation.locale)
  194. if translation.source == 'open'
  195. expect(translation.target).to eq('offen2')
  196. expect(translation.target_initial).to eq('offen')
  197. else
  198. expect(translation.target).to eq(translation.target_initial)
  199. end
  200. end
  201. # check for existing custom changes after new translations are loaded
  202. described_class.sync_locale_from_po(locale)
  203. list = described_class.lang(locale)
  204. list['list'].each do |item|
  205. translation = described_class.find_source(locale, item[1])
  206. expect(translation.class).to be(described_class)
  207. expect(locale).to eq(translation.locale)
  208. if translation.source == 'open'
  209. expect(translation.target).to eq('offen2')
  210. expect(translation.target_initial).to eq('offen')
  211. else
  212. expect(translation.target).to eq(translation.target_initial)
  213. end
  214. end
  215. # reset custom translations and check for non existing custom changes
  216. described_class.reset(locale)
  217. list = described_class.lang(locale)
  218. list['list'].each do |item|
  219. translation = described_class.find_source(locale, item[1])
  220. expect(translation.class).to be(described_class)
  221. expect(locale).to eq(translation.locale)
  222. expect(translation.target).to eq(translation.target_initial)
  223. end
  224. end
  225. end
  226. describe 'scope -> customized' do
  227. context 'when no customized translations exist' do
  228. it 'returns an empty array' do
  229. expect(described_class.customized).to eq([])
  230. end
  231. end
  232. context 'when customized translations exist' do
  233. before do
  234. described_class.find_by(locale: 'de-de', source: 'New').update!(target: 'Neu!')
  235. end
  236. it 'returns the customized translation' do
  237. expect(described_class.customized[0].source).to eq('New')
  238. end
  239. end
  240. context 'when only a new customized translation exists' do
  241. before do
  242. create(:translation, locale: 'de-de', source: 'A example', target: 'Ein Beispiel')
  243. end
  244. it 'returns the customized translation' do
  245. expect(described_class.customized[0].source).to eq('A example')
  246. end
  247. end
  248. end
  249. describe 'scope -> not_customized', :aggregate_failures do
  250. let(:translation_item) { described_class.find_by(locale: 'de-de', source: 'New') }
  251. context 'when customized items exists' do
  252. before do
  253. translation_item.update!(target: 'Neu!')
  254. create(:translation, locale: 'de-de', source: 'A example', target: 'Ein Beispiel')
  255. end
  256. it 'list without customized translations' do
  257. not_customized = described_class.not_customized.where(locale: 'de-de')
  258. expect(not_customized).to be_none { |item| item.source == translation_item.source }
  259. expect(not_customized).to be_none { |item| item.source == 'A example' }
  260. end
  261. end
  262. end
  263. describe 'reset' do
  264. context 'when record is not synced from codebase' do
  265. subject(:translation) { create(:translation, locale: 'de-de', source: 'A example', target: 'Ein Beispiel') }
  266. it 'no changes for record' do
  267. expect { translation.reset }.to not_change { translation.reload }
  268. end
  269. end
  270. context 'when record is synced from codebase' do
  271. subject(:translation) { described_class.find_by(locale: 'de-de', source: 'New') }
  272. context 'when translation was not customized' do
  273. it 'no changes for record' do
  274. expect { translation.reset }.to not_change { translation.reload }
  275. end
  276. end
  277. context 'when translation was customized' do
  278. before do
  279. translation.update!(target: 'Neu!')
  280. end
  281. it 'resets target to initial' do
  282. expect { translation.reset }.to change { translation.reload.target }.to(translation.target_initial)
  283. end
  284. end
  285. end
  286. end
  287. end