blob: 2ea4a3824e12cc0e6e8ade4f6fc3ee9bef0d961a [file] [log] [blame]
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +00001" Vim script for checking .po files.
2"
Bram Moolenaar595f51c2008-06-09 12:46:00 +00003" 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".
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +00006
7if 1 " Only execute this if the eval feature is available.
8
Bram Moolenaar78ee6252023-05-28 18:39:55 +01009" using line continuation
10set cpo&vim
11
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000012" Function to get a split line at the cursor.
13" Used for both msgid and msgstr lines.
14" Removes all text except % items and returns the result.
15func! GetMline()
16 let idline = substitute(getline('.'), '"\(.*\)"$', '\1', '')
17 while line('.') < line('$')
18 +
19 let line = getline('.')
20 if line[0] != '"'
21 break
22 endif
23 let idline .= substitute(line, '"\(.*\)"$', '\1', '')
24 endwhile
25
Bram Moolenaara5792f52005-11-23 21:25:05 +000026 " remove '%', not used for formatting.
27 let idline = substitute(idline, "'%'", '', 'g')
Bram Moolenaar0b8cf272020-08-30 19:42:06 +020028 let idline = substitute(idline, "%%", '', 'g')
Bram Moolenaara5792f52005-11-23 21:25:05 +000029
Bram Moolenaar1c6136a2009-09-11 11:00:05 +000030 " remove '%' used for plural forms.
31 let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '')
32
Christ van Willegen0c6181f2023-08-13 18:03:14 +020033 " remove duplicate positional format arguments
34 let idline2 = ""
35 while idline2 != idline
36 let idline2 = idline
37 let idline = substitute(idline, '%\([1-9][0-9]*\)\$\([-+ #''.*]*[0-9]*l\=[dsuxXpoc%]\)\(.*\)%\1$\([-+ #''.*]*\)\(l\=[dsuxXpoc%]\)', '%\1$\2\3\4', 'g')
38 endwhile
39
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000040 " remove everything but % items.
Christ van Willegen0c6181f2023-08-13 18:03:14 +020041 return substitute(idline, '[^%]*\(%([1-9][0-9]*\$)\=[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000042endfunc
43
Bram Moolenaarec420592016-08-26 15:51:53 +020044" This only works when 'wrapscan' is not set.
Bram Moolenaara411e5d2010-08-04 15:47:08 +020045let s:save_wrapscan = &wrapscan
Bram Moolenaarec420592016-08-26 15:51:53 +020046set nowrapscan
Bram Moolenaara411e5d2010-08-04 15:47:08 +020047
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000048" Start at the first "msgid" line.
Bram Moolenaar7f937032017-07-19 14:34:42 +020049let wsv = winsaveview()
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000501
Bram Moolenaar8a370472022-05-22 15:28:31 +010051keeppatterns /^msgid\>
Bram Moolenaarec420592016-08-26 15:51:53 +020052
53" When an error is detected this is set to the line number.
54" Note: this is used in the Makefile.
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000055let error = 0
56
57while 1
Bram Moolenaare53ec392019-11-16 18:49:50 +010058 let lnum = line('.')
59 if getline(lnum) =~ 'msgid "Text;.*;"'
60 if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"'
61 echomsg 'Mismatching ; in line ' . (lnum + 1)
62 echomsg 'Did you forget the trailing semicolon?'
63 if error == 0
64 let error = lnum + 1
65 endif
66 endif
67 endif
68
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000069 if getline(line('.') - 1) !~ "no-c-format"
Bram Moolenaarec420592016-08-26 15:51:53 +020070 " go over the "msgid" and "msgid_plural" lines
71 let prevfromline = 'foobar'
Bram Moolenaar78ee6252023-05-28 18:39:55 +010072 let plural = 0
Bram Moolenaarec420592016-08-26 15:51:53 +020073 while 1
Bram Moolenaar78ee6252023-05-28 18:39:55 +010074 if getline('.') =~ 'msgid_plural'
75 let plural += 1
76 endif
Bram Moolenaarec420592016-08-26 15:51:53 +020077 let fromline = GetMline()
78 if prevfromline != 'foobar' && prevfromline != fromline
Bram Moolenaar78ee6252023-05-28 18:39:55 +010079 \ && (plural != 1
80 \ || count(prevfromline, '%') + 1 != count(fromline, '%'))
Bram Moolenaarec420592016-08-26 15:51:53 +020081 echomsg 'Mismatching % in line ' . (line('.') - 1)
82 echomsg 'msgid: ' . prevfromline
Bram Moolenaar78ee6252023-05-28 18:39:55 +010083 echomsg 'msgid: ' . fromline
Bram Moolenaarec420592016-08-26 15:51:53 +020084 if error == 0
85 let error = line('.')
86 endif
87 endif
88 if getline('.') !~ 'msgid_plural'
89 break
90 endif
91 let prevfromline = fromline
92 endwhile
93
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000094 if getline('.') !~ '^msgstr'
Bram Moolenaarec420592016-08-26 15:51:53 +020095 echomsg 'Missing "msgstr" in line ' . line('.')
96 if error == 0
97 let error = line('.')
98 endif
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000099 endif
Bram Moolenaarec420592016-08-26 15:51:53 +0200100
101 " check all the 'msgstr' lines
102 while getline('.') =~ '^msgstr'
103 let toline = GetMline()
104 if fromline != toline
Bram Moolenaar78ee6252023-05-28 18:39:55 +0100105 \ && (plural == 0 || count(fromline, '%') != count(toline, '%') + 1)
Bram Moolenaarec420592016-08-26 15:51:53 +0200106 echomsg 'Mismatching % in line ' . (line('.') - 1)
107 echomsg 'msgid: ' . fromline
108 echomsg 'msgstr: ' . toline
109 if error == 0
110 let error = line('.')
111 endif
112 endif
113 if line('.') == line('$')
114 break
115 endif
116 endwhile
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000117 endif
118
Bram Moolenaarec420592016-08-26 15:51:53 +0200119 " Find next msgid. Quit when there is no more.
120 let lnum = line('.')
Bram Moolenaar8a370472022-05-22 15:28:31 +0100121 silent! keeppatterns /^msgid\>
Bram Moolenaarec420592016-08-26 15:51:53 +0200122 if line('.') == lnum
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000123 break
124 endif
125endwhile
126
Bram Moolenaar595f51c2008-06-09 12:46:00 +0000127" Check that error code in msgid matches the one in msgstr.
128"
129" Examples of mismatches found with msgid "E123: ..."
130" - msgstr "E321: ..." error code mismatch
131" - msgstr "W123: ..." warning instead of error
132" - msgstr "E123 ..." missing colon
133" - msgstr "..." missing error code
134"
1351
136if search('msgid "\("\n"\)\?\([EW][0-9]\+:\).*\nmsgstr "\("\n"\)\?[^"]\@=\2\@!') > 0
Bram Moolenaarec420592016-08-26 15:51:53 +0200137 echomsg 'Mismatching error/warning code in line ' . line('.')
138 if error == 0
139 let error = line('.')
140 endif
Bram Moolenaar595f51c2008-06-09 12:46:00 +0000141endif
142
Bram Moolenaar7f937032017-07-19 14:34:42 +0200143func! CountNl(first, last)
144 let nl = 0
145 for lnum in range(a:first, a:last)
Bram Moolenaar9966b212017-07-28 16:46:57 +0200146 let nl += count(getline(lnum), "\n")
Bram Moolenaar7f937032017-07-19 14:34:42 +0200147 endfor
148 return nl
149endfunc
150
Bram Moolenaar9966b212017-07-28 16:46:57 +0200151" Check that the \n at the end of the msgid line is also present in the msgstr
Bram Moolenaar7f937032017-07-19 14:34:42 +0200152" line. Skip over the header.
Bram Moolenaarb07bbb02018-04-30 15:45:17 +02001531
Bram Moolenaar8a370472022-05-22 15:28:31 +0100154keeppatterns /^"MIME-Version:
Bram Moolenaar7f937032017-07-19 14:34:42 +0200155while 1
156 let lnum = search('^msgid\>')
157 if lnum <= 0
158 break
159 endif
160 let strlnum = search('^msgstr\>')
161 let end = search('^$')
162 if end <= 0
163 let end = line('$') + 1
164 endif
165 let origcount = CountNl(lnum, strlnum - 1)
166 let transcount = CountNl(strlnum, end - 1)
167 " Allow for a few more or less line breaks when there are 2 or more
168 if origcount != transcount && (origcount <= 2 || transcount <= 2)
Bram Moolenaar9966b212017-07-28 16:46:57 +0200169 echomsg 'Mismatching "\n" in line ' . line('.')
Bram Moolenaar7f937032017-07-19 14:34:42 +0200170 if error == 0
171 let error = lnum
172 endif
173 endif
174endwhile
175
Bram Moolenaaraaef1ba2017-08-01 17:40:23 +0200176" Check that the file is well formed according to msgfmts understanding
177if executable("msgfmt")
178 let filename = expand("%")
Bram Moolenaar01164a62017-11-02 22:58:42 +0100179 " Newer msgfmt does not take OLD_PO_FILE_INPUT argument, must be in
180 " environment.
181 let $OLD_PO_FILE_INPUT = 'yes'
182 let a = system("msgfmt --statistics " . filename)
Bram Moolenaaraaef1ba2017-08-01 17:40:23 +0200183 if v:shell_error != 0
184 let error = matchstr(a, filename.':\zs\d\+\ze:')+0
185 for line in split(a, '\n') | echomsg line | endfor
186 endif
187endif
188
Bram Moolenaar9cfc7d82018-05-13 22:37:03 +0200189" Check that the plural form is properly initialized
1901
191let plural = search('^msgid_plural ', 'n')
192if (plural && search('^"Plural-Forms: ', 'n') == 0) || (plural && search('^msgstr\[0\] ".\+"', 'n') != plural + 1)
193 if search('^"Plural-Forms: ', 'n') == 0
194 echomsg "Missing Plural header"
195 if error == 0
196 let error = search('\(^"[A-Za-z-_]\+: .*\\n"\n\)\+\zs', 'n') - 1
197 endif
198 elseif error == 0
199 let error = plural
200 endif
201elseif !plural && search('^"Plural-Forms: ', 'n')
202 " We allow for a stray plural header, msginit adds one.
203endif
204
Bram Moolenaard1d037e2018-06-24 18:04:50 +0200205" Check that 8bit encoding is used instead of 8-bit
206let cte = search('^"Content-Transfer-Encoding:\s\+8-bit', 'n')
207let ctc = search('^"Content-Type:.*;\s\+\<charset=[iI][sS][oO]_', 'n')
208let ctu = search('^"Content-Type:.*;\s\+\<charset=utf-8', 'n')
209if cte
210 echomsg "Content-Transfer-Encoding should be 8bit instead of 8-bit"
211 " TODO: make this an error
212 " if error == 0
213 " let error = cte
214 " endif
215elseif ctc
216 echomsg "Content-Type charset should be 'ISO-...' instead of 'ISO_...'"
217 " TODO: make this an error
218 " if error == 0
219 " let error = ct
220 " endif
221elseif ctu
222 echomsg "Content-Type charset should be 'UTF-8' instead of 'utf-8'"
223 " TODO: make this an error
224 " if error == 0
225 " let error = ct
226 " endif
227endif
228
Christian Brabandt32a5faa2024-05-30 09:51:47 +0200229" Check that no lines are longer than 80 chars (except comments)
230let overlong = search('^[^#]\%>80v', 'n')
Christian Brabandt6f585da2024-04-11 22:02:28 +0200231if overlong > 0
232 echomsg "Lines should be wrapped at 80 columns"
Christian Brabandtc8583822024-04-11 23:10:54 +0200233 " TODO: make this an error
234 " if error == 0
235 " let error = overlong
236 " endif
Christian Brabandt6f585da2024-04-11 22:02:28 +0200237endif
Bram Moolenaar9cfc7d82018-05-13 22:37:03 +0200238
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000239if error == 0
Bram Moolenaar7f937032017-07-19 14:34:42 +0200240 " If all was OK restore the view.
241 call winrestview(wsv)
Bram Moolenaarec420592016-08-26 15:51:53 +0200242 echomsg "OK"
243else
Bram Moolenaard1d037e2018-06-24 18:04:50 +0200244 " Put the cursor on the line with the error.
Bram Moolenaarec420592016-08-26 15:51:53 +0200245 exe error
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000246endif
247
Bram Moolenaara411e5d2010-08-04 15:47:08 +0200248let &wrapscan = s:save_wrapscan
249unlet s:save_wrapscan
250
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000251endif