summaryrefslogtreecommitdiff
path: root/uvim/src/po/check.mnv
blob: 3320087be4683624addc5f6e48c3d3a27132ecf2 (plain)
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
" MNV script for checking .po files.
"
" Goes through the xx.po file (more than once)
" and verify various congruences
" See the comments in the code

" Last Update: 2025 Aug 06

if 1 " Only execute this if the eval feature is available.

" Using line continuation (set cpo to mnv default value)
let s:save_cpo = &cpo
set cpo&mnv

" This only works when 'wrapscan' is not set.
let s:save_wrapscan = &wrapscan
set nowrapscan

" 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('$')
    silent +
    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

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

" main

" Start at the first "msgid" line.
let wsv = winsaveview()
silent 1
silent 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
  " for each "msgid"

  " check msgid "Text;editor;"
  " translation must have two or more ";" (in case of more categories)
  let lnum = line('.')
  if getline(lnum) =~ 'msgid "Text;.*;"'
    if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"$'
      echomsg 'Mismatching ; in line ' . (lnum + 1)
      echomsg 'Wrong semicolon count'
      if error == 0
        let error = lnum + 1
      endif
    endif
  endif

  " check for equal number of % in msgid and msgstr
  " it is skipping the no-c-format strings
  if getline(line('.') - 1) !~ "no-c-format"
    " skip the "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 'possibly 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

    " checks that for each 'msgid' there is a 'msgstr'
    if getline('.') !~ '^msgstr'
      echomsg 'Missing "msgstr" in line ' . line('.')
      if error == 0
        let error = line('.')
      endif
    endif

    " check all the 'msgstr' lines have the same number of '%'
    " only the number of '%' is checked,
    " %d vs. %s or %d vs. %ld  go undetected
    while getline('.') =~ '^msgstr'
      let toline = GetMline()
      if fromline != toline
            \ && (plural == 0 || count(fromline, '%') != count(toline, '%') + 1)
        echomsg 'possibly 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
"
silent 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

" Check that the \n at the end of the msgid line is also present in the msgstr
" line.  Skip over the header.
silent 1
silent 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 eventual continuation of 'msgstr' is well formed
" final '""', '\n"', ' "' '/"' '."' '-"' are OK
" Beware, it can give false positives if the message is split
" in the middle of a word
silent 1
silent keeppatterns /^"MIME-Version:
while 1
  let lnum = search('^msgid\>')
  if lnum <= 0
    break
  endif
  " "msgstr" goes from strlnum to end-1
  let strlnum = search('^msgstr\>')
  let end = search('^$')
  if end <= 0
    let end = line('$') + 1
  endif
  " only if there is a continuation line...
  if end > strlnum + 1
    let ilnum = strlnum
    while ilnum < end - 1
      let iltype = 0
      if getline( ilnum ) =~ "^msgid_plural"
        let iltype = 2
      endif
      if getline( ilnum ) =~ "^msgstr["
        let iltype = 2
      endif
      if getline( ilnum ) =~ "\"\""
        let iltype = 1
      endif
      if getline( ilnum ) =~ " \"$"
        let iltype = 1
      endif
      if getline( ilnum ) =~ "-\"$"
        let iltype = 1
      endif
      if getline( ilnum ) =~ "/\"$"
        let iltype = 1
      endif
      if getline( ilnum ) =~ "\\.\"$"
        let iltype = 1
      endif
      if getline( ilnum ) =~ "\\\\n\"$"
        let iltype = 1
      endif
      if iltype == 0
        echomsg 'Possibly incorrect final at line: ' . ilnum
        " TODO: make this an error
        " if error == 0
        "   let error = ilnum
        " endif
      endif
      let ilnum += 1
    endwhile
  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
silent 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 "Warn: 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 "Warn: Content-Type charset should be 'ISO-...' instead of 'ISO_...'"
  " TODO: make this an error
  " if error == 0
  "   let error = ct
  " endif
elseif ctu
  echomsg "Warn: 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 no lines are longer than 80 chars (except comments)
let overlong = search('^[^#]\%>80v', 'n')
if overlong > 0
  echomsg "Warn: Lines should be wrapped at 80 columns"
  " TODO: make this an error
  " if error == 0
  "   let error = overlong
  " endif
endif

" Check that there is no trailing whitespace
let overlong = search('\s\+$', 'n')
if overlong > 0
  echomsg $"Warn: Trailing whitespace at line: {overlong}"
  " 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

" restore original wrapscan
let &wrapscan = s:save_wrapscan
unlet s:save_wrapscan

" restore original cpo
let &cpo = s:save_cpo
unlet s:save_cpo

endif

" mnv:sts=2:sw=2:et