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