Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
0fa0ed7d
Commit
0fa0ed7d
authored
Aug 25, 2017
by
Bob Van Landuyt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move `PoLinter` into `Gitlab::I18n`
parent
49b38194
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
196 additions
and
194 deletions
+196
-194
lib/gitlab/i18n/po_linter.rb
lib/gitlab/i18n/po_linter.rb
+193
-0
lib/gitlab/po_linter.rb
lib/gitlab/po_linter.rb
+0
-191
lib/tasks/gettext.rake
lib/tasks/gettext.rake
+2
-2
spec/lib/gitlab/i18n/po_linter_spec.rb
spec/lib/gitlab/i18n/po_linter_spec.rb
+1
-1
No files found.
lib/gitlab/i18n/po_linter.rb
0 → 100644
View file @
0fa0ed7d
require
'simple_po_parser'
module
Gitlab
module
I18n
class
PoLinter
attr_reader
:po_path
,
:entries
,
:locale
VARIABLE_REGEX
=
/%{\w*}|%[a-z]/
.
freeze
def
initialize
(
po_path
,
locale
=
I18n
.
locale
.
to_s
)
@po_path
=
po_path
@locale
=
locale
end
def
errors
@errors
||=
validate_po
end
def
validate_po
if
parse_error
=
parse_po
return
'PO-syntax errors'
=>
[
parse_error
]
end
validate_entries
end
def
parse_po
@entries
=
SimplePoParser
.
parse
(
po_path
)
nil
rescue
SimplePoParser
::
ParserError
=>
e
@entries
=
[]
e
.
message
end
def
validate_entries
errors
=
{}
entries
.
each
do
|
entry
|
# Skip validation of metadata
next
if
entry
[
:msgid
].
empty?
errors_for_entry
=
validate_entry
(
entry
)
errors
[
join_message
(
entry
[
:msgid
])]
=
errors_for_entry
if
errors_for_entry
.
any?
end
errors
end
def
validate_entry
(
entry
)
errors
=
[]
validate_flags
(
errors
,
entry
)
validate_variables
(
errors
,
entry
)
validate_newlines
(
errors
,
entry
)
errors
end
def
validate_newlines
(
errors
,
entry
)
message_id
=
join_message
(
entry
[
:msgid
])
if
entry
[
:msgid
].
is_a?
(
Array
)
errors
<<
"<
#{
message_id
}
> is defined over multiple lines, this breaks some tooling."
end
if
translations_in_entry
(
entry
).
any?
{
|
translation
|
translation
.
is_a?
(
Array
)
}
errors
<<
"<
#{
message_id
}
> has translations defined over multiple lines, this breaks some tooling."
end
end
def
validate_variables
(
errors
,
entry
)
if
entry
[
:msgid_plural
].
present?
validate_variables_in_message
(
errors
,
entry
[
:msgid
],
entry
[
'msgstr[0]'
])
# Validate all plurals
entry
.
keys
.
select
{
|
key_name
|
key_name
=~
/msgstr\[[1-9]\]/
}.
each
do
|
plural_key
|
validate_variables_in_message
(
errors
,
entry
[
:msgid_plural
],
entry
[
plural_key
])
end
else
validate_variables_in_message
(
errors
,
entry
[
:msgid
],
entry
[
:msgstr
])
end
end
def
validate_variables_in_message
(
errors
,
message_id
,
message_translation
)
message_id
=
join_message
(
message_id
)
required_variables
=
message_id
.
scan
(
VARIABLE_REGEX
)
validate_unnamed_variables
(
errors
,
required_variables
)
validate_translation
(
errors
,
message_id
,
required_variables
)
validate_variable_usage
(
errors
,
message_translation
,
required_variables
)
end
def
validate_translation
(
errors
,
message_id
,
used_variables
)
variables
=
fill_in_variables
(
used_variables
)
begin
Gitlab
::
I18n
.
with_locale
(
locale
)
do
translated
=
if
message_id
.
include?
(
'|'
)
FastGettext
::
Translation
.
s_
(
message_id
)
else
FastGettext
::
Translation
.
_
(
message_id
)
end
translated
%
variables
end
# `sprintf` could raise an `ArgumentError` when invalid passing something
# other than a Hash when using named variables
#
# `sprintf` could raise `TypeError` when passing a wrong type when using
# unnamed variables
#
# FastGettext::Translation could raise `RuntimeError` (raised as a string),
# or as subclassess `NoTextDomainConfigured` & `InvalidFormat`
#
# `FastGettext::Translation` could raise `ArgumentError` as subclassess
# `InvalidEncoding`, `IllegalSequence` & `InvalidCharacter`
rescue
ArgumentError
,
TypeError
,
RuntimeError
=>
e
errors
<<
"Failure translating to
#{
locale
}
with
#{
variables
}
:
#{
e
.
message
}
"
end
end
def
fill_in_variables
(
variables
)
if
variables
.
empty?
[]
elsif
variables
.
any?
{
|
variable
|
unnamed_variable?
(
variable
)
}
variables
.
map
do
|
variable
|
variable
==
'%d'
?
Random
.
rand
(
1000
)
:
Gitlab
::
Utils
.
random_string
end
else
variables
.
inject
({})
do
|
hash
,
variable
|
variable_name
=
variable
[
/\w+/
]
hash
[
variable_name
]
=
Gitlab
::
Utils
.
random_string
hash
end
end
end
def
validate_unnamed_variables
(
errors
,
variables
)
if
variables
.
size
>
1
&&
variables
.
any?
{
|
variable_name
|
unnamed_variable?
(
variable_name
)
}
errors
<<
'is combining multiple unnamed variables'
end
end
def
validate_variable_usage
(
errors
,
translation
,
required_variables
)
translation
=
join_message
(
translation
)
# We don't need to validate when the message is empty.
# Translations could fallback to the default, or we could be validating a
# language that does not have plurals.
return
if
translation
.
empty?
found_variables
=
translation
.
scan
(
VARIABLE_REGEX
)
missing_variables
=
required_variables
-
found_variables
if
missing_variables
.
any?
errors
<<
"<
#{
translation
}
> is missing: [
#{
missing_variables
.
to_sentence
}
]"
end
unknown_variables
=
found_variables
-
required_variables
if
unknown_variables
.
any?
errors
<<
"<
#{
translation
}
> is using unknown variables: [
#{
unknown_variables
.
to_sentence
}
]"
end
end
def
unnamed_variable?
(
variable_name
)
!
variable_name
.
start_with?
(
'%{'
)
end
def
validate_flags
(
errors
,
entry
)
if
flag
=
entry
[
:flag
]
errors
<<
"is marked
#{
flag
}
"
end
end
def
join_message
(
message
)
Array
(
message
).
join
end
def
translations_in_entry
(
entry
)
if
entry
[
:msgid_plural
].
present?
entry
.
fetch_values
(
*
plural_translation_keys_in_entry
(
entry
))
else
[
entry
[
:msgstr
]]
end
end
def
plural_translation_keys_in_entry
(
entry
)
entry
.
keys
.
select
{
|
key
|
key
=~
/msgstr\[\d*\]/
}
end
end
end
end
lib/gitlab/po_linter.rb
deleted
100644 → 0
View file @
49b38194
require
'simple_po_parser'
module
Gitlab
class
PoLinter
attr_reader
:po_path
,
:entries
,
:locale
VARIABLE_REGEX
=
/%{\w*}|%[a-z]/
.
freeze
def
initialize
(
po_path
,
locale
=
I18n
.
locale
.
to_s
)
@po_path
=
po_path
@locale
=
locale
end
def
errors
@errors
||=
validate_po
end
def
validate_po
if
parse_error
=
parse_po
return
'PO-syntax errors'
=>
[
parse_error
]
end
validate_entries
end
def
parse_po
@entries
=
SimplePoParser
.
parse
(
po_path
)
nil
rescue
SimplePoParser
::
ParserError
=>
e
@entries
=
[]
e
.
message
end
def
validate_entries
errors
=
{}
entries
.
each
do
|
entry
|
# Skip validation of metadata
next
if
entry
[
:msgid
].
empty?
errors_for_entry
=
validate_entry
(
entry
)
errors
[
join_message
(
entry
[
:msgid
])]
=
errors_for_entry
if
errors_for_entry
.
any?
end
errors
end
def
validate_entry
(
entry
)
errors
=
[]
validate_flags
(
errors
,
entry
)
validate_variables
(
errors
,
entry
)
validate_newlines
(
errors
,
entry
)
errors
end
def
validate_newlines
(
errors
,
entry
)
message_id
=
join_message
(
entry
[
:msgid
])
if
entry
[
:msgid
].
is_a?
(
Array
)
errors
<<
"<
#{
message_id
}
> is defined over multiple lines, this breaks some tooling."
end
if
translations_in_entry
(
entry
).
any?
{
|
translation
|
translation
.
is_a?
(
Array
)
}
errors
<<
"<
#{
message_id
}
> has translations defined over multiple lines, this breaks some tooling."
end
end
def
validate_variables
(
errors
,
entry
)
if
entry
[
:msgid_plural
].
present?
validate_variables_in_message
(
errors
,
entry
[
:msgid
],
entry
[
'msgstr[0]'
])
# Validate all plurals
entry
.
keys
.
select
{
|
key_name
|
key_name
=~
/msgstr\[[1-9]\]/
}.
each
do
|
plural_key
|
validate_variables_in_message
(
errors
,
entry
[
:msgid_plural
],
entry
[
plural_key
])
end
else
validate_variables_in_message
(
errors
,
entry
[
:msgid
],
entry
[
:msgstr
])
end
end
def
validate_variables_in_message
(
errors
,
message_id
,
message_translation
)
message_id
=
join_message
(
message_id
)
required_variables
=
message_id
.
scan
(
VARIABLE_REGEX
)
validate_unnamed_variables
(
errors
,
required_variables
)
validate_translation
(
errors
,
message_id
,
required_variables
)
validate_variable_usage
(
errors
,
message_translation
,
required_variables
)
end
def
validate_translation
(
errors
,
message_id
,
used_variables
)
variables
=
fill_in_variables
(
used_variables
)
begin
Gitlab
::
I18n
.
with_locale
(
locale
)
do
translated
=
if
message_id
.
include?
(
'|'
)
FastGettext
::
Translation
.
s_
(
message_id
)
else
FastGettext
::
Translation
.
_
(
message_id
)
end
translated
%
variables
end
# `sprintf` could raise an `ArgumentError` when invalid passing something
# other than a Hash when using named variables
#
# `sprintf` could raise `TypeError` when passing a wrong type when using
# unnamed variables
#
# FastGettext::Translation could raise `RuntimeError` (raised as a string),
# or as subclassess `NoTextDomainConfigured` & `InvalidFormat`
#
# `FastGettext::Translation` could raise `ArgumentError` as subclassess
# `InvalidEncoding`, `IllegalSequence` & `InvalidCharacter`
rescue
ArgumentError
,
TypeError
,
RuntimeError
=>
e
errors
<<
"Failure translating to
#{
locale
}
with
#{
variables
}
:
#{
e
.
message
}
"
end
end
def
fill_in_variables
(
variables
)
if
variables
.
empty?
[]
elsif
variables
.
any?
{
|
variable
|
unnamed_variable?
(
variable
)
}
variables
.
map
do
|
variable
|
variable
==
'%d'
?
Random
.
rand
(
1000
)
:
Gitlab
::
Utils
.
random_string
end
else
variables
.
inject
({})
do
|
hash
,
variable
|
variable_name
=
variable
[
/\w+/
]
hash
[
variable_name
]
=
Gitlab
::
Utils
.
random_string
hash
end
end
end
def
validate_unnamed_variables
(
errors
,
variables
)
if
variables
.
size
>
1
&&
variables
.
any?
{
|
variable_name
|
unnamed_variable?
(
variable_name
)
}
errors
<<
'is combining multiple unnamed variables'
end
end
def
validate_variable_usage
(
errors
,
translation
,
required_variables
)
translation
=
join_message
(
translation
)
# We don't need to validate when the message is empty.
# Translations could fallback to the default, or we could be validating a
# language that does not have plurals.
return
if
translation
.
empty?
found_variables
=
translation
.
scan
(
VARIABLE_REGEX
)
missing_variables
=
required_variables
-
found_variables
if
missing_variables
.
any?
errors
<<
"<
#{
translation
}
> is missing: [
#{
missing_variables
.
to_sentence
}
]"
end
unknown_variables
=
found_variables
-
required_variables
if
unknown_variables
.
any?
errors
<<
"<
#{
translation
}
> is using unknown variables: [
#{
unknown_variables
.
to_sentence
}
]"
end
end
def
unnamed_variable?
(
variable_name
)
!
variable_name
.
start_with?
(
'%{'
)
end
def
validate_flags
(
errors
,
entry
)
if
flag
=
entry
[
:flag
]
errors
<<
"is marked
#{
flag
}
"
end
end
def
join_message
(
message
)
Array
(
message
).
join
end
def
translations_in_entry
(
entry
)
if
entry
[
:msgid_plural
].
present?
entry
.
fetch_values
(
*
plural_translation_keys_in_entry
(
entry
))
else
[
entry
[
:msgstr
]]
end
end
def
plural_translation_keys_in_entry
(
entry
)
entry
.
keys
.
select
{
|
key
|
key
=~
/msgstr\[\d*\]/
}
end
end
end
lib/tasks/gettext.rake
View file @
0fa0ed7d
...
...
@@ -28,11 +28,11 @@ namespace :gettext do
linters
=
files
.
map
do
|
file
|
locale
=
File
.
basename
(
File
.
dirname
(
file
))
Gitlab
::
PoLinter
.
new
(
file
,
locale
)
Gitlab
::
I18n
::
PoLinter
.
new
(
file
,
locale
)
end
pot_file
=
Rails
.
root
.
join
(
'locale/gitlab.pot'
)
linters
.
unshift
(
Gitlab
::
PoLinter
.
new
(
pot_file
))
linters
.
unshift
(
Gitlab
::
I18n
::
PoLinter
.
new
(
pot_file
))
failed_linters
=
linters
.
select
{
|
linter
|
linter
.
errors
.
any?
}
...
...
spec/lib/gitlab/po_linter_spec.rb
→
spec/lib/gitlab/
i18n/
po_linter_spec.rb
View file @
0fa0ed7d
require
'spec_helper'
describe
Gitlab
::
PoLinter
do
describe
Gitlab
::
I18n
::
PoLinter
do
let
(
:linter
)
{
described_class
.
new
(
po_path
)
}
let
(
:po_path
)
{
'spec/fixtures/valid.po'
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment