FIX: i18n integrity specs

FIX: check all .yml files in the project for integrity
FIX: ensure localized yamls are compatible with english
This commit is contained in:
Régis Hanol 2017-02-24 11:35:33 +01:00
parent a2c04be718
commit ecdae9f863
22 changed files with 116 additions and 140 deletions

View File

@ -2120,8 +2120,6 @@ ar:
performance_report: performance_report:
initial_post_raw: 'هذا الموضوع يحتوي على معلومات الاداء اليومي للموقع ' initial_post_raw: 'هذا الموضوع يحتوي على معلومات الاداء اليومي للموقع '
initial_topic_title: التبليغ عن اداء الموقع initial_topic_title: التبليغ عن اداء الموقع
topic_invite:
user_exists: "آسف، ذلك المستخدم قد تمت دعوته من قبل. تستطيع فقط أن تدعوا عضواً لموضوعِ مرة واحدة."
activemodel: activemodel:
errors: errors:
<<: *errors <<: *errors

View File

@ -1197,8 +1197,6 @@ da:
performance_report: performance_report:
initial_post_raw: Dette emne inkluderer daglige præstationsrapporter for dit site. initial_post_raw: Dette emne inkluderer daglige præstationsrapporter for dit site.
initial_topic_title: Website præstationsrapport initial_topic_title: Website præstationsrapport
topic_invite:
user_exists: "Beklager, men brugeren er allerede inviteret. Du kan kun invitere en bruger til et emne én gang."
tags: tags:
title: "Tags" title: "Tags"
staff_tag_disallowed: "Tagget \"%{tag}\" kan kun tildeles af personalet." staff_tag_disallowed: "Tagget \"%{tag}\" kan kun tildeles af personalet."

View File

@ -2900,8 +2900,6 @@ de:
performance_report: performance_report:
initial_post_raw: Dieser Beitrag enthält tägliche Leistungsberichte deiner Site. initial_post_raw: Dieser Beitrag enthält tägliche Leistungsberichte deiner Site.
initial_topic_title: Berichte zur Websitegeschwindigkeit initial_topic_title: Berichte zur Websitegeschwindigkeit
topic_invite:
user_exists: "Entschuldige, dieser Benutzer ist bereits eingeladen worden. Du kannst einen Benutzer nur einmal zu einem Thema einladen."
tags: tags:
title: "Schlagwörter" title: "Schlagwörter"
staff_tag_disallowed: "Das Schlagwort \"%{tag}\" darf nur vom Team verwendet werden." staff_tag_disallowed: "Das Schlagwort \"%{tag}\" darf nur vom Team verwendet werden."

View File

@ -2695,8 +2695,6 @@ es:
performance_report: performance_report:
initial_post_raw: Este tema contiene informes diarios sobre el rendimiento de tu sito. initial_post_raw: Este tema contiene informes diarios sobre el rendimiento de tu sito.
initial_topic_title: Informe sobre el rendimiento del sitio initial_topic_title: Informe sobre el rendimiento del sitio
topic_invite:
user_exists: "Lo sentimos, ese usuario ya ha sido invitado. Solo se puede invitar una vez a un usuario a un tema."
tags: tags:
title: "Etiquetas" title: "Etiquetas"
staff_tag_disallowed: "La etiqueta \"%{tag}\" solo puede ser insertada por moderadores." staff_tag_disallowed: "La etiqueta \"%{tag}\" solo puede ser insertada por moderadores."

View File

@ -2652,8 +2652,6 @@ fi:
performance_report: performance_report:
initial_post_raw: Tämä ketju sisältää päivittäisiä suorituskykyrapotteja sivustoltasi initial_post_raw: Tämä ketju sisältää päivittäisiä suorituskykyrapotteja sivustoltasi
initial_topic_title: Sivuston suorituskykyraportit initial_topic_title: Sivuston suorituskykyraportit
topic_invite:
user_exists: "Pahoittelut, tämä käyttäjä on jo kutsuttu. Voit kutsua toisen käyttäjän ketjuun vain yhden kerran."
tags: tags:
title: "Tunnisteet" title: "Tunnisteet"
staff_tag_disallowed: "Tunnisteen \"%{tag}\" voi lisätä vain henkilökunta" staff_tag_disallowed: "Tunnisteen \"%{tag}\" voi lisätä vain henkilökunta"

View File

@ -2665,8 +2665,6 @@ fr:
performance_report: performance_report:
initial_post_raw: Ce sujet comprend des rapports de performance journaliers concernant votre site. initial_post_raw: Ce sujet comprend des rapports de performance journaliers concernant votre site.
initial_topic_title: Rapports de performances du site initial_topic_title: Rapports de performances du site
topic_invite:
user_exists: "Désolé, cet utilisateur a déjà été invité. Vous ne pouvez inviter un utilisateur qu'une seule fois par sujet."
tags: tags:
title: "Tags" title: "Tags"
staff_tag_disallowed: "Le tag \"%{tag}\" ne peut être mis que par un responsable." staff_tag_disallowed: "Le tag \"%{tag}\" ne peut être mis que par un responsable."

View File

@ -2904,8 +2904,6 @@ he:
performance_report: performance_report:
initial_post_raw: 'נושא זה כולל דוחות פעילות יומיים עבור האתר שלך. ' initial_post_raw: 'נושא זה כולל דוחות פעילות יומיים עבור האתר שלך. '
initial_topic_title: דוחות פעילות לאתר initial_topic_title: דוחות פעילות לאתר
topic_invite:
user_exists: "מצטערים, המשתמשים כבר הוזמנו. ניתן להזמין משתמשים לנושא רק פעם אחת."
tags: tags:
title: "תגיות" title: "תגיות"
staff_tag_disallowed: "התג \"%{tag}\" ניתן רק על ידי הצוות." staff_tag_disallowed: "התג \"%{tag}\" ניתן רק על ידי הצוות."

View File

@ -1570,8 +1570,6 @@ nl:
performance_report: performance_report:
initial_post_raw: Deze topic bevat dagelijkse performancerapporten van je site initial_post_raw: Deze topic bevat dagelijkse performancerapporten van je site
initial_topic_title: Performancerapportages van de website initial_topic_title: Performancerapportages van de website
topic_invite:
user_exists: "Sorry, die gebruiker is al uitgenodigd. Je kan een gebruiker maar een keer voor een topic uitnodigen."
safe_mode: safe_mode:
no_customizations: "Alle website-aanpassingen uitschakelen" no_customizations: "Alle website-aanpassingen uitschakelen"
only_official: "Niet-officiële plug-ins uitschakelen" only_official: "Niet-officiële plug-ins uitschakelen"

View File

@ -2747,8 +2747,6 @@ pt:
performance_report: performance_report:
initial_post_raw: Este tópico inclui relatórios diários de desempenho para o seu sítio. initial_post_raw: Este tópico inclui relatórios diários de desempenho para o seu sítio.
initial_topic_title: Relatórios de desempenho do sítio initial_topic_title: Relatórios de desempenho do sítio
topic_invite:
user_exists: "Pedimos desculpa, esse utilizador já foi convidado. Pode convidar um utilizador para um tópico apenas uma vez."
tags: tags:
title: "Etiquetas" title: "Etiquetas"
staff_tag_disallowed: "A etiqueta \"%{tag}\" pode ser aplicada pela equipa de apoio apenas." staff_tag_disallowed: "A etiqueta \"%{tag}\" pode ser aplicada pela equipa de apoio apenas."

View File

@ -1882,8 +1882,6 @@ pt_BR:
performance_report: performance_report:
initial_post_raw: Este tópico inclui relatórios de performance diários de seu site. initial_post_raw: Este tópico inclui relatórios de performance diários de seu site.
initial_topic_title: Relatórios de performance do Site initial_topic_title: Relatórios de performance do Site
topic_invite:
user_exists: "Desculpe, este usuário já foi convidado. Você pode convidar um usuário para um tópico apenas uma única vez."
tags: tags:
title: "Marcações" title: "Marcações"
staff_tag_disallowed: "A marcação \"%{tag}\" pode ser aplicada somente pelo pessoal de apoio." staff_tag_disallowed: "A marcação \"%{tag}\" pode ser aplicada somente pelo pessoal de apoio."

View File

@ -2803,8 +2803,6 @@ ro:
performance_report: performance_report:
initial_post_raw: Acest subiect include rapoarte zilnice de performanță referitoare la site-ul tău. initial_post_raw: Acest subiect include rapoarte zilnice de performanță referitoare la site-ul tău.
initial_topic_title: Rapoarte de performanță website initial_topic_title: Rapoarte de performanță website
topic_invite:
user_exists: "Ne pare rău, acest utilizator a fost deja invitat. Nu poți să inviți un utilizator la un subiect decât o singură dată."
tags: tags:
title: "Etichete" title: "Etichete"
staff_tag_disallowed: "Eticheta \"%{tag}\" poate fi pusă doar de un membru al echipei." staff_tag_disallowed: "Eticheta \"%{tag}\" poate fi pusă doar de un membru al echipei."

View File

@ -1833,8 +1833,6 @@ ru:
performance_report: performance_report:
initial_post_raw: Эта тема содержит ежедневные отчеты активности форума. initial_post_raw: Эта тема содержит ежедневные отчеты активности форума.
initial_topic_title: Отчеты активности форума initial_topic_title: Отчеты активности форума
topic_invite:
user_exists: "К сожалению, этот пользователь уже был приглашён. Вы можете пригласить пользователя в тему только один раз."
tags: tags:
title: "Теги" title: "Теги"
staff_tag_disallowed: "Тег \"%{tag}\" может быть применён только персоналом." staff_tag_disallowed: "Тег \"%{tag}\" может быть применён только персоналом."

View File

@ -2306,8 +2306,6 @@ sv:
performance_report: performance_report:
initial_post_raw: Det här ämnet innehåller dagliga prestandarapporter för din webbplats. initial_post_raw: Det här ämnet innehåller dagliga prestandarapporter för din webbplats.
initial_topic_title: Prestandarapporter för webbplatsen initial_topic_title: Prestandarapporter för webbplatsen
topic_invite:
user_exists: "Tyvärr, den användaren har redan bjudits in. Du kan endast bjuda in en användare till ett ämne en gång."
tags: tags:
title: "Taggar" title: "Taggar"
staff_tag_disallowed: "Taggen \"%{tag}\" kan endast användas av personalen." staff_tag_disallowed: "Taggen \"%{tag}\" kan endast användas av personalen."

View File

@ -2048,8 +2048,6 @@ tr_TR:
performance_report: performance_report:
initial_post_raw: Bu konu siteniz hakkında günlük performans raporlarını içerir. initial_post_raw: Bu konu siteniz hakkında günlük performans raporlarını içerir.
initial_topic_title: Site performansı raporları initial_topic_title: Site performansı raporları
topic_invite:
user_exists: "Üzgünüz, bu kullanıcı zaten davet edildi. Konuya yalnızca bir kullanıcı davet edebilirsiniz."
tags: tags:
title: "Etiketler" title: "Etiketler"
staff_tag_disallowed: " \"%{tag}\" etiketi yalnızca görevliler tarafından eklenebilir gözüküyor." staff_tag_disallowed: " \"%{tag}\" etiketi yalnızca görevliler tarafından eklenebilir gözüküyor."

View File

@ -1913,8 +1913,6 @@ vi:
performance_report: performance_report:
initial_post_raw: Chủ đề này bao gồm các báo cáo hiệu suất hàng ngày của website. initial_post_raw: Chủ đề này bao gồm các báo cáo hiệu suất hàng ngày của website.
initial_topic_title: Báo cáo hiệu suất website initial_topic_title: Báo cáo hiệu suất website
topic_invite:
user_exists: "Xin lỗi, thành viên này đã được mời. Bạn chỉ có thể mời một người dùng đến một chủ đề một lần."
tags: tags:
title: "Thẻ" title: "Thẻ"
activemodel: activemodel:

View File

@ -2741,8 +2741,6 @@ zh_CN:
performance_report: performance_report:
initial_post_raw: 这个主题将用来展示网站每日性能报告。 initial_post_raw: 这个主题将用来展示网站每日性能报告。
initial_topic_title: 网站性能报告 initial_topic_title: 网站性能报告
topic_invite:
user_exists: "抱歉,用户已经被邀请了。你可能只想邀请用户参与主题一次。"
tags: tags:
title: "标签" title: "标签"
staff_tag_disallowed: "“%{tag}”只可由管理人员使用。" staff_tag_disallowed: "“%{tag}”只可由管理人员使用。"

View File

@ -2797,8 +2797,6 @@ zh_TW:
performance_report: performance_report:
initial_post_raw: 這個主題將用來展示網站每日性能報告。 initial_post_raw: 這個主題將用來展示網站每日性能報告。
initial_topic_title: 網站效能報表 initial_topic_title: 網站效能報表
topic_invite:
user_exists: "抱歉,用戶已經被邀請了。你可能只想邀請用戶參與主題一次。"
tags: tags:
title: "標籤" title: "標籤"
staff_tag_disallowed: "“%{tag}”只可由管理人員使用。" staff_tag_disallowed: "“%{tag}”只可由管理人員使用。"

View File

@ -143,6 +143,3 @@ vi:
: "Y" : "Y"
: "Y" : "Y"
Đ: "D" Đ: "D"
ê: "e"
ù: "u"
à: "a"

View File

@ -0,0 +1,17 @@
require_relative "locale_file_walker"
class DuplicateKeyFinder < LocaleFileWalker
def find_duplicates(path)
@keys_with_count = Hash.new { 0 }
handle_document(Psych.parse_file(path))
@keys_with_count.select { |key, count| count > 1 }.keys
end
protected
def handle_scalar(node, depth, parents)
super
@keys_with_count[parents.join('.')] += 1
end
end

View File

@ -1,28 +1,26 @@
require 'psych'
require 'set'
class LocaleFileWalker class LocaleFileWalker
protected protected
def handle_stream(stream)
stream.children.each { |document| handle_document(document) }
end
def handle_document(document) def handle_document(document)
# we want to ignore the language (first key), so let's start at -1 # we want to ignore the locale (first key), so let's start at -1
handle_nodes(document.root.children, -1, []) handle_nodes(document.root.children, -1, [])
end end
def handle_nodes(nodes, depth, parents) def handle_nodes(nodes, depth, parents)
if nodes return unless nodes
consecutive_scalars = 0 consecutive_scalars = 0
nodes.each do |node| nodes.each do |node|
consecutive_scalars = handle_node(node, depth, parents, consecutive_scalars) consecutive_scalars = handle_node(node, depth, parents, consecutive_scalars)
end
end end
end end
def handle_node(node, depth, parents, consecutive_scalars) def handle_node(node, depth, parents, consecutive_scalars)
node_is_scalar = node.is_a?(Psych::Nodes::Scalar) if node_is_scalar = node.is_a?(Psych::Nodes::Scalar)
valid_scalar?(depth, consecutive_scalars) ? handle_scalar(node, depth, parents) : handle_value(node.value, parents)
if node_is_scalar
handle_scalar(node, depth, parents) if valid_scalar?(depth, consecutive_scalars)
elsif node.is_a?(Psych::Nodes::Alias) elsif node.is_a?(Psych::Nodes::Alias)
handle_alias(node, depth, parents) handle_alias(node, depth, parents)
elsif node.is_a?(Psych::Nodes::Mapping) elsif node.is_a?(Psych::Nodes::Mapping)
@ -37,6 +35,9 @@ class LocaleFileWalker
depth >= 0 && consecutive_scalars.even? depth >= 0 && consecutive_scalars.even?
end end
def handle_value(value, parents)
end
def handle_scalar(node, depth, parents) def handle_scalar(node, depth, parents)
parents[depth] = node.value parents[depth] = node.value
end end

View File

@ -1,3 +1,3 @@
zh_CN: zn_CN:
site_settings: site_settings:
daily_performance_report: "每日分析 NGINX 日志并且发布详情主题到管理人员才能看到的主题" daily_performance_report: "每日分析 NGINX 日志并且发布详情主题到管理人员才能看到的主题"

View File

@ -1,127 +1,122 @@
require 'rails_helper' require "rails_helper"
require 'locale_file_walker' require "i18n/duplicate_key_finder"
def extract_locale(path)
path[/\.([^.]{2,})\.yml$/, 1]
end
PLURALIZATION_KEYS ||= ['zero', 'one', 'two', 'few', 'many', 'other']
def find_pluralizations(hash, parent_key = '', pluralizations = Hash.new)
hash.each do |key, value|
if Hash === value
current_key = parent_key.blank? ? key : "#{parent_key}.#{key}"
find_pluralizations(value, current_key, pluralizations)
elsif PLURALIZATION_KEYS.include? key
pluralizations[parent_key] = hash
end
end
pluralizations
end
def is_yaml_compatible?(english, translated)
english.each do |k, v|
if translated.has_key?(k)
if Hash === v
if Hash === translated[k]
return false unless is_yaml_compatible?(v, translated[k])
end
else
return false unless v.class == translated[k].class
end
end
end
true
end
describe "i18n integrity checks" do describe "i18n integrity checks" do
it 'should have an i18n key for all trust levels' do it 'has an i18n key for each Trust Levels' do
TrustLevel.all.each do |ts| TrustLevel.all.each do |ts|
expect(ts.name).not_to match(/translation missing/) expect(ts.name).not_to match(/translation missing/)
end end
end end
it "needs an i18n key (description) for each Site Setting" do it "has an i18n key for each Site Setting" do
SiteSetting.all_settings.each do |s| SiteSetting.all_settings.each do |s|
next if s[:setting] =~ /^test/ next if s[:setting][/^test_/]
expect(s[:description]).not_to match(/translation missing/) expect(s[:description]).not_to match(/translation missing/)
end end
end end
it "has an i18n key for each badge description" do it "has an i18n key for each Badge description" do
Badge.where(system: true).each do |b| Badge.where(system: true).each do |b|
expect(b.long_description).to be_present expect(b.long_description).to be_present
expect(b.description).to be_present expect(b.description).to be_present
end end
end end
it "has valid YAML for client" do Dir["#{Rails.root}/config/locales/client.*.yml"].each do |path|
Dir["#{Rails.root}/config/locales/client.*.yml"].each do |f| it "has valid client YAML for '#{path}'" do
locale = /.*\.([^.]{2,})\.yml$/.match(f)[1] yaml = YAML.load_file(path)
client = YAML.load_file("#{Rails.root}/config/locales/client.#{locale}.yml") locale = extract_locale(path)
expect(client.count).to eq(1)
expect(client[locale]).not_to eq(nil) expect(yaml.keys).to eq([locale])
expect(client[locale]["js"]).not_to eq(nil)
expect(client[locale]["admin_js"]).not_to eq(nil) expect(yaml[locale]["js"]).to be
expect(yaml[locale]["admin_js"]).to be
# expect(yaml[locale]["wizard_js"]).to be
end end
end end
it "has valid YAML for server" do Dir["#{Rails.root}/**/locale*/*.en.yml"].each do |english_path|
Dir["#{Rails.root}/config/locales/server.*.yml"].each do |f| english_yaml = YAML.load_file(english_path)["en"]
locale = /.*\.([^.]{2,})\.yml$/.match(f)[1]
server = YAML.load_file("#{Rails.root}/config/locales/server.#{locale}.yml")
expect(server.count).to eq(1)
expect(server[locale]).not_to eq(nil)
end
end
it "does not overwrite another language" do context(english_path) do
all = Dir["#{Rails.root}/config/locales/client.*.yml"] + Dir["#{Rails.root}/config/locales/server.*.yml"] it "has no duplicate keys" do
all.each do |f| english_duplicates = DuplicateKeyFinder.new.find_duplicates(english_path)
locale = /.*\.([^.]{2,})\.yml$/.match(f)[1] + ':' expect(english_duplicates).to be_empty
IO.foreach(f) do |line|
line.strip!
next if line.start_with? "#"
next if line.start_with? "---"
next if line.blank?
expect(line).to eq locale
break
end
end
end
describe 'English locale file' do
locale_files = ['config/locales', 'plugins/**/locales']
.product(['server.en.yml', 'client.en.yml'])
.collect { |dir, filename| Dir["#{Rails.root}/#{dir}/#{filename}"] }
.flatten
.map { |path| Pathname.new(path).relative_path_from(Rails.root) }
class DuplicateKeyFinder < LocaleFileWalker
def find_duplicates(filename)
@keys_with_count = {}
document = Psych.parse_file(filename)
handle_document(document)
@keys_with_count.delete_if { |key, count| count <= 1 }.keys
end end
protected find_pluralizations(english_yaml).each do |key, hash|
next if key["messages.restrict_dependent_destroy"]
def handle_scalar(node, depth, parents) it "has valid pluralizations for '#{key}'" do
super(node, depth, parents) expect(hash.keys).to contain_exactly("one", "other")
key = parents.join('.')
@keys_with_count[key] = @keys_with_count.fetch(key, 0) + 1
end
end
module Pluralizations
def self.load(path)
whitelist = Regexp.union([/messages.restrict_dependent_destroy/])
yaml = YAML.load_file("#{Rails.root}/#{path}")
pluralizations = find_pluralizations(yaml['en'])
pluralizations.reject! { |key| key.match(whitelist) }
pluralizations
end
def self.find_pluralizations(hash, parent_key = '', pluralizations = Hash.new)
hash.each do |key, value|
if value.is_a? Hash
current_key = parent_key.blank? ? key : "#{parent_key}.#{key}"
find_pluralizations(value, current_key, pluralizations)
elsif key == 'one' || key == 'other'
pluralizations[parent_key] = hash
end
end end
pluralizations
end end
end end
locale_files.each do |path| Dir[english_path.sub(".en.yml", ".*.yml")].each do |path|
context path do next if path[".en.yml"]
it 'has no duplicate keys' do
duplicates = DuplicateKeyFinder.new.find_duplicates("#{Rails.root}/#{path}") context(path) do
locale = extract_locale(path)
yaml = YAML.load_file(path)
it "has no duplicate keys" do
duplicates = DuplicateKeyFinder.new.find_duplicates(path)
expect(duplicates).to be_empty expect(duplicates).to be_empty
end end
Pluralizations.load(path).each do |key, values| it "does not overwrite another locale" do
it "key '#{key}' has valid pluralizations" do expect(yaml.keys).to eq([locale])
expect(values.keys).to contain_exactly('one', 'other')
end
end end
unless path["transliterate"]
it "is compatible with english" do
expect(is_yaml_compatible?(english_yaml, yaml)).to eq(true)
end
end
end end
end end
end end
end end