2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Verifies that GRD resource files define all the strings used by a given
7 set of source files. For file formats where it is not possible to infer which
8 strings represent message identifiers, localized strings should be explicitly
9 annotated with the string "i18n-content", for example:
11 LocalizeString(/*i18n-content*/"PRODUCT_NAME");
13 This script also recognises localized strings in HTML and manifest.json files:
15 HTML: <span i18n-content="PRODUCT_NAME"></span>
16 or ...i18n-value-name-1="BUTTON_NAME"...
17 manifest.json: __MSG_PRODUCT_NAME__
19 Note that these forms must be exact; extra spaces are not permitted, though
20 either single or double quotes are recognized.
22 In addition, the script checks that all the messages are still in use; if
23 this is not the case then a warning is issued, but the script still succeeds.
31 import xml
.dom
.minidom
as minidom
34 To remove this warning, either remove the unused tags from
35 resource files, add the files that use the tags listed above to
36 remoting.gyp, or annotate existing uses of those tags with the
37 prefix /*i18n-content*/
40 def LoadTagsFromGrd(filename
):
41 xml
= minidom
.parse(filename
)
43 msgs_and_structs
= xml
.getElementsByTagName("message")
44 msgs_and_structs
.extend(xml
.getElementsByTagName("structure"))
45 for res
in msgs_and_structs
:
46 name
= res
.getAttribute("name")
47 if not name
or not name
.startswith("IDR_"):
48 raise Exception("Tag name doesn't start with IDR_: %s" % name
)
52 def ExtractTagFromLine(file_type
, line
):
53 """Extract a tag from a line of HTML, C++, JS or JSON."""
54 if file_type
== "html":
56 m
= re
.search('i18n-content=[\'"]([^\'"]*)[\'"]', line
)
57 if m
: return m
.group(1)
58 # HTML-style (substitutions)
59 m
= re
.search('i18n-value-name-[1-9]=[\'"]([^\'"]*)[\'"]', line
)
60 if m
: return m
.group(1)
61 elif file_type
== 'js':
63 m
= re
.search('/\*i18n-content\*/[\'"]([^\`"]*)[\'"]', line
)
64 if m
: return m
.group(1)
65 elif file_type
== 'cc' or file_type
== 'mm':
67 m
= re
.search('IDR_([A-Z0-9_]*)', line
)
68 if m
: return m
.group(1)
69 m
= re
.search('/\*i18n-content\*/["]([^\`"]*)["]', line
)
70 if m
: return m
.group(1)
71 elif file_type
== 'json':
73 m
= re
.search('__MSG_(.*)__', line
)
74 if m
: return m
.group(1)
75 elif file_type
== 'jinja2':
76 # Jinja2 template file
77 m
= re
.search('\{\%\s+trans\s+\%\}([A-Z0-9_]+)\{\%\s+endtrans\s+\%\}', line
)
78 if m
: return m
.group(1)
82 def VerifyFile(filename
, messages
, used_tags
):
84 Parse |filename|, looking for tags and report any that are not included in
85 |messages|. Return True if all tags are present and correct, or False if
86 any are missing. If no tags are found, print a warning message and return
90 base_name
, extension
= os
.path
.splitext(filename
)
91 extension
= extension
[1:]
92 if extension
not in ['js', 'cc', 'html', 'json', 'jinja2', 'mm']:
93 raise Exception("Unknown file type: %s" % extension
)
97 f
= open(filename
, 'r')
99 for i
in xrange(0, len(lines
)):
100 tag
= ExtractTagFromLine(extension
, lines
[i
])
105 if not tag
in messages
:
107 print '%s/%s:%d: error: Undefined tag: %s' % \
108 (os
.getcwd(), filename
, i
+ 1, tag
)
110 print '%s/%s:0: warning: No tags found' % (os
.getcwd(), filename
)
116 parser
= optparse
.OptionParser(
117 usage
='Usage: %prog [options...] [source_file...]')
118 parser
.add_option('-t', '--touch', dest
='touch',
119 help='File to touch when finished.')
120 parser
.add_option('-r', '--grd', dest
='grd', action
='append',
123 options
, args
= parser
.parse_args()
124 if not options
.touch
:
125 print '-t is not specified.'
127 if len(options
.grd
) == 0 or len(args
) == 0:
128 print 'At least one GRD file needs to be specified.'
132 for f
in options
.grd
:
133 resources
.extend(LoadTagsFromGrd(f
))
138 if not VerifyFile(f
, resources
, used_tags
):
142 for tag
in resources
:
143 if tag
not in used_tags
:
144 print ('%s/%s:0: warning: %s is defined but not used') % \
145 (os
.getcwd(), sys
.argv
[2], tag
)
148 print WARNING_MESSAGE
151 f
= open(options
.touch
, 'a')
153 os
.utime(options
.touch
, None)
158 if __name__
== '__main__':