1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
" Vim script for checking .po files.
"
" Go through the file and verify that:
" - All %...s items in "msgid" are identical to the ones in "msgstr".
" - An error or warning code in "msgid" matches the one in "msgstr".
if 1 " Only execute this if the eval feature is available.
" using line continuation
set cpo&vim
" Function to get a split line at the cursor.
" Used for both msgid and msgstr lines.
" Removes all text except % items and returns the result.
func! GetMline()
let idline = substitute(getline('.'), '"\(.*\)"$', '\1', '')
while line('.') < line('$')
+
let line = getline('.')
if line[0] != '"'
break
endif
let idline .= substitute(line, '"\(.*\)"$', '\1', '')
endwhile
" remove '%', not used for formatting.
let idline = substitute(idline, "'%'", '', 'g')
let idline = substitute(idline, "%%", '', 'g')
" remove '%' used for plural forms.
let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '')
" remove duplicate positional format arguments
let idline2 = ""
while idline2 != idline
let idline2 = idline
let idline = substitute(idline, '%\([1-9][0-9]*\)\$\([-+ #''.*]*[0-9]*l\=[dsuxXpoc%]\)\(.*\)%\1$\([-+ #''.*]*\)\(l\=[dsuxXpoc%]\)', '%\1$\2\3\4', 'g')
endwhile
" remove everything but % items.
return substitute(idline, '[^%]*\(%([1-9][0-9]*\$)\=[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
endfunc
" This only works when 'wrapscan' is not set.
let s:save_wrapscan = &wrapscan
set nowrapscan
" Start at the first "msgid" line.
let wsv = winsaveview()
1
keeppatterns /^msgid\>
" When an error is detected this is set to the line number.
" Note: this is used in the Makefile.
let error = 0
while 1
let lnum = line('.')
if getline(lnum) =~ 'msgid "Text;.*;"'
if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"'
echomsg 'Mismatching ; in line ' . (lnum + 1)
echomsg 'Did you forget the trailing semicolon?'
if error == 0
let error = lnum + 1
endif
endif
endif
if getline(line('.') - 1) !~ "no-c-format"
" go over the "msgid" and "msgid_plural" lines
let prevfromline = 'foobar'
let plural = 0
while 1
if getline('.') =~ 'msgid_plural'
let plural += 1
endif
let fromline = GetMline()
if prevfromline != 'foobar' && prevfromline != fromline
\ && (plural != 1
\ || count(prevfromline, '%') + 1 != count(fromline, '%'))
echomsg 'Mismatching % in line ' . (line('.') - 1)
echomsg 'msgid: ' . prevfromline
echomsg 'msgid: ' . fromline
if error == 0
let error = line('.')
endif
endif
if getline('.') !~ 'msgid_plural'
break
endif
let prevfromline = fromline
endwhile
if getline('.') !~ '^msgstr'
echomsg 'Missing "msgstr" in line ' . line('.')
if error == 0
let error = line('.')
endif
endif
" check all the 'msgstr' lines
while getline('.') =~ '^msgstr'
let toline = GetMline()
if fromline != toline
\ && (plural == 0 || count(fromline, '%') != count(toline, '%') + 1)
echomsg 'Mismatching % in line ' . (line('.') - 1)
echomsg 'msgid: ' . fromline
echomsg 'msgstr: ' . toline
if error == 0
let error = line('.')
endif
endif
if line('.') == line('$')
break
endif
endwhile
endif
" Find next msgid. Quit when there is no more.
let lnum = line('.')
silent! keeppatterns /^msgid\>
if line('.') == lnum
break
endif
endwhile
" Check that error code in msgid matches the one in msgstr.
"
" Examples of mismatches found with msgid "E123: ..."
" - msgstr "E321: ..." error code mismatch
" - msgstr "W123: ..." warning instead of error
" - msgstr "E123 ..." missing colon
" - msgstr "..." missing error code
"
1
if search('msgid "\("\n"\)\?\([EW][0-9]\+:\).*\nmsgstr "\("\n"\)\?[^"]\@=\2\@!') > 0
echomsg 'Mismatching error/warning code in line ' . line('.')
if error == 0
let error = line('.')
endif
endif
func! CountNl(first, last)
let nl = 0
for lnum in range(a:first, a:last)
let nl += count(getline(lnum), "\n")
endfor
return nl
endfunc
" Check that the \n at the end of the msgid line is also present in the msgstr
" line. Skip over the header.
1
keeppatterns /^"MIME-Version:
while 1
let lnum = search('^msgid\>')
if lnum <= 0
break
endif
let strlnum = search('^msgstr\>')
let end = search('^$')
if end <= 0
let end = line('$') + 1
endif
let origcount = CountNl(lnum, strlnum - 1)
let transcount = CountNl(strlnum, end - 1)
" Allow for a few more or less line breaks when there are 2 or more
if origcount != transcount && (origcount <= 2 || transcount <= 2)
echomsg 'Mismatching "\n" in line ' . line('.')
if error == 0
let error = lnum
endif
endif
endwhile
" Check that the file is well formed according to msgfmts understanding
if executable("msgfmt")
let filename = expand("%")
" Newer msgfmt does not take OLD_PO_FILE_INPUT argument, must be in
" environment.
let $OLD_PO_FILE_INPUT = 'yes'
let a = system("msgfmt --statistics " . filename)
if v:shell_error != 0
let error = matchstr(a, filename.':\zs\d\+\ze:')+0
for line in split(a, '\n') | echomsg line | endfor
endif
endif
" Check that the plural form is properly initialized
1
let plural = search('^msgid_plural ', 'n')
if (plural && search('^"Plural-Forms: ', 'n') == 0) || (plural && search('^msgstr\[0\] ".\+"', 'n') != plural + 1)
if search('^"Plural-Forms: ', 'n') == 0
echomsg "Missing Plural header"
if error == 0
let error = search('\(^"[A-Za-z-_]\+: .*\\n"\n\)\+\zs', 'n') - 1
endif
elseif error == 0
let error = plural
endif
elseif !plural && search('^"Plural-Forms: ', 'n')
" We allow for a stray plural header, msginit adds one.
endif
" Check that 8bit encoding is used instead of 8-bit
let cte = search('^"Content-Transfer-Encoding:\s\+8-bit', 'n')
let ctc = search('^"Content-Type:.*;\s\+\<charset=[iI][sS][oO]_', 'n')
let ctu = search('^"Content-Type:.*;\s\+\<charset=utf-8', 'n')
if cte
echomsg "Content-Transfer-Encoding should be 8bit instead of 8-bit"
" TODO: make this an error
" if error == 0
" let error = cte
" endif
elseif ctc
echomsg "Content-Type charset should be 'ISO-...' instead of 'ISO_...'"
" TODO: make this an error
" if error == 0
" let error = ct
" endif
elseif ctu
echomsg "Content-Type charset should be 'UTF-8' instead of 'utf-8'"
" TODO: make this an error
" if error == 0
" let error = ct
" endif
endif
" Check that all lines are no longer than 80 chars
let overlong = search('\%>80v', 'n')
if overlong > 0
echomsg "Lines should be wrapped at 80 columns"
" TODO: make this an error
" if error == 0
" let error = overlong
" endif
endif
if error == 0
" If all was OK restore the view.
call winrestview(wsv)
echomsg "OK"
else
" Put the cursor on the line with the error.
exe error
endif
let &wrapscan = s:save_wrapscan
unlet s:save_wrapscan
endif
|