Browse Source

Maintenance: Integrate mail/slack template files into Weblate toolchain.

Martin Gruner 2 years ago
parent
commit
783a51a749

+ 31 - 13
app/models/translation/synchronizes_from_po.rb

@@ -3,7 +3,31 @@
 module Translation::SynchronizesFromPo
   extend ActiveSupport::Concern
 
-  TRANSLATION_FILE_STRUCT = Struct.new(:translation, :translation_file, keyword_init: true).freeze
+  # Represents an entry to import to the Translation table.
+  class TranslationEntry
+    attr_reader :source, :translation, :translation_file
+
+    def self.create(locale, file, entry)
+      source = unescape_po(entry.msgid.to_s)
+
+      # Make sure to ignore fuzzy entries.
+      translation = entry.translated? ? unescape_po(entry.msgstr.to_s) : ''
+
+      # For 'en-*' locales, treat source as translation as well, to indicate that nothing is missing.
+      translation = source if translation.empty? && locale.start_with?('en')
+      new(source: source, translation: translation, translation_file: file)
+    end
+
+    def self.unescape_po(string)
+      string.gsub(%r{\\n}, "\n").gsub(%r{\\"}, '"').gsub(%r{\\\\}, '\\')
+    end
+
+    def initialize(source:, translation:, translation_file:)
+      @source = source
+      @translation = translation
+      @translation_file = translation_file
+    end
+  end
 
   class_methods do # rubocop:disable Metrics/BlockLength
 
@@ -67,29 +91,23 @@ module Translation::SynchronizesFromPo
       true
     end
 
-    def strings_for_locale(locale) # rubocop:disable Metrics/AbcSize
+    def strings_for_locale(locale)
       result = {}
       po_files_for_locale(locale).each do |file|
         require 'poparser' # Only load when it is actually used
         PoParser.parse_file(Rails.root.join(file)).entries.each do |entry|
 
-          source = unescape_po(entry.msgid.to_s)
-
-          # Make sure to ignore fuzzy entries.
-          translation = entry.translated? ? unescape_po(entry.msgstr.to_s) : ''
+          # Some strings are not needed in the database.
+          next if entry.flag&.to_s&.include?('zammad-skip-translation-sync')
 
-          # For 'en-*' locales, treat source as translation as well, to indicate that nothing is missing.
-          translation = source if translation.empty? && locale.start_with?('en')
-          result[source] = TRANSLATION_FILE_STRUCT.new(translation: translation, translation_file: file)
+          TranslationEntry.create(locale, file, entry).tap do |translation_entry|
+            result[translation_entry.source] = translation_entry
+          end
         end
       end
       result
     end
 
-    def unescape_po(string)
-      string.gsub(%r{\\n}, "\n").gsub(%r{\\"}, '"').gsub(%r{\\\\}, '\\')
-    end
-
     # Returns all po files for a locale with zammad.*.po as first entry,
     #   followed by all other files in alphabetical order
     # For en-us, i18n/zammad.pot will be returned instead.

+ 154 - 0
i18n/zammad.pot

@@ -50,6 +50,55 @@ msgstr ""
 msgid "\"Database\" stores all attachments in the database (not recommended for storing large amounts of data). \"Filesystem\" stores the data in the filesystem. You can switch between the modules even on a system that is already in production without any loss of data."
 msgstr ""
 
+#. This is the template file app/views/slack/ticket_create/en.md.erb in ERB/Markdown format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/slack/ticket_create/en.md.erb
+#, zammad-skip-translation-sync
+msgid "# #{ticket.title}\n_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Created by #{current_user.longname} at #{ticket.updated_at}_\n* #{t('Group')}: #{ticket.group.name}\n* #{t('Owner')}: #{ticket.owner.fullname}\n* #{t('State')}: #{t(ticket.state.name)}\n\n<% if @objects[:article] %>\n#{article.body_as_text}\n<% end %>\n"
+msgstr ""
+
+#. This is the template file app/views/slack/ticket_escalation/en.md.erb in ERB/Markdown format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/slack/ticket_escalation/en.md.erb
+#, zammad-skip-translation-sync
+msgid "# #{ticket.title}\n_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Escalated at #{ticket.escalation_at}_\nA ticket (#{ticket.title}) from \"#{ticket.customer.longname}\" is escalated since \"#{ticket.escalation_at}\"!\n\n<% if @objects[:article] %>\n#{article.body_as_text}\n<% end %>\n"
+msgstr ""
+
+#. This is the template file app/views/slack/ticket_reminder_reached/en.md.erb in ERB/Markdown format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/slack/ticket_reminder_reached/en.md.erb
+#, zammad-skip-translation-sync
+msgid "# #{ticket.title}\n_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Reminder reached!_\nA ticket needs attention, reminder reached for (#{ticket.title}) with customer \"*#{ticket.customer.longname}*\".\n\n<% if @objects[:article] %>\n#{article.body_as_text}\n<% end %>\n"
+msgstr ""
+
+#. This is the template file app/views/slack/ticket_update/en.md.erb in ERB/Markdown format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/slack/ticket_update/en.md.erb
+#, zammad-skip-translation-sync
+msgid "# #{ticket.title}\n_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Updated by #{current_user.longname} at #{ticket.updated_at}_\n<% if @objects[:changes].present? %>\n  <% @objects[:changes].each do |key, value| %>\n  * <%= t key %>: <%= h value[0] %> -> <%= h value[1] %>\n  <% end %>\n<% end %>\n\n<% if @objects[:article] %>\n#{article.body_as_text}\n<% end %>\n"
+msgstr ""
+
+#. This is the template file app/views/slack/ticket_escalation_warning/en.md.erb in ERB/Markdown format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/slack/ticket_escalation_warning/en.md.erb
+#, zammad-skip-translation-sync
+msgid "# #{ticket.title}\n_<#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}|Ticket##{ticket.number}>: Will escalate at #{ticket.escalation_at}_\nA ticket (#{ticket.title}) from \"#{ticket.customer.longname}\" will escalate at \"#{ticket.escalation_at}\"!\n\n<% if @objects[:article] %>\n#{article.body_as_text}\n<% end %>\n"
+msgstr ""
+
+#. This is the template file app/views/mailer/user_device_new_location/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/user_device_new_location/en.html.erb
+#, zammad-skip-translation-sync
+msgid "#{config.product_name} signin detected from a new country\n\n<div>Hi #{user.firstname},</div>\n<br>\n<div>It looks like you used your account with an <b>known device but from a new country</b> on \"#{user_device.created_at}\":</div>\n<br>\n<div>\nYour device: #{user_device.name}<br>\nYour location (relative): #{user_device.location}<br>\nYour IP: #{user_device.ip}<br>\n</div>\n<br>\n<div>The country has been added to your list of known devices, which you can view here:</div>\n<br>\n<div><a href=\"#{config.http_type}://#{config.fqdn}/#profile/devices\">#{config.http_type}://#{config.fqdn}/#profile/devices</a></div>\n<br>\n<div>If this wasn't you, remove the device, changing your account password, and contacting your administrator. Somebody might have gained unauthorized access to your account.</div>\n<br>\n<div>Your #{config.product_name} Team</div>\n"
+msgstr ""
+
+#. This is the template file app/views/mailer/user_device_new/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/user_device_new/en.html.erb
+#, zammad-skip-translation-sync
+msgid "#{config.product_name} signin detected from a new device\n\n<div>Hi #{user.firstname},</div>\n<br>\n<div>It looks like you signed into your account <b>using a new device</b> on \"#{user_device.created_at}\":</div>\n<br>\n<div>\nYour device: #{user_device.name}<br>\nYour location (relative): #{user_device.location}<br>\nYour IP: #{user_device.ip}<br>\n</div>\n<br>\n<div>Your device has been added to your list of known devices, which you can view here:</div>\n<br>\n<div><a href=\"#{config.http_type}://#{config.fqdn}/#profile/devices\">#{config.http_type}://#{config.fqdn}/#profile/devices</a></div>\n<br>\n<div>If this wasn't you, remove the device, changing your account password, and contacting your administrator. Somebody might have gained unauthorized access to your account.</div>\n<br>\n<div>Your #{config.product_name} Team</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/views/telegram/bot_add.jst.eco
 #: app/assets/javascripts/app/views/telegram/bot_edit.jst.eco
 msgid "%s API Token"
@@ -746,6 +795,13 @@ msgstr ""
 msgid "Admin Password Login"
 msgstr ""
 
+#. This is the template file app/views/mailer/admin_password_auth/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/admin_password_auth/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Administration login to #{config.product_name}\n\n<div>Hi #{user.firstname},</div>\n<br>\n<div>We received a request for an administration login for your #{config.product_name} account <b>#{user.login}</b>.</div>\n<br>\n<div>Please, click on the link below (or copy and paste the URL into your browser) to proceed:</div>\n<br>\n<div><a href=\"#{config.http_type}://#{config.fqdn}/#login/admin/{token.name}\">#{config.http_type}://#{config.fqdn}/#login/admin/{token.name}</a></div>\n<br>\n<div>This link takes you to a page where you can log in.</div>\n<br>\n<div>If you don't want to log in, please ignore this message.</div>\n<br>\n<div>Your #{config.product_name} Team</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/views/getting_started/admin.jst.eco
 msgid "Administrator Account"
 msgstr ""
@@ -895,6 +951,13 @@ msgstr ""
 msgid "Analyzing structure…"
 msgstr ""
 
+#. This is the template file app/views/mailer/ticket_update_received_merge/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/ticket_update_received_merge/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Another ticket was merged into ticket (#{ticket.title})\n\n<div>Hi #{recipient.firstname},</div>\n<br>\n<div>\nAnother ticket was merged into ticket (#{ticket.title}) by \"<b>#{current_user.longname}</b>\".\n</div>\n<br>\n<div>\n  <a href=\"#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}\" target=\"zammad_app\">#{t('View this in Zammad')}</a>\n</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/models/ticket.coffee
 msgid "Another ticket was merged into ticket |%s|"
 msgstr ""
@@ -2117,6 +2180,13 @@ msgstr ""
 msgid "Configure Basic Settings"
 msgstr ""
 
+#. This is the template file app/views/mailer/signup/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/signup/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Confirm your #{config.product_name} account, #{user.firstname} #{user.lastname}\n\n<div>Hi #{user.firstname},</div>\n<br>\n<div>Confirm your email address to complete your #{config.product_name} account. It's easy, just click the link below.</div>\n<br>\n<div><a href=\"#{config.http_type}://#{config.fqdn}/#email_verify/#{token.name}\">#{config.http_type}://#{config.fqdn}/#email_verify/#{token.name}</a></div>\n<br>\n<div>Your #{config.product_name} Team</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/controllers/_plugin/keyboard_shortcuts.coffee
 msgid "Confirm/submit dialog"
 msgstr ""
@@ -5539,6 +5609,13 @@ msgstr ""
 msgid "Invitation sent!"
 msgstr ""
 
+#. This is the template file app/views/mailer/user_invite/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/user_invite/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Invitation to #{config.product_name} at #{config.fqdn}\n\n<div>Hi #{user.firstname},</div>\n<br>\n<div>I (#{current_user.firstname} #{current_user.lastname}) invite you to #{config.product_name} - our customer support / ticket system platform.</div>\n<br>\n<div>Click <a href=\"#{config.http_type}://#{config.fqdn}/#password_reset_verify/#{token.name}\">here</a> and set your password.</div>\n<br>\n<div>Enjoy,</div>\n<br>\n<div>#{current_user.firstname} #{current_user.lastname}</div>\n<br>\n<div>Your #{config.product_name} Team</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/views/getting_started/agent.jst.eco
 #: app/assets/javascripts/app/views/widget/invite_user.jst.eco
 msgid "Invite"
@@ -6608,6 +6685,13 @@ msgstr ""
 msgid "New Ticket"
 msgstr ""
 
+#. This is the template file app/views/mailer/ticket_create/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/ticket_create/en.html.erb
+#, zammad-skip-translation-sync
+msgid "New Ticket (#{ticket.title})\n\n<div>Hi #{recipient.firstname},</div>\n<br>\n<div>A new ticket (#{ticket.title}) has been created by \"<b>#{current_user.longname}</b>\".</div>\n<br>\n<div>\n#{t('Group')}: #{ticket.group.name}<br>\n#{t('Owner')}: #{ticket.owner.fullname}<br>\n#{t('State')}: #{t(ticket.state.name)}<br>\n</div>\n<br>\n<% if @objects[:article] %>\n  <div>\n  #{t('Information')}:\n  <blockquote type=\"cite\">\n  #{article.body_as_html}\n  </blockquote>\n  </div>\n<% end %>\n<br>\n<div>\n  <a href=\"#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}\" target=\"zammad_app\">#{t('View this in Zammad')}</a>\n</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/controllers/trigger.coffee
 msgid "New Trigger"
 msgstr ""
@@ -7949,6 +8033,13 @@ msgstr ""
 msgid "Remember me"
 msgstr ""
 
+#. This is the template file app/views/mailer/ticket_reminder_reached/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/ticket_reminder_reached/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Reminder reached (#{ticket.title})\n\n<div>Hi #{recipient.firstname},</div>\n<br>\n<div>A ticket needs attention, reminder reached for (#{ticket.title}) with customer \"<b>#{ticket.customer.longname}</b>\".</div>\n<br>\n<% if @objects[:article] %>\n  <div>\n  #{t('Information')}:\n  <blockquote type=\"cite\">\n  #{article.body_as_html}\n  </blockquote>\n  </div>\n<% end %>\n<br>\n<div>\n  <a href=\"#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}\" target=\"zammad_app\">#{t('View this in Zammad')}</a>\n</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/views/generic/postmaster_match.jst.eco
 #: app/assets/javascripts/app/views/generic/postmaster_set_row.jst.eco
 #: app/assets/javascripts/app/views/generic/ticket_perform_action/row.jst.eco
@@ -8111,6 +8202,20 @@ msgstr ""
 msgid "Reset overview order"
 msgstr ""
 
+#. This is the template file app/views/mailer/password_reset/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/password_reset/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Reset your #{config.product_name} password\n\n<div>Hi #{user.firstname},</div>\n<br>\n<div>We received a request to reset the password for your #{config.product_name} account <b>#{user.login}</b>.</div>\n<br>\n<div>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</div>\n<br>\n<div><a href=\"#{config.http_type}://#{config.fqdn}/#password_reset_verify/#{token.name}\">#{config.http_type}://#{config.fqdn}/#password_reset_verify/#{token.name}</a></div>\n<br>\n<div>This link takes you to a page where you can change your password.</div>\n<br>\n<div>If you don't want to reset your password, please ignore this message. Your password will not be reset.</div>\n<br>\n<div>Your #{config.product_name} Team</div>\n"
+msgstr ""
+
+#. This is the template file app/views/mailer/signup_taken_reset/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/signup_taken_reset/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Reset your #{config.product_name} password\n\n<div>Hi #{user.firstname},</div>\n<br>\n<div>You or someone else tried to sign up with this email address. However, this email is already being used.</div>\n<br>\n<div>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</div>\n<br>\n<div><a href=\"#{config.http_type}://#{config.fqdn}/#password_reset_verify/#{token.name}\">#{config.http_type}://#{config.fqdn}/#password_reset_verify/#{token.name}</a></div>\n<br>\n<div>This link takes you to a page where you can change your password.</div>\n<br>\n<div>If you don't want to reset your password, please ignore this message. Your password will not be reset.</div>\n<br>\n<div>Your #{config.product_name} Team</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/controllers/translation.coffee
 msgid "Resetting changes…"
 msgstr ""
@@ -9325,6 +9430,13 @@ msgstr ""
 msgid "Test Ticket"
 msgstr ""
 
+#. This is the template file app/views/mailer/test_ticket/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/test_ticket/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Test Ticket!\n\n<div>Dear #{agent.firstname},</div>\n<br>\n<div>This is a <b>test ticket</b>. I'm a customer and I need some help! :)</div>\n<br>\n<div>#{customer.fullname}</div>\n<br>\n<div>The Zammad Project</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/controllers/_channel/sms.coffee
 msgid "Test message from Zammad"
 msgstr ""
@@ -10161,6 +10273,13 @@ msgstr ""
 msgid "Ticket %s merged."
 msgstr ""
 
+#. This is the template file app/views/mailer/ticket_update_merged_into/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/ticket_update_merged_into/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Ticket (#{ticket.title}) was merged into another ticket\n\n<div>Hi #{recipient.firstname},</div>\n<br>\n<div>\nTicket (#{ticket.title}) was merged into another ticket by \"<b>#{current_user.longname}</b>\".\n</div>\n<br>\n<div>\n  <a href=\"#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}\" target=\"zammad_app\">#{t('View this in Zammad')}</a>\n</div>\n"
+msgstr ""
+
 #: app/models/report.rb
 msgid "Ticket Count"
 msgstr ""
@@ -10256,6 +10375,13 @@ msgstr ""
 msgid "Ticket information"
 msgstr ""
 
+#. This is the template file app/views/mailer/ticket_escalation/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/ticket_escalation/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Ticket is escalated (#{ticket.title})\n\n<div>Hi #{recipient.firstname},</div>\n<br>\n<div>A ticket (#{ticket.title}) from \"<b>#{ticket.customer.longname}</b>\" is escalated since \"#{ticket.escalation_at}\"!</div>\n<br>\n<% if @objects[:article] %>\n  <div>\n  #{t('Information')}:\n  <blockquote type=\"cite\">\n  #{article.body_as_html}\n  </blockquote>\n  </div>\n<% end %>\n<br>\n<div>\n  <a href=\"#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}\" target=\"zammad_app\">#{t('View this in Zammad')}</a>\n</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/controllers/_profile/notification.coffee
 msgid "Ticket reminder reached"
 msgstr ""
@@ -10268,6 +10394,13 @@ msgstr ""
 msgid "Ticket viewers"
 msgstr ""
 
+#. This is the template file app/views/mailer/ticket_escalation_warning/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/ticket_escalation_warning/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Ticket will escalate (#{ticket.title})\n\n<div>Hi #{recipient.firstname},</div>\n<br>\n<div>A ticket (#{ticket.title}) from \"<b>#{ticket.customer.longname}</b>\" will escalate at \"#{ticket.escalation_at}\"!</div>\n<br>\n<% if @objects[:article] %>\n  <div>\n  #{t('Information')}:\n  <blockquote type=\"cite\">\n  #{article.body_as_html}\n  </blockquote>\n  </div>\n<% end %>\n<br>\n<div>\n  <a href=\"#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}\" target=\"zammad_app\">#{t('View this in Zammad')}</a>\n</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/models/ticket.coffee
 msgid "Ticket |%s| has escalated!"
 msgstr ""
@@ -10758,6 +10891,13 @@ msgstr ""
 msgid "Updated At"
 msgstr ""
 
+#. This is the template file app/views/mailer/ticket_update/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/ticket_update/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Updated Ticket (#{ticket.title})\n\n<div>Hi #{recipient.firstname},</div>\n<br>\n<div>\nTicket (#{ticket.title}) has been updated by \"<b>#{current_user.longname}</b>\".\n</div>\n<br>\n<% if @objects[:changes].present? %>\n  <div>\n  #{t('Changes')}:<br>\n  <% @objects[:changes].each do |key, value| %>\n    <%= t key %>: <%= h value[0] %> -&gt; <%= h value[1] %><br>\n  <% end %>\n  </div>\n<% end %>\n<br>\n<% if @objects[:article] %>\n  <div>\n  #{t('Information')}:\n  <blockquote type=\"cite\">\n  #{article.body_as_html}\n  </blockquote>\n  </div>\n<% end %>\n<br>\n<div>\n  <a href=\"#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}<% if @objects[:article] %>/#{article.id}<% end %>\" target=\"zammad_app\">#{t('View this in Zammad')}</a>\n</div>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/models/knowledge_base_answer_translation.coffee
 #: app/assets/javascripts/app/models/organization.coffee
 #: app/assets/javascripts/app/models/ticket.coffee
@@ -11627,6 +11767,13 @@ msgstr ""
 msgid "YouTube or Vimeo address"
 msgstr ""
 
+#. This is the template file app/views/mailer/password_change/en.html.erb in ERB/HTML format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/password_change/en.html.erb
+#, zammad-skip-translation-sync
+msgid "Your #{config.product_name} password has been changed\n\n<p>Hi #{user.firstname},</p>\n<br>\n<p>The password for your #{config.product_name} account <b>#{user.login}</b> has been changed recently.</p>\n<br>\n<p>This activity is not known to you? If not, contact your system administrator.</p>\n<br>\n<p>Your #{config.product_name} Team</p>\n"
+msgstr ""
+
 #: app/assets/javascripts/app/controllers/_integration/clearbit.coffee
 msgid "Your API key."
 msgstr ""
@@ -11774,6 +11921,13 @@ msgstr ""
 msgid "Zip"
 msgstr ""
 
+#. This is the template file app/views/mailer/email_oversized/en.txt.erb in ERB/Text format.
+#. Please make sure to translate it to a valid corresponding output structure.
+#: app/views/mailer/email_oversized/en.txt.erb
+#, zammad-skip-translation-sync
+msgid "[undeliverable] Message too large\nDear #{mail.from_display_name},\n\nUnfortunately your email titled \"#{mail.subject}\" could not be delivered to one or more recipients.\n\nYour message was #{mail.msg_size} MB but we only accept messages up to #{config.postmaster_max_size} MB.\n\nPlease reduce the message size and try again. Thank you for your understanding.\n\nRegretfully,\n\nPostmaster of #{config.fqdn}\n"
+msgstr ""
+
 #: db/seeds/settings.rb
 msgid "absolute - e. g. \"Monday 09:30\" or \"Tuesday 23. Feb 14:20\""
 msgstr ""

+ 25 - 0
lib/generators/translation_catalog/extracted_string.rb

@@ -0,0 +1,25 @@
+# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+class Generators::TranslationCatalog::ExtractedString
+  attr_accessor :string, :comment, :references, :skip_translation_sync
+
+  def initialize(string:, references:, comment: nil, skip_translation_sync: false)
+    @string = string
+    @comment = comment
+    @references = Set.new(references)
+    @skip_translation_sync = skip_translation_sync
+  end
+
+  def merge!(other)
+    if @string != other.string
+      raise 'Cannot merge different strings.'
+    end
+
+    if other.comment
+      @comment ||= ''
+      @comment += other.comment
+    end
+    @references = references.merge other.references
+    @skip_translation_sync &= other.skip_translation_sync
+  end
+end

+ 31 - 0
lib/generators/translation_catalog/extracted_strings.rb

@@ -0,0 +1,31 @@
+# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+class Generators::TranslationCatalog::ExtractedStrings < SimpleDelegator
+  def initialize
+    super({})
+  end
+
+  def <<(extracted_string)
+    string = extracted_string.string
+    # validate
+    if string.length > 3000
+      raise "Found a string that is longer than the allowed 3000 characters: '#{string}'"
+    end
+
+    if key? string
+      self[string].merge! extracted_string
+    else
+      self[string] = extracted_string
+    end
+  end
+
+  def merge!(other)
+    other.each_value do |s|
+      self << s
+    end
+  end
+
+  def sorted_values
+    keys.sort.map { |k| self[k] }
+  end
+end

+ 2 - 11
lib/generators/translation_catalog/extractor/base.rb

@@ -3,12 +3,11 @@
 class Generators::TranslationCatalog::Extractor::Base
 
   attr_reader   :options
-  attr_accessor :strings, :references
+  attr_accessor :strings
 
   def initialize(options:)
     @options = options
-    @strings = Set[]
-    @references = {}
+    @strings = Generators::TranslationCatalog::ExtractedStrings.new
   end
 
   def extract_translatable_strings(base_path)
@@ -17,14 +16,6 @@ class Generators::TranslationCatalog::Extractor::Base
     end
   end
 
-  def validate_strings
-    @strings.to_a.each do |s|
-      if s.length > 3000
-        raise "Found a string that longer than than the allowed 3000 characters: '#{s}'"
-      end
-    end
-  end
-
   def extract_from_string(string, filename)
     raise NotImplementedError
   end

+ 2 - 6
lib/generators/translation_catalog/extractor/chat.rb

@@ -3,7 +3,7 @@
 class Generators::TranslationCatalog::Extractor::Chat < Generators::TranslationCatalog::Extractor::Base
 
   # Extract some unmarked strings from the chat coffee files.
-  def extract_from_string(string, filename) # rubocop:disable Metrics/AbcSize
+  def extract_from_string(string, filename)
     return if string.empty?
 
     # title: '...'
@@ -15,12 +15,8 @@ class Generators::TranslationCatalog::Extractor::Chat < Generators::TranslationC
       result = match[1].gsub(%r{\\'}, "'")
       next if match[0].eql?('"') && result.include?('#{')
 
-      strings << result
-      references[result] ||= Set[]
-      references[result] << filename
+      strings << Generators::TranslationCatalog::ExtractedString.new(string: result, references: [filename])
     end
-
-    validate_strings
   end
 
   def find_files(base_path)

+ 2 - 5
lib/generators/translation_catalog/extractor/erb.rb

@@ -2,7 +2,7 @@
 
 class Generators::TranslationCatalog::Extractor::Erb < Generators::TranslationCatalog::Extractor::Base
 
-  def extract_from_string(string, filename) # rubocop:disable Metrics/AbcSize
+  def extract_from_string(string, filename)
     return if string.empty?
 
     # zt() / t()
@@ -14,12 +14,9 @@ class Generators::TranslationCatalog::Extractor::Erb < Generators::TranslationCa
         result = match[1].gsub(%r{\\'}, "'")
         next if match[0].eql?('"') && result.include?('#{')
 
-        strings << result
-        references[result] ||= Set[]
-        references[result] << filename
+        strings << Generators::TranslationCatalog::ExtractedString.new(string: result, references: [filename])
       end
     end
-    validate_strings
   end
 
   def find_files(base_path)

+ 2 - 6
lib/generators/translation_catalog/extractor/form_js.rb

@@ -3,7 +3,7 @@
 class Generators::TranslationCatalog::Extractor::FormJs < Generators::TranslationCatalog::Extractor::Base
 
   # Extract some unmarked strings from form.js asset file.
-  def extract_from_string(string, filename) # rubocop:disable Metrics/AbcSize
+  def extract_from_string(string, filename)
     return if string.empty?
 
     # display: '...'
@@ -14,12 +14,8 @@ class Generators::TranslationCatalog::Extractor::FormJs < Generators::Translatio
       result = match[1].gsub(%r{\\'}, "'")
       next if match[0].eql?('"') && result.include?('#{')
 
-      strings << result
-      references[result] ||= Set[]
-      references[result] << filename
+      strings << Generators::TranslationCatalog::ExtractedString.new(string: result, references: [filename])
     end
-
-    validate_strings
   end
 
   def find_files(base_path)

+ 2 - 5
lib/generators/translation_catalog/extractor/frontend.rb

@@ -2,7 +2,7 @@
 
 class Generators::TranslationCatalog::Extractor::Frontend < Generators::TranslationCatalog::Extractor::Base
 
-  def extract_from_string(string, filename) # rubocop:disable Metrics/AbcSize
+  def extract_from_string(string, filename)
     return if string.empty?
 
     # @T / @Ti
@@ -30,12 +30,9 @@ class Generators::TranslationCatalog::Extractor::Frontend < Generators::Translat
         result = match[1].gsub(%r{\\'}, "'")
         next if match[0].eql?('"') && result.include?('#{')
 
-        strings << result
-        references[result] ||= Set[]
-        references[result] << filename
+        strings << Generators::TranslationCatalog::ExtractedString.new(string: result, references: [filename])
       end
     end
-    validate_strings
   end
 
   def find_files(base_path)

+ 2 - 5
lib/generators/translation_catalog/extractor/ruby.rb

@@ -2,7 +2,7 @@
 
 class Generators::TranslationCatalog::Extractor::Ruby < Generators::TranslationCatalog::Extractor::Base
 
-  def extract_from_string(string, filename) # rubocop:disable Metrics/AbcSize
+  def extract_from_string(string, filename)
     return if string.empty?
 
     # Remove doc comments
@@ -24,12 +24,9 @@ class Generators::TranslationCatalog::Extractor::Ruby < Generators::TranslationC
         result = match[1].gsub(%r{\\'}, "'")
         next if match[0].eql?('"') && result.include?('#{')
 
-        strings << result
-        references[result] ||= Set[]
-        references[result] << filename
+        strings << Generators::TranslationCatalog::ExtractedString.new(string: result, references: [filename])
       end
     end
-    validate_strings
   end
 
   def find_files(base_path)

Some files were not shown because too many files changed in this diff