Commit 17fc178c authored by Bob Van Landuyt's avatar Bob Van Landuyt

Correctly translate all forms in tests

parent f5d45e06
...@@ -3,16 +3,25 @@ module Gitlab ...@@ -3,16 +3,25 @@ module Gitlab
class MetadataEntry class MetadataEntry
attr_reader :entry_data attr_reader :entry_data
# Avoid testing too many plurals if `nplurals` was incorrectly set.
# Based on info on https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html
# which mentions special cases for numbers ending in 2 digits
MAX_FORMS_TO_TEST = 101
def initialize(entry_data) def initialize(entry_data)
@entry_data = entry_data @entry_data = entry_data
end end
def expected_plurals def expected_forms
return nil unless plural_information return nil unless plural_information
plural_information['nplurals'].to_i plural_information['nplurals'].to_i
end end
def forms_to_test
@forms_to_test ||= [expected_forms, MAX_FORMS_TO_TEST].compact.min
end
private private
def plural_information def plural_information
......
...@@ -36,7 +36,7 @@ module Gitlab ...@@ -36,7 +36,7 @@ module Gitlab
end end
@translation_entries = entries.map do |entry_data| @translation_entries = entries.map do |entry_data|
Gitlab::I18n::TranslationEntry.new(entry_data, metadata_entry.expected_plurals) Gitlab::I18n::TranslationEntry.new(entry_data, metadata_entry.expected_forms)
end end
nil nil
...@@ -84,25 +84,25 @@ module Gitlab ...@@ -84,25 +84,25 @@ module Gitlab
end end
def validate_number_of_plurals(errors, entry) def validate_number_of_plurals(errors, entry)
return unless metadata_entry&.expected_plurals return unless metadata_entry&.expected_forms
return unless entry.translated? return unless entry.translated?
if entry.has_plural? && entry.all_translations.size != metadata_entry.expected_plurals if entry.has_plural? && entry.all_translations.size != metadata_entry.expected_forms
errors << "should have #{metadata_entry.expected_plurals} "\ errors << "should have #{metadata_entry.expected_forms} "\
"#{'translations'.pluralize(metadata_entry.expected_plurals)}" "#{'translations'.pluralize(metadata_entry.expected_forms)}"
end end
end end
def validate_newlines(errors, entry) def validate_newlines(errors, entry)
if entry.msgid_contains_newlines? if entry.msgid_has_multiple_lines?
errors << 'is defined over multiple lines, this breaks some tooling.' errors << 'is defined over multiple lines, this breaks some tooling.'
end end
if entry.plural_id_contains_newlines? if entry.plural_id_has_multiple_lines?
errors << 'plural is defined over multiple lines, this breaks some tooling.' errors << 'plural is defined over multiple lines, this breaks some tooling.'
end end
if entry.translations_contain_newlines? if entry.translations_have_multiple_lines?
errors << 'has translations defined over multiple lines, this breaks some tooling.' errors << 'has translations defined over multiple lines, this breaks some tooling.'
end end
end end
...@@ -179,16 +179,41 @@ module Gitlab ...@@ -179,16 +179,41 @@ module Gitlab
end end
def numbers_covering_all_plurals def numbers_covering_all_plurals
@numbers_covering_all_plurals ||= Array.new(metadata_entry.expected_plurals) do |index| @numbers_covering_all_plurals ||= calculate_numbers_covering_all_plurals
number_for_pluralization(index) end
def calculate_numbers_covering_all_plurals
required_numbers = []
discovered_indexes = []
counter = 0
while discovered_indexes.size < metadata_entry.forms_to_test && counter < Gitlab::I18n::MetadataEntry::MAX_FORMS_TO_TEST
index_for_count = index_for_pluralization(counter)
unless discovered_indexes.include?(index_for_count)
discovered_indexes << index_for_count
required_numbers << counter
end
counter += 1
end end
required_numbers
end end
def number_for_pluralization(counter) def index_for_pluralization(counter)
pluralization_result = FastGettext.pluralisation_rule.call(counter) # This calls the C function that defines the pluralization rule, it can
# return a boolean (`false` represents 0, `true` represents 1) or an integer
# that specifies the plural form to be used for the given number
pluralization_result = Gitlab::I18n.with_locale(locale) do
FastGettext.pluralisation_rule.call(counter)
end
if pluralization_result.is_a?(TrueClass) || pluralization_result.is_a?(FalseClass) case pluralization_result
counter when false
0
when true
1
else else
pluralization_result pluralization_result
end end
...@@ -211,11 +236,13 @@ module Gitlab ...@@ -211,11 +236,13 @@ module Gitlab
end end
def validate_unnamed_variables(errors, variables) def validate_unnamed_variables(errors, variables)
if variables.any? { |name| unnamed_variable?(name) } && variables.any? { |name| !unnamed_variable?(name) } unnamed_variables, named_variables = variables.partition { |name| unnamed_variable?(name) }
if unnamed_variables.any? && named_variables.any?
errors << 'is combining named variables with unnamed variables' errors << 'is combining named variables with unnamed variables'
end end
if variables.select { |variable_name| unnamed_variable?(variable_name) }.size > 1 if unnamed_variables.size > 1
errors << 'is combining multiple unnamed variables' errors << 'is combining multiple unnamed variables'
end end
end end
......
...@@ -53,15 +53,15 @@ module Gitlab ...@@ -53,15 +53,15 @@ module Gitlab
nplurals > 1 || !has_plural? nplurals > 1 || !has_plural?
end end
def msgid_contains_newlines? def msgid_has_multiple_lines?
entry_data[:msgid].is_a?(Array) entry_data[:msgid].is_a?(Array)
end end
def plural_id_contains_newlines? def plural_id_has_multiple_lines?
entry_data[:msgid_plural].is_a?(Array) entry_data[:msgid_plural].is_a?(Array)
end end
def translations_contain_newlines? def translations_have_multiple_lines?
translation_entries.any? { |translation| translation.is_a?(Array) } translation_entries.any? { |translation| translation.is_a?(Array) }
end end
......
...@@ -51,7 +51,7 @@ namespace :gettext do ...@@ -51,7 +51,7 @@ namespace :gettext do
end end
task :updated_check do task :updated_check do
# Removeing all pre-translated files speeds up `gettext:find` as the # Removing all pre-translated files speeds up `gettext:find` as the
# files don't need to be merged. # files don't need to be merged.
`rm locale/*/gitlab.po` `rm locale/*/gitlab.po`
...@@ -62,15 +62,15 @@ namespace :gettext do ...@@ -62,15 +62,15 @@ namespace :gettext do
changed_files = `git diff --name-only`.lines.map(&:strip) changed_files = `git diff --name-only`.lines.map(&:strip)
# reset the locale folder for potential next tasks # reset the locale folder for potential next tasks
`git checkout locale` `git checkout -- locale`
if changed_files.include?('locale/gitlab.pot') if changed_files.include?('locale/gitlab.pot')
raise <<~MSG raise <<~MSG
Newly translated strings found, please add them to `gitlab.pot` by running: Newly translated strings found, please add them to `gitlab.pot` by running:
bundle exec rake gettext:find; git checkout locale/*/gitlab.po; bundle exec rake gettext:find; git checkout -- locale/*/gitlab.po;
Then check in the resulting `locale/gitlab.pot` Then commit and push the resulting changes to `locale/gitlab.pot`.
MSG MSG
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::I18n::MetadataEntry do describe Gitlab::I18n::MetadataEntry do
describe '#expected_plurals' do describe '#expected_forms' do
it 'returns the number of plurals' do it 'returns the number of plurals' do
data = { data = {
msgid: "", msgid: "",
...@@ -22,7 +22,7 @@ describe Gitlab::I18n::MetadataEntry do ...@@ -22,7 +22,7 @@ describe Gitlab::I18n::MetadataEntry do
} }
entry = described_class.new(data) entry = described_class.new(data)
expect(entry.expected_plurals).to eq(2) expect(entry.expected_forms).to eq(2)
end end
it 'returns 0 for the POT-metadata' do it 'returns 0 for the POT-metadata' do
...@@ -45,7 +45,7 @@ describe Gitlab::I18n::MetadataEntry do ...@@ -45,7 +45,7 @@ describe Gitlab::I18n::MetadataEntry do
} }
entry = described_class.new(data) entry = described_class.new(data)
expect(entry.expected_plurals).to eq(0) expect(entry.expected_forms).to eq(0)
end end
end end
end end
require 'spec_helper' require 'spec_helper'
require 'simple_po_parser' require 'simple_po_parser'
# Disabling this cop to allow for multi-language examples in comments
# rubocop:disable Style/AsciiComments
describe Gitlab::I18n::PoLinter do describe Gitlab::I18n::PoLinter do
let(:linter) { described_class.new(po_path) } let(:linter) { described_class.new(po_path) }
let(:po_path) { 'spec/fixtures/valid.po' } let(:po_path) { 'spec/fixtures/valid.po' }
def fake_translation(id:, translation:, plural_id: nil, plurals: []) def fake_translation(msgid:, translation:, plural_id: nil, plurals: [])
data = { msgid: id, msgid_plural: plural_id } data = { msgid: msgid, msgid_plural: plural_id }
if plural_id if plural_id
[translation, *plurals].each_with_index do |plural, index| [translation, *plurals].each_with_index do |plural, index|
allow(FastGettext::Translation).to receive(:n_).with(plural_id, index).and_return(plural) allow(FastGettext::Translation).to receive(:n_).with(msgid, plural_id, index).and_return(plural)
data.merge!("msgstr[#{index}]" => plural) data.merge!("msgstr[#{index}]" => plural)
end end
else else
allow(FastGettext::Translation).to receive(:_).with(id).and_return(translation) allow(FastGettext::Translation).to receive(:_).with(msgid).and_return(translation)
data[:msgstr] = translation data[:msgstr] = translation
end end
...@@ -174,7 +176,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -174,7 +176,7 @@ describe Gitlab::I18n::PoLinter do
describe '#validate_entries' do describe '#validate_entries' do
it 'keeps track of errors for entries' do it 'keeps track of errors for entries' do
fake_invalid_entry = fake_translation(id: "Hello %{world}", fake_invalid_entry = fake_translation(msgid: "Hello %{world}",
translation: "Bonjour %{monde}") translation: "Bonjour %{monde}")
allow(linter).to receive(:translation_entries) { [fake_invalid_entry] } allow(linter).to receive(:translation_entries) { [fake_invalid_entry] }
...@@ -204,7 +206,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -204,7 +206,7 @@ describe Gitlab::I18n::PoLinter do
describe '#validate_number_of_plurals' do describe '#validate_number_of_plurals' do
it 'validates when there are an incorrect number of translations' do it 'validates when there are an incorrect number of translations' do
fake_metadata = double fake_metadata = double
allow(fake_metadata).to receive(:expected_plurals).and_return(2) allow(fake_metadata).to receive(:expected_forms).and_return(2)
allow(linter).to receive(:metadata_entry).and_return(fake_metadata) allow(linter).to receive(:metadata_entry).and_return(fake_metadata)
fake_entry = Gitlab::I18n::TranslationEntry.new( fake_entry = Gitlab::I18n::TranslationEntry.new(
...@@ -226,7 +228,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -226,7 +228,7 @@ describe Gitlab::I18n::PoLinter do
it 'validates both singular and plural in a pluralized string when the entry has a singular' do it 'validates both singular and plural in a pluralized string when the entry has a singular' do
pluralized_entry = fake_translation( pluralized_entry = fake_translation(
id: 'Hello %{world}', msgid: 'Hello %{world}',
translation: 'Bonjour %{world}', translation: 'Bonjour %{world}',
plural_id: 'Hello all %{world}', plural_id: 'Hello all %{world}',
plurals: ['Bonjour tous %{world}'] plurals: ['Bonjour tous %{world}']
...@@ -244,7 +246,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -244,7 +246,7 @@ describe Gitlab::I18n::PoLinter do
it 'only validates plural when there is no separate singular' do it 'only validates plural when there is no separate singular' do
pluralized_entry = fake_translation( pluralized_entry = fake_translation(
id: 'Hello %{world}', msgid: 'Hello %{world}',
translation: 'Bonjour %{world}', translation: 'Bonjour %{world}',
plural_id: 'Hello all %{world}' plural_id: 'Hello all %{world}'
) )
...@@ -256,7 +258,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -256,7 +258,7 @@ describe Gitlab::I18n::PoLinter do
end end
it 'validates the message variables' do it 'validates the message variables' do
entry = fake_translation(id: 'Hello', translation: 'Bonjour') entry = fake_translation(msgid: 'Hello', translation: 'Bonjour')
expect(linter).to receive(:validate_variables_in_message) expect(linter).to receive(:validate_variables_in_message)
.with([], 'Hello', 'Bonjour') .with([], 'Hello', 'Bonjour')
...@@ -266,7 +268,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -266,7 +268,7 @@ describe Gitlab::I18n::PoLinter do
it 'validates variable usage in message ids' do it 'validates variable usage in message ids' do
entry = fake_translation( entry = fake_translation(
id: 'Hello %{world}', msgid: 'Hello %{world}',
translation: 'Bonjour %{world}', translation: 'Bonjour %{world}',
plural_id: 'Hello all %{world}', plural_id: 'Hello all %{world}',
plurals: ['Bonjour tous %{world}'] plurals: ['Bonjour tous %{world}']
...@@ -309,7 +311,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -309,7 +311,7 @@ describe Gitlab::I18n::PoLinter do
end end
describe '#validate_translation' do describe '#validate_translation' do
let(:entry) { fake_translation(id: 'Hello %{world}', translation: 'Bonjour %{world}') } let(:entry) { fake_translation(msgid: 'Hello %{world}', translation: 'Bonjour %{world}') }
it 'succeeds with valid variables' do it 'succeeds with valid variables' do
errors = [] errors = []
...@@ -330,7 +332,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -330,7 +332,7 @@ describe Gitlab::I18n::PoLinter do
end end
it 'adds an error message when translating fails when translating with context' do it 'adds an error message when translating fails when translating with context' do
entry = fake_translation(id: 'Tests|Hello', translation: 'broken') entry = fake_translation(msgid: 'Tests|Hello', translation: 'broken')
errors = [] errors = []
expect(FastGettext::Translation).to receive(:s_) { raise 'broken' } expect(FastGettext::Translation).to receive(:s_) { raise 'broken' }
...@@ -341,7 +343,7 @@ describe Gitlab::I18n::PoLinter do ...@@ -341,7 +343,7 @@ describe Gitlab::I18n::PoLinter do
end end
it "adds an error when trying to translate with incorrect variables when using unnamed variables" do it "adds an error when trying to translate with incorrect variables when using unnamed variables" do
entry = fake_translation(id: 'Hello %s', translation: 'Hello %d') entry = fake_translation(msgid: 'Hello %s', translation: 'Hello %d')
errors = [] errors = []
linter.validate_translation(errors, entry) linter.validate_translation(errors, entry)
...@@ -350,13 +352,55 @@ describe Gitlab::I18n::PoLinter do ...@@ -350,13 +352,55 @@ describe Gitlab::I18n::PoLinter do
end end
it "adds an error when trying to translate with named variables when unnamed variables are expected" do it "adds an error when trying to translate with named variables when unnamed variables are expected" do
entry = fake_translation(id: 'Hello %s', translation: 'Hello %{thing}') entry = fake_translation(msgid: 'Hello %s', translation: 'Hello %{thing}')
errors = [] errors = []
linter.validate_translation(errors, entry) linter.validate_translation(errors, entry)
expect(errors.first).to start_with("Failure translating to en") expect(errors.first).to start_with("Failure translating to en")
end end
it 'tests translation for all given forms' do
# Fake a language that has 3 forms to translate
fake_metadata = double
allow(fake_metadata).to receive(:forms_to_test).and_return(3)
allow(linter).to receive(:metadata_entry).and_return(fake_metadata)
entry = fake_translation(
msgid: '%d exception',
translation: '%d uitzondering',
plural_id: '%d exceptions',
plurals: ['%d uitzonderingen', '%d uitzonderingetjes']
)
# Make each count use a different index
allow(linter).to receive(:index_for_pluralization).with(0).and_return(0)
allow(linter).to receive(:index_for_pluralization).with(1).and_return(1)
allow(linter).to receive(:index_for_pluralization).with(2).and_return(2)
expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 0).and_call_original
expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 1).and_call_original
expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 2).and_call_original
linter.validate_translation([], entry)
end
end
describe '#numbers_covering_all_plurals' do
it 'can correctly find all required numbers to translate to Polish' do
# Polish used as an example with 3 different forms:
# 0, all plurals except the ones ending in 2,3,4: Kotów
# 1: Kot
# 2-3-4: Koty
# So translating with [0, 1, 2] will give us all different posibilities
fake_metadata = double
allow(fake_metadata).to receive(:forms_to_test).and_return(4)
allow(linter).to receive(:metadata_entry).and_return(fake_metadata)
allow(linter).to receive(:locale).and_return('pl_PL')
numbers = linter.numbers_covering_all_plurals
expect(numbers).to contain_exactly(0, 1, 2)
end
end end
describe '#fill_in_variables' do describe '#fill_in_variables' do
...@@ -380,3 +424,4 @@ describe Gitlab::I18n::PoLinter do ...@@ -380,3 +424,4 @@ describe Gitlab::I18n::PoLinter do
end end
end end
end end
# rubocop:enable Style/AsciiComments
...@@ -109,7 +109,7 @@ describe Gitlab::I18n::TranslationEntry do ...@@ -109,7 +109,7 @@ describe Gitlab::I18n::TranslationEntry do
data = { msgid: %w(hello world) } data = { msgid: %w(hello world) }
entry = described_class.new(data, 2) entry = described_class.new(data, 2)
expect(entry.msgid_contains_newlines?).to be_truthy expect(entry.msgid_has_multiple_lines?).to be_truthy
end end
end end
...@@ -118,7 +118,7 @@ describe Gitlab::I18n::TranslationEntry do ...@@ -118,7 +118,7 @@ describe Gitlab::I18n::TranslationEntry do
data = { msgid_plural: %w(hello world) } data = { msgid_plural: %w(hello world) }
entry = described_class.new(data, 2) entry = described_class.new(data, 2)
expect(entry.plural_id_contains_newlines?).to be_truthy expect(entry.plural_id_has_multiple_lines?).to be_truthy
end end
end end
...@@ -127,7 +127,7 @@ describe Gitlab::I18n::TranslationEntry do ...@@ -127,7 +127,7 @@ describe Gitlab::I18n::TranslationEntry do
data = { msgstr: %w(hello world) } data = { msgstr: %w(hello world) }
entry = described_class.new(data, 2) entry = described_class.new(data, 2)
expect(entry.translations_contain_newlines?).to be_truthy expect(entry.translations_have_multiple_lines?).to be_truthy
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment