blob: b2a5fd582eb7fd7b0baf404abb4d6d9a13ae4fe0 [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
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000033 " remove everything but % items.
34 return substitute(idline, '[^%]*\(%[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
35endfunc
36
Bram Moolenaarec420592016-08-26 15:51:53 +020037" This only works when 'wrapscan' is not set.
Bram Moolenaara411e5d2010-08-04 15:47:08 +020038let s:save_wrapscan = &wrapscan
Bram Moolenaarec420592016-08-26 15:51:53 +020039set nowrapscan
Bram Moolenaara411e5d2010-08-04 15:47:08 +020040
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000041" Start at the first "msgid" line.
Bram Moolenaar7f937032017-07-19 14:34:42 +020042let wsv = winsaveview()
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000431
Bram Moolenaar8a370472022-05-22 15:28:31 +010044keeppatterns /^msgid\>
Bram Moolenaarec420592016-08-26 15:51:53 +020045
46" When an error is detected this is set to the line number.
47" Note: this is used in the Makefile.
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000048let error = 0
49
50while 1
Bram Moolenaare53ec392019-11-16 18:49:50 +010051 let lnum = line('.')
52 if getline(lnum) =~ 'msgid "Text;.*;"'
53 if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"'
54 echomsg 'Mismatching ; in line ' . (lnum + 1)
55 echomsg 'Did you forget the trailing semicolon?'
56 if error == 0
57 let error = lnum + 1
58 endif
59 endif
60 endif
61
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000062 if getline(line('.') - 1) !~ "no-c-format"
Bram Moolenaarec420592016-08-26 15:51:53 +020063 " go over the "msgid" and "msgid_plural" lines
64 let prevfromline = 'foobar'
Bram Moolenaar78ee6252023-05-28 18:39:55 +010065 let plural = 0
Bram Moolenaarec420592016-08-26 15:51:53 +020066 while 1
Bram Moolenaar78ee6252023-05-28 18:39:55 +010067 if getline('.') =~ 'msgid_plural'
68 let plural += 1
69 endif
Bram Moolenaarec420592016-08-26 15:51:53 +020070 let fromline = GetMline()
71 if prevfromline != 'foobar' && prevfromline != fromline
Bram Moolenaar78ee6252023-05-28 18:39:55 +010072 \ && (plural != 1
73 \ || count(prevfromline, '%') + 1 != count(fromline, '%'))
Bram Moolenaarec420592016-08-26 15:51:53 +020074 echomsg 'Mismatching % in line ' . (line('.') - 1)
75 echomsg 'msgid: ' . prevfromline
Bram Moolenaar78ee6252023-05-28 18:39:55 +010076 echomsg 'msgid: ' . fromline
Bram Moolenaarec420592016-08-26 15:51:53 +020077 if error == 0
78 let error = line('.')
79 endif
80 endif
81 if getline('.') !~ 'msgid_plural'
82 break
83 endif
84 let prevfromline = fromline
85 endwhile
86
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000087 if getline('.') !~ '^msgstr'
Bram Moolenaarec420592016-08-26 15:51:53 +020088 echomsg 'Missing "msgstr" in line ' . line('.')
89 if error == 0
90 let error = line('.')
91 endif
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +000092 endif
Bram Moolenaarec420592016-08-26 15:51:53 +020093
94 " check all the 'msgstr' lines
95 while getline('.') =~ '^msgstr'
96 let toline = GetMline()
97 if fromline != toline
Bram Moolenaar78ee6252023-05-28 18:39:55 +010098 \ && (plural == 0 || count(fromline, '%') != count(toline, '%') + 1)
Bram Moolenaarec420592016-08-26 15:51:53 +020099 echomsg 'Mismatching % in line ' . (line('.') - 1)
100 echomsg 'msgid: ' . fromline
101 echomsg 'msgstr: ' . toline
102 if error == 0
103 let error = line('.')
104 endif
105 endif
106 if line('.') == line('$')
107 break
108 endif
109 endwhile
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000110 endif
111
Bram Moolenaarec420592016-08-26 15:51:53 +0200112 " Find next msgid. Quit when there is no more.
113 let lnum = line('.')
Bram Moolenaar8a370472022-05-22 15:28:31 +0100114 silent! keeppatterns /^msgid\>
Bram Moolenaarec420592016-08-26 15:51:53 +0200115 if line('.') == lnum
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000116 break
117 endif
118endwhile
119
Bram Moolenaar595f51c2008-06-09 12:46:00 +0000120" Check that error code in msgid matches the one in msgstr.
121"
122" Examples of mismatches found with msgid "E123: ..."
123" - msgstr "E321: ..." error code mismatch
124" - msgstr "W123: ..." warning instead of error
125" - msgstr "E123 ..." missing colon
126" - msgstr "..." missing error code
127"
1281
129if search('msgid "\("\n"\)\?\([EW][0-9]\+:\).*\nmsgstr "\("\n"\)\?[^"]\@=\2\@!') > 0
Bram Moolenaarec420592016-08-26 15:51:53 +0200130 echomsg 'Mismatching error/warning code in line ' . line('.')
131 if error == 0
132 let error = line('.')
133 endif
Bram Moolenaar595f51c2008-06-09 12:46:00 +0000134endif
135
Bram Moolenaar7f937032017-07-19 14:34:42 +0200136func! CountNl(first, last)
137 let nl = 0
138 for lnum in range(a:first, a:last)
Bram Moolenaar9966b212017-07-28 16:46:57 +0200139 let nl += count(getline(lnum), "\n")
Bram Moolenaar7f937032017-07-19 14:34:42 +0200140 endfor
141 return nl
142endfunc
143
Bram Moolenaar9966b212017-07-28 16:46:57 +0200144" Check that the \n at the end of the msgid line is also present in the msgstr
Bram Moolenaar7f937032017-07-19 14:34:42 +0200145" line. Skip over the header.
Bram Moolenaarb07bbb02018-04-30 15:45:17 +02001461
Bram Moolenaar8a370472022-05-22 15:28:31 +0100147keeppatterns /^"MIME-Version:
Bram Moolenaar7f937032017-07-19 14:34:42 +0200148while 1
149 let lnum = search('^msgid\>')
150 if lnum <= 0
151 break
152 endif
153 let strlnum = search('^msgstr\>')
154 let end = search('^$')
155 if end <= 0
156 let end = line('$') + 1
157 endif
158 let origcount = CountNl(lnum, strlnum - 1)
159 let transcount = CountNl(strlnum, end - 1)
160 " Allow for a few more or less line breaks when there are 2 or more
161 if origcount != transcount && (origcount <= 2 || transcount <= 2)
Bram Moolenaar9966b212017-07-28 16:46:57 +0200162 echomsg 'Mismatching "\n" in line ' . line('.')
Bram Moolenaar7f937032017-07-19 14:34:42 +0200163 if error == 0
164 let error = lnum
165 endif
166 endif
167endwhile
168
Bram Moolenaaraaef1ba2017-08-01 17:40:23 +0200169" Check that the file is well formed according to msgfmts understanding
170if executable("msgfmt")
171 let filename = expand("%")
Bram Moolenaar01164a62017-11-02 22:58:42 +0100172 " Newer msgfmt does not take OLD_PO_FILE_INPUT argument, must be in
173 " environment.
174 let $OLD_PO_FILE_INPUT = 'yes'
175 let a = system("msgfmt --statistics " . filename)
Bram Moolenaaraaef1ba2017-08-01 17:40:23 +0200176 if v:shell_error != 0
177 let error = matchstr(a, filename.':\zs\d\+\ze:')+0
178 for line in split(a, '\n') | echomsg line | endfor
179 endif
180endif
181
Bram Moolenaar9cfc7d82018-05-13 22:37:03 +0200182" Check that the plural form is properly initialized
1831
184let plural = search('^msgid_plural ', 'n')
185if (plural && search('^"Plural-Forms: ', 'n') == 0) || (plural && search('^msgstr\[0\] ".\+"', 'n') != plural + 1)
186 if search('^"Plural-Forms: ', 'n') == 0
187 echomsg "Missing Plural header"
188 if error == 0
189 let error = search('\(^"[A-Za-z-_]\+: .*\\n"\n\)\+\zs', 'n') - 1
190 endif
191 elseif error == 0
192 let error = plural
193 endif
194elseif !plural && search('^"Plural-Forms: ', 'n')
195 " We allow for a stray plural header, msginit adds one.
196endif
197
Bram Moolenaard1d037e2018-06-24 18:04:50 +0200198" Check that 8bit encoding is used instead of 8-bit
199let cte = search('^"Content-Transfer-Encoding:\s\+8-bit', 'n')
200let ctc = search('^"Content-Type:.*;\s\+\<charset=[iI][sS][oO]_', 'n')
201let ctu = search('^"Content-Type:.*;\s\+\<charset=utf-8', 'n')
202if cte
203 echomsg "Content-Transfer-Encoding should be 8bit instead of 8-bit"
204 " TODO: make this an error
205 " if error == 0
206 " let error = cte
207 " endif
208elseif ctc
209 echomsg "Content-Type charset should be 'ISO-...' instead of 'ISO_...'"
210 " TODO: make this an error
211 " if error == 0
212 " let error = ct
213 " endif
214elseif ctu
215 echomsg "Content-Type charset should be 'UTF-8' instead of 'utf-8'"
216 " TODO: make this an error
217 " if error == 0
218 " let error = ct
219 " endif
220endif
221
Bram Moolenaar9cfc7d82018-05-13 22:37:03 +0200222
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000223if error == 0
Bram Moolenaar7f937032017-07-19 14:34:42 +0200224 " If all was OK restore the view.
225 call winrestview(wsv)
Bram Moolenaarec420592016-08-26 15:51:53 +0200226 echomsg "OK"
227else
Bram Moolenaard1d037e2018-06-24 18:04:50 +0200228 " Put the cursor on the line with the error.
Bram Moolenaarec420592016-08-26 15:51:53 +0200229 exe error
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000230endif
231
Bram Moolenaara411e5d2010-08-04 15:47:08 +0200232let &wrapscan = s:save_wrapscan
233unlet s:save_wrapscan
234
Bram Moolenaar1d94f9b2005-08-04 21:29:45 +0000235endif