vocab 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,8 +2,9 @@
2
2
  *.tmp
3
3
  .bundle
4
4
  Gemfile.lock
5
+ coverage/*
5
6
  pkg/*
6
7
  spec/tmp/*
7
8
  !spec/tmp/.vocab
8
9
  spec/data/android/versions/*.tmp
9
- tmp/last_translation/*
10
+ tmp/last_translation/*
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # 0.0.3
2
+
3
+ * Support multiple pending translations by changing .vocab in extract command
4
+ * Escape apostrophe "'" characters in android string xml files for make android resource loader not crash
data/MIT-LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Inquisitive Minds, Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Command line tool to automate the manipulation of i18n string files in Rails and Android.
4
4
 
5
- Features include:
5
+ ## Features include:
6
6
 
7
7
  * Generate a list all English translations that have been updated or added since the last translation
8
8
  * Integrate a list of newly translated strings into the right place in the project translation file(s)
@@ -11,7 +11,7 @@ Features include:
11
11
  # Requirements
12
12
 
13
13
  * Must be used in context of git controlled project
14
- * Assumes that android string files are called strings.xml
14
+ * Assumes that android string files are called strings.xml under res/values-xx
15
15
 
16
16
  # Installation
17
17
 
@@ -23,36 +23,88 @@ Set the current git commit to be the starting point for the next translation dif
23
23
 
24
24
  vocab init
25
25
 
26
+ You may need to change the SHA hash in .vocab to match the commit of the last translation
27
+
26
28
  # Usage
27
29
 
28
- ## Extracting Changed and Updated Strings For Translation
30
+ ## Android
31
+
32
+ ### Extract Changed and Updated Strings For Translation
29
33
 
34
+ cd APP_ROOT
30
35
  vocab extract android
31
36
 
32
- or
37
+ This will create two files in the current directory:
33
38
 
34
- vocab extract rails
39
+ `strings.diff.xml` Contains all the keys that need to be updated for existing languages
40
+ `strings.full.xml` Contains all keys for a full translation
35
41
 
36
- Note: keys starting with 'debug_' are ignored. Prepend development-only strings
37
- with 'debug_' to avoid these keys being sent to translators.
42
+ Send these files off to the appropriate translator. When they files come back,
43
+ put the partial language translations in `tmp/translations`. For example:
38
44
 
39
- ## Merging new translations into project string files
45
+ tmp/translations/values-zn/strings.xml
46
+ tmp/translations/values-es/es-strings-7.xml
40
47
 
41
- vocab merge android
48
+ The file must be in the properly named folder and end with xml.
42
49
 
43
- or
50
+ For new language files, just put them directly under the `res` directory.
44
51
 
45
- vocab merge rails
52
+ ### Merging new translations into project string files
46
53
 
47
- ## Merging new translations into project string files
54
+ Integrate the translations with the following command:
48
55
 
56
+ cd APP_ROOT
57
+ vocab merge android
58
+
59
+ ### Validating that all translations have the same keys
60
+
61
+ cd APP_ROOT
49
62
  vocab validate android
50
63
 
51
- or
64
+ ## Rails
65
+
66
+ ### Extract Changed and Updated Strings For Translation
67
+
68
+ cd RAILS_ROOT
69
+ vocab extract rails
70
+
71
+ This will create two files in the current directory:
52
72
 
73
+ `en.diff.yml` Contains all the keys that need to be updated for existing languages
74
+ `en.full.yml` Contains all keys for a full translation
75
+
76
+ Send these files off to the appropriate translator. When they files come back,
77
+ put them in `tmp/translations`. For example:
78
+
79
+ tmp/translations/zn.yml
80
+ tmp/translations/es.yml
81
+
82
+ ### Merging new translations into project string files
83
+
84
+ Integrate the translations with the following command:
85
+
86
+ cd RAILS_ROOT
87
+ vocab merge rails
88
+
89
+ Keys will be put in the correct nested yml file. You can put whole or partial translations
90
+ in tmp/translations.
91
+
92
+ ### Validating that all translations have the same keys
93
+
94
+ cd RAILS_ROOT
53
95
  vocab validate rails
54
96
 
97
+ # Other details
98
+
99
+ Keys starting with 'debug_' are ignored for both Rails and Android string files. Prepend development-only strings
100
+ with 'debug_' to avoid these keys being sent to translators.
101
+
55
102
  # TODO
56
103
 
57
104
  * Add .processed to each tmp/translation after success
58
- * Accept filenames other than strings.xml under tmp/translation/values-xx/yyyy.xml
105
+ * Handle full translations under tmp/translations when no existing translation exists in android
106
+ * Add iOS support
107
+ * Add AIR support
108
+ * Add Chrome support
109
+ * Add Firefox support
110
+
data/bin/vocab CHANGED
@@ -8,4 +8,4 @@ end
8
8
 
9
9
  require 'vocab'
10
10
 
11
- Vocab::Application.run
11
+ exit Vocab::Application.run
@@ -3,10 +3,10 @@ require 'ostruct'
3
3
 
4
4
  module Vocab
5
5
  class Application
6
-
6
+
7
7
  class << self
8
8
  def run
9
- handle_command
9
+ return handle_command
10
10
  end
11
11
 
12
12
  ##############################
@@ -14,6 +14,7 @@ module Vocab
14
14
  ##############################
15
15
 
16
16
  def handle_command
17
+ success = true
17
18
  options = OpenStruct.new
18
19
  parser = OptionParser.new
19
20
 
@@ -45,14 +46,15 @@ module Vocab
45
46
  elsif( options.command == 'merge' && options.platform == 'android' )
46
47
  Merger::Android.new.merge
47
48
  elsif( options.command == 'validate' && options.platform == 'android' )
48
- Validator::Android.new.validate
49
+ success = Validator::Android.new.validate
49
50
  elsif( options.command == 'validate' && options.platform == 'rails' )
50
- Validator::Rails.new.validate
51
+ success = Validator::Rails.new.validate
51
52
  else
52
53
  puts parser.help
53
54
  end
55
+
56
+ return success
54
57
  end
55
-
56
58
  end
57
59
  end
58
60
  end
@@ -6,30 +6,38 @@ module Vocab
6
6
  DIFF = 'strings.diff.xml'
7
7
  FULL = 'strings.full.xml'
8
8
 
9
- def extract_current( path = nil )
10
- path ||= "#{Vocab.root}/res/values/strings.xml"
9
+ STRINGS_XML = 'res/values/strings.xml'
10
+
11
+ def current_strings( path = nil )
12
+ path ||= "#{Vocab.root}/#{STRINGS_XML}"
11
13
  return Vocab::Translator::Android.hash_from_xml( path )
12
14
  end
13
15
 
14
- def extract_previous( path = nil )
15
- path ||= "res/values/strings.xml"
16
- sha = Vocab.settings.last_translation
17
- xml = previous_file( path, sha )
18
-
19
- tmpfile = "#{Vocab.root}/tmp/last_translation/#{File.basename(path)}"
20
- FileUtils.mkdir_p( File.dirname( tmpfile ) )
21
- File.open( tmpfile, 'w' ) { |f| f.write( xml ) }
16
+ def previous_strings( path = nil )
17
+ path ||= STRINGS_XML
18
+ tmpfile = tmp_file( path )
22
19
  return Vocab::Translator::Android.hash_from_xml( tmpfile )
23
20
  end
24
21
 
25
- def write_diff( diff, path = nil )
22
+ def current_plurals( path = nil )
23
+ path ||= "#{Vocab.root}/#{STRINGS_XML}"
24
+ return Vocab::Translator::Android.plurals_from_xml( path )
25
+ end
26
+
27
+ def previous_plurals( path = nil )
28
+ path ||= STRINGS_XML
29
+ tmpfile = tmp_file( path )
30
+ return Vocab::Translator::Android.plurals_from_xml( tmpfile )
31
+ end
32
+
33
+ def write_diff( strings, plurals, path = nil )
26
34
  path ||= "#{Vocab.root}/#{DIFF}"
27
- Vocab::Translator::Android.write( diff, path )
35
+ Vocab::Translator::Android.write( strings, plurals, path )
28
36
  end
29
37
 
30
- def write_full( diff, path = nil )
38
+ def write_full( strings, plurals, path = nil )
31
39
  path ||= "#{Vocab.root}/#{FULL}"
32
- Vocab::Translator::Android.write( diff, path )
40
+ Vocab::Translator::Android.write( strings, plurals, path )
33
41
  end
34
42
 
35
43
  def examples( locales_dir = nil )
@@ -50,6 +58,15 @@ tmp/translations/values-zh-rCN/strings.xml
50
58
  super( values )
51
59
  end
52
60
 
61
+ def tmp_file( path )
62
+ sha = Vocab.settings.last_translation
63
+ xml = previous_file( path, sha )
64
+ tmpfile = "#{Vocab.root}/tmp/last_translation/#{File.basename(path)}"
65
+ FileUtils.mkdir_p( File.dirname( tmpfile ) )
66
+ File.open( tmpfile, 'w' ) { |f| f.write( xml ) }
67
+ return tmpfile
68
+ end
69
+
53
70
  end
54
71
  end
55
72
  end
@@ -3,11 +3,14 @@ module Vocab
3
3
  class Base
4
4
  class << self
5
5
  def extract( diff_path = nil, full_path = nil )
6
- current = extract_current
7
- previous = extract_previous
8
- diff = diff( previous, current )
9
- write_diff( diff, diff_path )
10
- write_full( current, full_path )
6
+ all_strings = current_strings
7
+ all_plurals = current_plurals
8
+
9
+ updated_strings = diff( previous_strings, all_strings )
10
+ updated_plurals = diff( previous_plurals, all_plurals )
11
+ write_diff( updated_strings, updated_plurals, diff_path )
12
+ write_full( all_strings, all_plurals, full_path )
13
+ update_settings
11
14
  mkdir_examples
12
15
  print_instructions
13
16
  end
@@ -26,19 +29,32 @@ module Vocab
26
29
  return diff
27
30
  end
28
31
 
29
- def extract_previous
30
- raise "extract_previous not implemented"
32
+ def update_settings
33
+ sha = Vocab.settings.update_translation
34
+ Vocab.ui.say( "Updated current translation to #{sha}" )
31
35
  end
32
36
 
33
- def extract_current
34
- raise "extract_current not implemented"
37
+ def previous_strings
38
+ raise "previous_strings not implemented"
35
39
  end
36
40
 
37
- def write_diff( diff, path )
41
+ def current_strings
42
+ raise "current_strings not implemented"
43
+ end
44
+
45
+ def current_plurals
46
+ raise "current_plurals not implemented"
47
+ end
48
+
49
+ def previous_plurals
50
+ raise "previous_plurals not implemented"
51
+ end
52
+
53
+ def write_diff( diff, plurals, path )
38
54
  raise "write_diff not implemented"
39
55
  end
40
56
 
41
- def write_full( diff, path )
57
+ def write_full( diff, plurals, path )
42
58
  raise "write_full not implemented"
43
59
  end
44
60
 
@@ -5,14 +5,14 @@ module Vocab
5
5
  FULL = 'en.full.yml'
6
6
 
7
7
  class << self
8
- def write_diff( diff, path )
8
+ def write_diff( strings, plurals, path )
9
9
  path ||= "#{Vocab.root}/#{DIFF}"
10
- write( diff, path )
10
+ write( strings, path )
11
11
  end
12
12
 
13
- def write_full( full, path )
13
+ def write_full( strings, plurals, path )
14
14
  path ||= "#{Vocab.root}/#{FULL}"
15
- write( full, path )
15
+ write( strings, path )
16
16
  end
17
17
 
18
18
  def write( translations, path )
@@ -21,7 +21,7 @@ module Vocab
21
21
  Vocab.ui.say( "Extracted to #{path}" )
22
22
  end
23
23
 
24
- def extract_previous( locales_root = nil )
24
+ def previous_strings( locales_root = nil )
25
25
  locales_root ||= "config/locales"
26
26
  tmpdir = "#{Vocab.root}/tmp/last_translation"
27
27
  FileUtils.rm_rf( "#{tmpdir}/*" )
@@ -41,11 +41,21 @@ module Vocab
41
41
  return translations( tmpdir )
42
42
  end
43
43
 
44
- def extract_current( locales_root = nil )
44
+ def current_strings( locales_root = nil )
45
45
  locales_root ||= "#{Vocab.root}/config/locales"
46
46
  return translations( locales_root )
47
47
  end
48
48
 
49
+ # Treat this as a no-op because plurals handled like normal strings
50
+ def previous_plurals
51
+ return {}
52
+ end
53
+
54
+ # Treat this as a no-op because plurals handled like normal strings
55
+ def current_plurals
56
+ return {}
57
+ end
58
+
49
59
  def translations( dir )
50
60
  translator = Vocab::Translator::Rails.new
51
61
  translator.load_dir( dir )
@@ -8,10 +8,26 @@ module Vocab
8
8
  end
9
9
 
10
10
  def merge_file( path )
11
- keys = english_keys
12
- current = current_for_locale( path )
13
- updates = updates_for_locale( path )
11
+ strings = strings( path )
12
+ plurals = plurals( path )
13
+ Vocab::Translator::Android.write( strings, plurals, path )
14
+ end
15
+
16
+ def strings( path )
17
+ keys = string_keys
18
+ current = current_strings_for_locale( path )
19
+ updates = update_strings_for_locale( path )
20
+ return translation_hash( keys, current, updates, path )
21
+ end
14
22
 
23
+ def plurals( path )
24
+ keys = plural_keys
25
+ current = current_plurals_for_locale( path )
26
+ updates = update_plurals_for_locale( path )
27
+ return translation_hash( keys, current, updates, path )
28
+ end
29
+
30
+ def translation_hash( keys, current, updates, path )
15
31
  translation = {}
16
32
  keys.each do |key|
17
33
  next if Vocab::Translator::Base.ignore_key?( key )
@@ -24,24 +40,33 @@ module Vocab
24
40
  end
25
41
  end
26
42
 
27
- Vocab::Translator::Android.write( translation, path )
43
+ return translation
28
44
  end
29
45
 
30
- def english_keys
31
- return Vocab::Translator::Android.english_keys( @locales_dir )
46
+ def string_keys
47
+ return Vocab::Translator::Android.string_keys( @locales_dir )
32
48
  end
33
49
 
34
- def current_for_locale( path )
35
- Vocab::Translator::Android.hash_from_xml( path )
50
+ def plural_keys
51
+ return Vocab::Translator::Android.plural_keys( @locales_dir )
36
52
  end
37
53
 
38
- def updates_for_locale( path )
39
- name = path.gsub( "#{@locales_dir}/", '' )
40
- dirname = File.dirname( name )
41
- entries = Dir.glob( "#{updates_dir}/#{dirname}/*.xml" )
42
- Vocab.ui.warn( "Multiple update files for #{path}: #{entries.join( ',' )}" ) if entries.size > 1
43
- update = entries.first
44
- Vocab::Translator::Android.hash_from_xml( update )
54
+ def current_strings_for_locale( path )
55
+ return Vocab::Translator::Android.hash_from_xml( path )
56
+ end
57
+
58
+ def update_strings_for_locale( path )
59
+ update = update_file_path( path )
60
+ return Vocab::Translator::Android.hash_from_xml( update )
61
+ end
62
+
63
+ def current_plurals_for_locale( path )
64
+ return Vocab::Translator::Android.plurals_from_xml( path )
65
+ end
66
+
67
+ def update_plurals_for_locale( path )
68
+ update = update_file_path( path )
69
+ return Vocab::Translator::Android.plurals_from_xml( update )
45
70
  end
46
71
 
47
72
  def translation_locales
@@ -54,6 +79,14 @@ module Vocab
54
79
  end
55
80
  end
56
81
 
82
+ def update_file_path( path )
83
+ name = path.gsub( "#{@locales_dir}/", '' )
84
+ dirname = File.dirname( name )
85
+ entries = Dir.glob( "#{updates_dir}/#{dirname}/*.xml" )
86
+ Vocab.ui.warn( "Multiple update files for #{path}: #{entries.join( ',' )}" ) if entries.size > 1
87
+ return entries.first
88
+ end
89
+
57
90
  end
58
91
  end
59
92
  end
@@ -4,17 +4,11 @@ module Vocab
4
4
 
5
5
  attr_accessor :locales_dir, :updates_dir
6
6
 
7
- def update_settings
8
- sha = Vocab.settings.update_translation
9
- Vocab.ui.say( "Updated current translation to #{sha}" )
10
- end
11
-
12
7
  def merge
13
8
  files_to_merge.each do |file|
14
9
  Vocab.ui.say( "Merging file: #{file}" )
15
10
  merge_file( file )
16
11
  end
17
- update_settings
18
12
  end
19
13
  end
20
14
  end
@@ -3,29 +3,77 @@ module Vocab
3
3
  class Android < Base
4
4
 
5
5
  def self.hash_from_xml( path )
6
- xml = File.open( path ) { |f| f.read }
7
- doc = Nokogiri::HTML.fragment( xml ) { |config| config.noblanks }
6
+ doc = doc_from_xml( path )
8
7
  children = doc.search( 'resources/string' )
9
8
  hash = {}
10
- children.each { |child| hash[ child['name'] ] = child.text }
9
+ children.each do |child|
10
+ text = child.text
11
+ # escape apostrophes for android resource loader
12
+ text = text.gsub( /([^\\])'/, %q[\1\\\'] )
13
+ hash[ child['name'] ] = text
14
+ end
11
15
  return hash
12
16
  end
13
17
 
14
- def self.write( hash, path )
18
+ # Extracts plural definitions from xml to a hash
19
+ #
20
+ # For example:
21
+ #
22
+ # <plurals name="user_count">
23
+ # <item quantity="one">1 user</item>
24
+ # <item quantity="many">%d users</item>
25
+ # </plurals>
26
+ #
27
+ # Is returned as:
28
+ #
29
+ # { "user_count" => { "one" => "1 user",
30
+ # "many" => "%d users" } }
31
+ def self.plurals_from_xml( path )
32
+ doc = doc_from_xml( path )
33
+
34
+ plurals = {}
35
+ doc.search( 'resources/plurals' ).each do |plural|
36
+ items = {}
37
+ plural.search( 'item' ).each do |item|
38
+ items[ item['quantity' ] ] = item.text
39
+ end
40
+
41
+ plurals[ plural['name'] ] = items
42
+ end
43
+ return plurals
44
+ end
45
+
46
+ def self.write( strings, plurals, path )
15
47
  builder = Nokogiri::XML::Builder.new do |xml|
16
- xml.resources {
17
- hash.sort.each do |key, value|
48
+ xml.resources do
49
+ strings.sort.each do |key, value|
18
50
  xml.string( value, :name => key )
19
51
  end
20
- }
52
+
53
+ plurals.keys.sort.each do |key|
54
+ xml.plurals( :name => key ) do
55
+ plurals[ key ].each do |quantity, value|
56
+ xml.item( value, :quantity => quantity )
57
+ end
58
+ end
59
+ end
60
+ end
21
61
  end
22
62
  File.open( path, 'w' ) { |f| f.write( builder.to_xml( :encoding => 'UTF-8' ) ) }
23
63
  end
24
64
 
25
- def self.english_keys( locales_dir )
65
+ def self.string_keys( locales_dir )
26
66
  path = "#{locales_dir}/values/strings.xml"
27
67
  translations = Vocab::Translator::Android.hash_from_xml( path )
28
- return translations.keys
68
+ keys = translations.keys.map { |key| Vocab::Translator::Base.ignore_key?( key ) ? nil : key }.compact
69
+ return keys
70
+ end
71
+
72
+ def self.plural_keys( locales_dir )
73
+ path = "#{locales_dir}/values/strings.xml"
74
+ translations = Vocab::Translator::Android.plurals_from_xml( path )
75
+ keys = translations.keys.map { |key| Vocab::Translator::Base.ignore_key?( key ) ? nil : key }.compact
76
+ return keys
29
77
  end
30
78
 
31
79
  def self.locales( dir, strict = true )
@@ -38,6 +86,16 @@ module Vocab
38
86
  return locales
39
87
  end
40
88
 
89
+ #########################################################################
90
+ # Helper methods
91
+ #########################################################################
92
+
93
+ def self.doc_from_xml( path )
94
+ xml = File.open( path ) { |f| f.read }
95
+ doc = Nokogiri::HTML.fragment( xml ) { |config| config.noblanks }
96
+ return doc
97
+ end
98
+
41
99
  end
42
100
  end
43
101
  end
@@ -10,8 +10,8 @@ module Vocab
10
10
  return Vocab::Translator::Android.hash_from_xml( path ).keys
11
11
  end
12
12
 
13
- def english_keys( path = nil )
14
- return Vocab::Translator::Android.english_keys( @locales_dir )
13
+ def string_keys( path = nil )
14
+ return Vocab::Translator::Android.string_keys( @locales_dir )
15
15
  end
16
16
 
17
17
  def files_to_validate
@@ -2,13 +2,19 @@ module Vocab
2
2
  module Validator
3
3
  class Base
4
4
 
5
+ # Returns false if validation fails
5
6
  def validate
7
+ ok = true
8
+
6
9
  files = files_to_validate
7
10
  Vocab.ui.say( "#{files.size} file(s) to validate in #{@locales_dir}" )
8
11
  files.each do |path|
9
12
  validation = validate_file( path )
10
13
  print( path, validation )
14
+ ok &&= validation[ :missing ] && validation[ :missing ].empty?
11
15
  end
16
+
17
+ return ok
12
18
  end
13
19
 
14
20
  def print( path, validation )
@@ -27,7 +33,7 @@ module Vocab
27
33
 
28
34
  def validate_file( path )
29
35
  other = other_keys( path )
30
- english = english_keys( path )
36
+ english = string_keys( path )
31
37
 
32
38
  result = {}
33
39
  result[ :missing ] = ( english - other ).sort
@@ -10,7 +10,7 @@ module Vocab
10
10
  return flattened_keys( path )
11
11
  end
12
12
 
13
- def english_keys( path )
13
+ def string_keys( path )
14
14
  return flattened_keys( Vocab::Translator::Rails.en_equivalent_path( path ) )
15
15
  end
16
16
 
data/lib/vocab/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Vocab
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -7,4 +7,15 @@
7
7
  <string name="cancel">Cancel</string>
8
8
  <string name="delete">Delete</string>
9
9
  <string name="not_in_es">This key not in spanish</string>
10
+ <string name="debug_key">Should be ignored</string>
11
+
12
+ <plurals name="user_count">
13
+ <item quantity="one">1 user</item>
14
+ <item quantity="many">%d users</item>
15
+ </plurals>
16
+
17
+ <plurals name="fish_count">
18
+ <item quantity="one">1 fish</item>
19
+ <item quantity="many">%d fish</item>
20
+ </plurals>
10
21
  </resources>
@@ -3,4 +3,9 @@
3
3
  <string name="app_name">Modo Niños</string>
4
4
  <string name="pd_app_name">el Panel para padres</string>
5
5
  <string name="app_current">actual</string>
6
+
7
+ <plurals name="fish_count">
8
+ <item quantity="one">1 pescado</item>
9
+ <item quantity="many">%d peces</item>
10
+ </plurals>
6
11
  </resources>