11" Vim script for checking .po files.
22"
3- " Go through the file and verify that:
4- " - All %...s items in "msgid" are identical to the ones in "msgstr".
5- " - An error or warning code in "msgid" matches the one in "msgstr".
3+ " Goes through the xx.po file (more than once)
4+ " and verify various congruences
5+ " See the comments in the code
66
7- " Last Update: 2025 Jul 10
7+ " Last Update: 2025 Jul 22
88
9- if 1 " Only execute this if the eval feature is available.
9+ if 1 " Only execute this if the eval feature is available.
1010
11- " using line continuation
11+ " Using line continuation (set cpo to vim default value)
12+ let s: save_cpo = &cpo
1213set cpo &vim
1314
15+ " This only works when 'wrapscan' is not set.
16+ let s: save_wrapscan = &wrapscan
17+ set nowrapscan
18+
1419" Function to get a split line at the cursor.
1520" Used for both msgid and msgstr lines.
1621" Removes all text except % items and returns the result.
@@ -43,9 +48,15 @@ func! GetMline()
4348 return substitute (idline, ' [^%]*\(%([1-9][0-9]*\$)\=[-+ #'' .0-9*]*l\=[dsuxXpoc%]\)\=' , ' \1' , ' g' )
4449endfunc
4550
46- " This only works when 'wrapscan' is not set.
47- let s: save_wrapscan = &wrapscan
48- set nowrapscan
51+ func ! CountNl (first , last )
52+ let nl = 0
53+ for lnum in range (a: first , a: last )
54+ let nl += count (getline (lnum), " \n " )
55+ endfor
56+ return nl
57+ endfunc
58+
59+ " main
4960
5061" Start at the first "msgid" line.
5162let wsv = winsaveview ()
@@ -57,63 +68,72 @@ keeppatterns /^msgid\>
5768let error = 0
5869
5970while 1
71+ " for each "msgid"
72+
73+ " check msgid "Text;editor;"
74+ " translation must have two ";" as well
6075 let lnum = line (' .' )
6176 if getline (lnum) = ~ ' msgid "Text;.*;"'
62- if getline (lnum + 1 ) !~ ' ^msgstr "\([^;]\+;\)\+"'
77+ if getline (lnum + 1 ) !~ ' ^msgstr "\([^;]\+;\)\+"$ '
6378 echomsg ' Mismatching ; in line ' . (lnum + 1 )
64- echomsg ' Did you forget the trailing semicolon? '
79+ echomsg ' Wrong semicolon count '
6580 if error == 0
66- let error = lnum + 1
81+ let error = lnum + 1
6782 endif
6883 endif
6984 endif
7085
86+ " check for equal number of % in msgid and msgstr
87+ " it is skipping the no-c-format strings
7188 if getline (line (' .' ) - 1 ) !~ " no-c-format"
72- " go over the "msgid" and "msgid_plural" lines
89+ " skip the "msgid_plural" lines
7390 let prevfromline = ' foobar'
7491 let plural = 0
7592 while 1
7693 if getline (' .' ) = ~ ' msgid_plural'
77- let plural += 1
94+ let plural += 1
7895 endif
7996 let fromline = GetMline ()
8097 if prevfromline != ' foobar' && prevfromline != fromline
81- \ && (plural != 1
82- \ || count (prevfromline, ' %' ) + 1 != count (fromline, ' %' ))
83- echomsg ' Mismatching % in line ' . (line (' .' ) - 1 )
84- echomsg ' msgid: ' . prevfromline
85- echomsg ' msgid: ' . fromline
86- if error == 0
87- let error = line (' .' )
88- endif
98+ \ && (plural != 1
99+ \ || count (prevfromline, ' %' ) + 1 != count (fromline, ' %' ))
100+ echomsg ' possibly mismatching % in line ' . (line (' .' ) - 1 )
101+ echomsg ' msgid: ' . prevfromline
102+ echomsg ' msgid: ' . fromline
103+ if error == 0
104+ let error = line (' .' )
105+ endif
89106 endif
90107 if getline (' .' ) !~ ' msgid_plural'
91- break
108+ break
92109 endif
93110 let prevfromline = fromline
94111 endwhile
95112
113+ " checks that for each 'msgid' there is a 'msgstr'
96114 if getline (' .' ) !~ ' ^msgstr'
97115 echomsg ' Missing "msgstr" in line ' . line (' .' )
98116 if error == 0
99- let error = line (' .' )
117+ let error = line (' .' )
100118 endif
101119 endif
102120
103- " check all the 'msgstr' lines
121+ " check all the 'msgstr' lines have the same number of '%'
122+ " only the number of '%' is checked,
123+ " %d vs. %s or %d vs. %ld go undetected
104124 while getline (' .' ) = ~ ' ^msgstr'
105125 let toline = GetMline ()
106126 if fromline != toline
107- \ && (plural == 0 || count (fromline, ' %' ) != count (toline, ' %' ) + 1 )
108- echomsg ' Mismatching % in line ' . (line (' .' ) - 1 )
109- echomsg ' msgid: ' . fromline
110- echomsg ' msgstr: ' . toline
111- if error == 0
112- let error = line (' .' )
113- endif
127+ \ && (plural == 0 || count (fromline, ' %' ) != count (toline, ' %' ) + 1 )
128+ echomsg ' possibly mismatching % in line ' . (line (' .' ) - 1 )
129+ echomsg ' msgid: ' . fromline
130+ echomsg ' msgstr: ' . toline
131+ if error == 0
132+ let error = line (' .' )
133+ endif
114134 endif
115135 if line (' .' ) == line (' $' )
116- break
136+ break
117137 endif
118138 endwhile
119139 endif
@@ -142,14 +162,6 @@ if search('msgid "\("\n"\)\?\([EW][0-9]\+:\).*\nmsgstr "\("\n"\)\?[^"]\@=\2\@!')
142162 endif
143163endif
144164
145- func ! CountNl (first , last )
146- let nl = 0
147- for lnum in range (a: first , a: last )
148- let nl += count (getline (lnum), " \n " )
149- endfor
150- return nl
151- endfunc
152-
153165" Check that the \n at the end of the msgid line is also present in the msgstr
154166" line. Skip over the header.
1551671
@@ -175,6 +187,55 @@ while 1
175187 endif
176188endwhile
177189
190+ " Check that the eventual continuation of 'msgstr' is well formed
191+ " final '""', '\n"', ' "' are OK
192+ " Beware, it can give false positives if the message is split
193+ " in the middle of a word
194+ 1
195+ keeppatterns /^" MIME-Version:
196+ while 1
197+ let lnum = search (' ^msgid\>' )
198+ if lnum <= 0
199+ break
200+ endif
201+ " "msgstr" goes from strlnum to end-1
202+ let strlnum = search (' ^msgstr\>' )
203+ let end = search (' ^$' )
204+ if end <= 0
205+ let end = line (' $' ) + 1
206+ endif
207+ " only if there is a continuation line...
208+ if end > strlnum + 1
209+ let ilnum = strlnum
210+ while ilnum < end - 1
211+ let iltype = 0
212+ if getline ( ilnum ) = ~ " ^msgid_plural"
213+ let iltype = 99
214+ endif
215+ if getline ( ilnum ) = ~ " ^msgstr["
216+ let iltype = 98
217+ endif
218+ if getline ( ilnum ) = ~ " \"\" "
219+ let iltype = 1
220+ endif
221+ if getline ( ilnum ) = ~ " \" $"
222+ let iltype = 2
223+ endif
224+ if getline ( ilnum ) = ~ " \\\\ n\" $"
225+ let iltype = 3
226+ endif
227+ if iltype == 0
228+ echomsg ' Possibly incorrect final at line: ' . ilnum
229+ " TODO: make this an error
230+ " if error == 0
231+ " let error = ilnum
232+ " endif
233+ endif
234+ let ilnum += 1
235+ endwhile
236+ endif
237+ endwhile
238+
178239" Check that the file is well formed according to msgfmts understanding
179240if executable (" msgfmt" )
180241 let filename = expand (" %" )
@@ -257,7 +318,14 @@ else
257318 exe error
258319endif
259320
321+ " restore original wrapscan
260322let &wrapscan = s: save_wrapscan
261323unlet s: save_wrapscan
262324
325+ " restore original cpo
326+ let &cpo = s: save_cpo
327+ unlet s: save_cpo
328+
263329endif
330+
331+ " vim:sts = 2 :sw = 2 :et
0 commit comments