diff --git a/app/jobs/account/contacts_export_job.rb b/app/jobs/account/contacts_export_job.rb index 952795604..a66928a79 100644 --- a/app/jobs/account/contacts_export_job.rb +++ b/app/jobs/account/contacts_export_job.rb @@ -42,8 +42,13 @@ class Account::ContactsExportJob < ApplicationJob def attach_export_file(csv_data) return if csv_data.blank? + # Prepend UTF-8 BOM so that spreadsheet applications (e.g. Excel) + # correctly recognise the file encoding for non-ASCII characters + # such as Arabic, Japanese, and Chinese. + bom = "\xEF\xBB\xBF" + @account.contacts_export.attach( - io: StringIO.new(csv_data), + io: StringIO.new("#{bom}#{csv_data}"), filename: "#{@account.name}_#{@account.id}_contacts.csv", content_type: 'text/csv' ) diff --git a/spec/jobs/account/contacts_export_job_spec.rb b/spec/jobs/account/contacts_export_job_spec.rb index 9b9d74675..561015ea7 100644 --- a/spec/jobs/account/contacts_export_job_spec.rb +++ b/spec/jobs/account/contacts_export_job_spec.rb @@ -85,6 +85,13 @@ RSpec.describe Account::ContactsExportJob do expect(phone_numbers).to include('+910808080818', '+910808080808') end + it 'prepends UTF-8 BOM to the exported CSV for spreadsheet compatibility' do + described_class.perform_now(account.id, user.id, [], {}) + + raw = account.contacts_export.download + expect(raw.bytes[0..2]).to eq([0xEF, 0xBB, 0xBF]) + end + it 'returns all resolved contacts as results when filter is not prvoided' do create(:contact, account: account, email: nil, phone_number: nil) described_class.perform_now(account.id, user.id, %w[id name email column_not_present], {})