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