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