i18n-processes 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile.lock +102 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +46 -0
  5. data/Rakefile +12 -0
  6. data/bin/i18n-processes +28 -0
  7. data/bin/i18n-processes.cmd +2 -0
  8. data/config/locales/en.yml +2 -0
  9. data/config/locales/zh-CN.yml +2 -0
  10. data/i18n-processes.gemspec +64 -0
  11. data/lib/i18n/processes/base_process.rb +47 -0
  12. data/lib/i18n/processes/cli.rb +208 -0
  13. data/lib/i18n/processes/command/collection.rb +21 -0
  14. data/lib/i18n/processes/command/commander.rb +43 -0
  15. data/lib/i18n/processes/command/commands/data.rb +107 -0
  16. data/lib/i18n/processes/command/commands/eq_base.rb +21 -0
  17. data/lib/i18n/processes/command/commands/health.rb +26 -0
  18. data/lib/i18n/processes/command/commands/meta.rb +38 -0
  19. data/lib/i18n/processes/command/commands/missing.rb +86 -0
  20. data/lib/i18n/processes/command/commands/preprocessing.rb +90 -0
  21. data/lib/i18n/processes/command/commands/tree.rb +119 -0
  22. data/lib/i18n/processes/command/commands/usages.rb +69 -0
  23. data/lib/i18n/processes/command/commands/xlsx.rb +29 -0
  24. data/lib/i18n/processes/command/dsl.rb +56 -0
  25. data/lib/i18n/processes/command/option_parsers/enum.rb +55 -0
  26. data/lib/i18n/processes/command/option_parsers/locale.rb +60 -0
  27. data/lib/i18n/processes/command/options/common.rb +41 -0
  28. data/lib/i18n/processes/command/options/data.rb +95 -0
  29. data/lib/i18n/processes/command/options/locales.rb +36 -0
  30. data/lib/i18n/processes/command_error.rb +13 -0
  31. data/lib/i18n/processes/commands.rb +31 -0
  32. data/lib/i18n/processes/configuration.rb +132 -0
  33. data/lib/i18n/processes/console_context.rb +76 -0
  34. data/lib/i18n/processes/data/adapter/json_adapter.rb +29 -0
  35. data/lib/i18n/processes/data/adapter/yaml_adapter.rb +27 -0
  36. data/lib/i18n/processes/data/file_formats.rb +111 -0
  37. data/lib/i18n/processes/data/file_system.rb +14 -0
  38. data/lib/i18n/processes/data/file_system_base.rb +205 -0
  39. data/lib/i18n/processes/data/router/conservative_router.rb +66 -0
  40. data/lib/i18n/processes/data/router/pattern_router.rb +60 -0
  41. data/lib/i18n/processes/data/tree/node.rb +204 -0
  42. data/lib/i18n/processes/data/tree/nodes.rb +97 -0
  43. data/lib/i18n/processes/data/tree/siblings.rb +333 -0
  44. data/lib/i18n/processes/data/tree/traversal.rb +190 -0
  45. data/lib/i18n/processes/data.rb +87 -0
  46. data/lib/i18n/processes/google_translation.rb +125 -0
  47. data/lib/i18n/processes/html_keys.rb +16 -0
  48. data/lib/i18n/processes/ignore_keys.rb +30 -0
  49. data/lib/i18n/processes/key_pattern_matching.rb +37 -0
  50. data/lib/i18n/processes/locale_list.rb +19 -0
  51. data/lib/i18n/processes/locale_pathname.rb +17 -0
  52. data/lib/i18n/processes/logging.rb +37 -0
  53. data/lib/i18n/processes/missing_keys.rb +122 -0
  54. data/lib/i18n/processes/path.rb +42 -0
  55. data/lib/i18n/processes/plural_keys.rb +41 -0
  56. data/lib/i18n/processes/rainbow_utils.rb +13 -0
  57. data/lib/i18n/processes/references.rb +101 -0
  58. data/lib/i18n/processes/reports/base.rb +71 -0
  59. data/lib/i18n/processes/reports/spreadsheet.rb +72 -0
  60. data/lib/i18n/processes/reports/terminal.rb +252 -0
  61. data/lib/i18n/processes/scanners/file_scanner.rb +65 -0
  62. data/lib/i18n/processes/scanners/files/caching_file_finder.rb +34 -0
  63. data/lib/i18n/processes/scanners/files/caching_file_finder_provider.rb +33 -0
  64. data/lib/i18n/processes/scanners/files/caching_file_reader.rb +28 -0
  65. data/lib/i18n/processes/scanners/files/file_finder.rb +60 -0
  66. data/lib/i18n/processes/scanners/files/file_reader.rb +19 -0
  67. data/lib/i18n/processes/scanners/occurrence_from_position.rb +27 -0
  68. data/lib/i18n/processes/scanners/pattern_mapper.rb +60 -0
  69. data/lib/i18n/processes/scanners/pattern_scanner.rb +103 -0
  70. data/lib/i18n/processes/scanners/pattern_with_scope_scanner.rb +98 -0
  71. data/lib/i18n/processes/scanners/relative_keys.rb +53 -0
  72. data/lib/i18n/processes/scanners/results/key_occurrences.rb +54 -0
  73. data/lib/i18n/processes/scanners/results/occurrence.rb +69 -0
  74. data/lib/i18n/processes/scanners/ruby_ast_call_finder.rb +62 -0
  75. data/lib/i18n/processes/scanners/ruby_ast_scanner.rb +206 -0
  76. data/lib/i18n/processes/scanners/ruby_key_literals.rb +30 -0
  77. data/lib/i18n/processes/scanners/scanner.rb +17 -0
  78. data/lib/i18n/processes/scanners/scanner_multiplexer.rb +41 -0
  79. data/lib/i18n/processes/split_key.rb +68 -0
  80. data/lib/i18n/processes/stats.rb +24 -0
  81. data/lib/i18n/processes/string_interpolation.rb +16 -0
  82. data/lib/i18n/processes/unused_keys.rb +23 -0
  83. data/lib/i18n/processes/used_keys.rb +177 -0
  84. data/lib/i18n/processes/version.rb +7 -0
  85. data/lib/i18n/processes.rb +69 -0
  86. data/source/p1/_messages/zh/article.properties +9 -0
  87. data/source/p1/_messages/zh/company.properties +62 -0
  88. data/source/p1/_messages/zh/devices.properties +40 -0
  89. data/source/p1/_messages/zh/meeting-rooms.properties +99 -0
  90. data/source/p1/_messages/zh/meetingBooking.properties +18 -0
  91. data/source/p1/_messages/zh/office-areas.properties +64 -0
  92. data/source/p1/_messages/zh/orders.properties +25 -0
  93. data/source/p1/_messages/zh/schedulings.properties +7 -0
  94. data/source/p1/_messages/zh/tag.properties +2 -0
  95. data/source/p1/_messages/zh/ticket.properties +9 -0
  96. data/source/p1/_messages/zh/visitor.properties +5 -0
  97. data/source/p1/messages +586 -0
  98. data/source/p2/orders.properties +25 -0
  99. data/source/p2/schedulings.properties +7 -0
  100. data/source/p2/tag.properties +2 -0
  101. data/source/p2/ticket.properties +9 -0
  102. data/source/p2/visitor.properties +5 -0
  103. data/source/zh.messages.ts +30 -0
  104. data/translated/en/p1/_messages/zh/article.properties +9 -0
  105. data/translated/en/p1/_messages/zh/company.properties +62 -0
  106. data/translated/en/p1/_messages/zh/devices.properties +40 -0
  107. data/translated/en/p1/_messages/zh/meeting-rooms.properties +99 -0
  108. data/translated/en/p1/_messages/zh/meetingBooking.properties +18 -0
  109. data/translated/en/p1/_messages/zh/office-areas.properties +64 -0
  110. data/translated/en/p1/_messages/zh/orders.properties +25 -0
  111. data/translated/en/p1/_messages/zh/schedulings.properties +7 -0
  112. data/translated/en/p1/_messages/zh/tag.properties +2 -0
  113. data/translated/en/p1/_messages/zh/ticket.properties +9 -0
  114. data/translated/en/p1/_messages/zh/visitor.properties +5 -0
  115. data/translated/en/p1/messages +586 -0
  116. data/translated/en/p2/orders.properties +25 -0
  117. data/translated/en/p2/schedulings.properties +7 -0
  118. data/translated/en/p2/tag.properties +2 -0
  119. data/translated/en/p2/ticket.properties +9 -0
  120. data/translated/en/p2/visitor.properties +5 -0
  121. data/translated/en/zh.messages.ts +30 -0
  122. data/translation/en/article.properties +9 -0
  123. data/translation/en/company.properties +56 -0
  124. data/translation/en/meeting-rooms.properties +87 -0
  125. data/translation/en/meetingBooking.properties +14 -0
  126. data/translation/en/messages.en +164 -0
  127. data/translation/en/office-areas.properties +51 -0
  128. data/translation/en/orders.properties +26 -0
  129. data/translation/en/tag.properties +2 -0
  130. data/translation/en/translated +1263 -0
  131. data/translation/en/visitor.properties +4 -0
  132. metadata +408 -0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/scanners/results/key_occurrences'
4
+
5
+ module I18n::Processes::Scanners
6
+ # Describes the API of a scanner.
7
+ #
8
+ # @abstract
9
+ # @since 0.9.0
10
+ class Scanner
11
+ # @abstract
12
+ # @return [Array<Results::KeyOccurrences>] the keys found by this scanner and their occurrences.
13
+ def keys
14
+ fail 'Unimplemented'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/processes/scanners/scanner'
4
+
5
+ module I18n::Processes::Scanners
6
+ # Run multiple {Scanner Scanners} and merge their results.
7
+ # @note The scanners are run concurrently. A thread is spawned per each scanner.
8
+ # @since 0.9.0
9
+ class ScannerMultiplexer < Scanner
10
+ # @param scanners [Array<Scanner>]
11
+ def initialize(scanners:)
12
+ @scanners = scanners
13
+ end
14
+
15
+ # Collect the results of all the scanners. Occurrences of a key from multiple scanners are merged.
16
+ #
17
+ # @note The scanners are run concurrently. A thread is spawned per each scanner.
18
+ # @return (see Scanner#keys)
19
+ def keys
20
+ Results::KeyOccurrences.merge_keys collect_results.flatten(1)
21
+ end
22
+
23
+ private
24
+
25
+ # @return [Array<Array<Results::KeyOccurrences>>]
26
+ def collect_results
27
+ return [@scanners[0].keys] if @scanners.length == 1
28
+ Array.new(@scanners.length).tap do |results|
29
+ results_mutex = Mutex.new
30
+ @scanners.map.with_index do |scanner, i|
31
+ Thread.start do
32
+ scanner_results = scanner.keys
33
+ results_mutex.synchronize do
34
+ results[i] = scanner_results
35
+ end
36
+ end
37
+ end.each(&:join)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Processes
4
+ module SplitKey
5
+ module_function
6
+
7
+ # split a key by dots (.)
8
+ # dots inside braces or parenthesis are not split on
9
+ #
10
+ # split_key 'a.b' # => ['a', 'b']
11
+ # split_key 'a.#{b.c}' # => ['a', '#{b.c}']
12
+ # split_key 'a.b.c', 2 # => ['a', 'b.c']
13
+ def split_key(key, max = Float::INFINITY)
14
+ parts = []
15
+ pos = 0
16
+ return [key] if max == 1
17
+ key_parts(key) do |part|
18
+ parts << part
19
+ pos += part.length + 1
20
+ if parts.length + 1 >= max
21
+ parts << key[pos..-1] unless pos == key.length
22
+ break
23
+ end
24
+ end
25
+ parts
26
+ end
27
+
28
+ def last_key_part(key)
29
+ last = nil
30
+ key_parts(key) { |part| last = part }
31
+ last
32
+ end
33
+
34
+ # yield each key part
35
+ # dots inside braces or parenthesis are not split on
36
+ def key_parts(key, &block)
37
+ return enum_for(:key_parts, key) unless block
38
+ nesting = PARENS
39
+ counts = PARENS_ZEROS # dup'd later if key contains parenthesis
40
+ delim = '.'
41
+ from = to = 0
42
+ key.each_char do |char|
43
+ if char == delim && PARENS_ZEROS == counts
44
+ block.yield key[from...to]
45
+ from = to = (to + 1)
46
+ else
47
+ nest_i, nest_inc = nesting[char]
48
+ if nest_i
49
+ counts = counts.dup if counts.frozen?
50
+ counts[nest_i] += nest_inc
51
+ end
52
+ to += 1
53
+ end
54
+ end
55
+ block.yield(key[from...to]) if from < to && to <= key.length
56
+ true
57
+ end
58
+
59
+ PARENS = %w({} [] ()).each_with_object({}) do |s, h|
60
+ i = h.size / 2
61
+ h[s[0].freeze] = [i, 1].freeze
62
+ h[s[1].freeze] = [i, -1].freeze
63
+ end.freeze
64
+ PARENS_ZEROS = Array.new(PARENS.size, 0).freeze
65
+ private_constant :PARENS
66
+ private_constant :PARENS_ZEROS
67
+ end
68
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Processes
4
+ module Stats
5
+ def forest_stats(forest)
6
+ key_count = forest.leaves.count
7
+ locale_count = forest.count
8
+ if key_count.zero?
9
+ { key_count: 0 }
10
+ else
11
+ {
12
+ locales: forest.map(&:key).join(', '),
13
+ key_count: key_count,
14
+ locale_count: locale_count,
15
+ per_locale_avg: forest.inject(0) { |sum, f| sum + f.leaves.count } / locale_count,
16
+ key_segments_avg: format(
17
+ '%.1f', forest.leaves.inject(0) { |sum, node| sum + node.walk_to_root.count - 1 } / key_count.to_f
18
+ ),
19
+ value_chars_avg: forest.leaves.inject(0) { |sum, node| sum + node.value.to_s.length } / key_count
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n::Processes
4
+ module StringInterpolation
5
+ module_function
6
+
7
+ def interpolate_soft(s, t = {})
8
+ return s unless s
9
+ t.each do |k, v|
10
+ pat = "%{#{k}}"
11
+ s = s.gsub pat, v.to_s if s.include?(pat)
12
+ end
13
+ s
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module I18n::Processes
6
+ module UnusedKeys
7
+ def unused_keys(locales: nil, strict: nil)
8
+ locales = Array(locales).presence || self.locales
9
+ locales.map { |locale| unused_tree(locale: locale, strict: strict) }.compact.reduce(:merge!)
10
+ end
11
+
12
+ # @param [String] locale
13
+ # @param [Boolean] strict if true, do not match dynamic keys
14
+ def unused_tree(locale: base_locale, strict: nil)
15
+ used_key_names = used_tree(strict: true).key_names
16
+ collapse_plural_nodes!(data[locale].select_keys do |key, _node|
17
+ !ignore_key?(key, :unused) &&
18
+ (strict || !used_in_expr?(key)) &&
19
+ !used_key_names.include?(depluralize_key(key, locale))
20
+ end)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'find'
4
+ require 'i18n/processes/scanners/pattern_with_scope_scanner'
5
+ require 'i18n/processes/scanners/ruby_ast_scanner'
6
+ require 'i18n/processes/scanners/scanner_multiplexer'
7
+ require 'i18n/processes/scanners/files/caching_file_finder_provider'
8
+ require 'i18n/processes/scanners/files/caching_file_reader'
9
+
10
+ # Require the pattern mapper even though it's not used by i18n-tasks directly.
11
+ # This allows the user to use it in config/i18n-processes.yml without having to require it.
12
+ # See https://github.com/glebm/i18n-tasks/issues/204.
13
+ require 'i18n/processes/scanners/pattern_mapper'
14
+
15
+ module I18n::Processes
16
+ module UsedKeys # rubocop:disable Metrics/ModuleLength
17
+ SEARCH_DEFAULTS = {
18
+ paths: %w[tmp/].freeze,
19
+ relative_roots: %w[app/controllers app/helpers app/mailers app/presenters app/views].freeze,
20
+ scanners: [
21
+ ['::I18n::Processes::Scanners::RubyAstScanner', only: %w[*.rb]],
22
+ ['::I18n::Processes::Scanners::PatternWithScopeScanner', exclude: %w[*.rb]]
23
+ ],
24
+ strict: true
25
+ }.freeze
26
+
27
+ ALWAYS_EXCLUDE = %w[*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
28
+ *.yml *.json *.zip *.tar.gz *.swf *.flv].freeze
29
+
30
+ # Find all keys in the source and return a forest with the keys in absolute form and their occurrences.
31
+ #
32
+ # @param key_filter [String] only return keys matching this pattern.
33
+ # @param strict [Boolean] if true, dynamic keys are excluded (e.g. `t("category.#{ category.key }")`)
34
+ # @param include_raw_references [Boolean] if true, includes reference usages as they appear in the source
35
+ # @return [Data::Tree::Siblings]
36
+ def used_tree(key_filter: nil, strict: nil, include_raw_references: false)
37
+ src_tree = used_in_source_tree(key_filter: key_filter, strict: strict)
38
+ raw_refs, resolved_refs, used_refs = process_references(src_tree['used'].children)
39
+ raw_refs.leaves { |node| node.data[:ref_type] = :reference_usage }
40
+ resolved_refs.leaves { |node| node.data[:ref_type] = :reference_usage_resolved }
41
+ used_refs.leaves { |node| node.data[:ref_type] = :reference_usage_key }
42
+ src_tree.tap do |result|
43
+ tree = result['used'].children
44
+ tree.subtract_by_key!(raw_refs)
45
+ tree.merge!(raw_refs) if include_raw_references
46
+ tree.merge!(used_refs).merge!(resolved_refs)
47
+ end
48
+ end
49
+
50
+ def used_in_source_tree(key_filter: nil, strict: nil)
51
+ keys = ((@keys_used_in_source_tree ||= {})[strict?(strict)] ||=
52
+ scanner(strict: strict).keys.freeze)
53
+ if key_filter
54
+ key_filter_re = compile_key_pattern(key_filter)
55
+ keys = keys.select { |k| k.key =~ key_filter_re }
56
+ end
57
+ Data::Tree::Node.new(
58
+ key: 'used',
59
+ data: { key_filter: key_filter },
60
+ children: Data::Tree::Siblings.from_key_occurrences(keys)
61
+ ).to_siblings
62
+ end
63
+
64
+ def scanner(strict: nil)
65
+ (@scanner ||= {})[strict?(strict)] ||= begin
66
+ shared_options = search_config.dup
67
+ shared_options.delete(:scanners)
68
+ shared_options[:strict] = strict unless strict.nil?
69
+ log_verbose 'Scanners: '
70
+ Scanners::ScannerMultiplexer.new(
71
+ scanners: search_config[:scanners].map do |(class_name, args)|
72
+ if args && args[:strict]
73
+ fail CommandError, 'the strict option is global and cannot be applied on the scanner level'
74
+ end
75
+ ActiveSupport::Inflector.constantize(class_name).new(
76
+ config: merge_scanner_configs(shared_options, args || {}),
77
+ file_finder_provider: caching_file_finder_provider,
78
+ file_reader: caching_file_reader
79
+ )
80
+ end.tap { |scanners| log_verbose { scanners.map { |s| " #{s.class.name} #{s.config.inspect}" } * "\n" } }
81
+ )
82
+ end
83
+ end
84
+
85
+ def search_config
86
+ @search_config ||= begin
87
+ conf = (config[:search] || {}).deep_symbolize_keys
88
+ if conf[:scanner]
89
+ warn_deprecated 'search.scanner is now search.scanners, an array of [ScannerClass, options]'
90
+ conf[:scanners] = [[conf.delete(:scanner)]]
91
+ end
92
+ if conf[:ignore_lines]
93
+ warn_deprecated 'search.ignore_lines is no longer a global setting: pass it directly to the pattern scanner.'
94
+ conf.delete(:ignore_lines)
95
+ end
96
+ if conf[:include]
97
+ warn_deprecated 'search.include is now search.only'
98
+ conf[:only] = conf.delete(:include)
99
+ end
100
+ merge_scanner_configs(SEARCH_DEFAULTS, conf).freeze
101
+ end
102
+ end
103
+
104
+ def merge_scanner_configs(a, b)
105
+ a.deep_merge(b).tap do |c|
106
+ %i[scanners paths relative_roots].each do |key|
107
+ c[key] = a[key] if b[key].blank?
108
+ end
109
+ %i[exclude].each do |key|
110
+ merged = Array(a[key]) + Array(b[key])
111
+ c[key] = merged unless merged.empty?
112
+ end
113
+ end
114
+ end
115
+
116
+ def caching_file_finder_provider
117
+ @caching_file_finder_provider ||= Scanners::Files::CachingFileFinderProvider.new(exclude: ALWAYS_EXCLUDE)
118
+ end
119
+
120
+ def caching_file_reader
121
+ @caching_file_reader ||= Scanners::Files::CachingFileReader.new
122
+ end
123
+
124
+ # @return [Boolean] whether the key is potentially used in a code expression such as `t("category.#{category_key}")`
125
+ def used_in_expr?(key)
126
+ !!(key =~ expr_key_re) # rubocop:disable Style/DoubleNegation
127
+ end
128
+
129
+ private
130
+
131
+ # @param strict [Boolean, nil]
132
+ # @return [Boolean]
133
+ def strict?(strict)
134
+ strict.nil? ? search_config[:strict] : strict
135
+ end
136
+
137
+ # keys in the source that end with a ., e.g. t("category.#{ cat.i18n_key }") or t("category." + category.key)
138
+ # @param [String] replacement for interpolated values.
139
+ def expr_key_re(replacement: ':')
140
+ @expr_key_re ||= begin
141
+ # disallow patterns with no keys
142
+ ignore_pattern_re = /\A[\.#{replacement}]*\z/
143
+ patterns = used_in_source_tree(strict: false).key_names.select do |k|
144
+ k.end_with?('.') || k =~ /\#{/
145
+ end.map do |k|
146
+ pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.')}"
147
+ next if pattern =~ ignore_pattern_re
148
+ pattern
149
+ end.compact
150
+ compile_key_pattern "{#{patterns * ','}}"
151
+ end
152
+ end
153
+
154
+ # Replace interpolations in dynamic keys such as "category.#{category.i18n_key}".
155
+ # @param key [String]
156
+ # @param replacement [String]
157
+ # @return [String]
158
+ def replace_key_exp(key, replacement)
159
+ scanner = StringScanner.new(key)
160
+ braces = []
161
+ result = []
162
+ while (match_until = scanner.scan_until(/(?:#?\{|})/))
163
+ if scanner.matched == '#{'
164
+ braces << scanner.matched
165
+ result << match_until[0..-3] if braces.length == 1
166
+ elsif scanner.matched == '}'
167
+ prev_brace = braces.pop
168
+ result << replacement if braces.empty? && prev_brace == '#{'
169
+ else
170
+ braces << '{'
171
+ end
172
+ end
173
+ result << key[scanner.pos..-1] unless scanner.eos?
174
+ result.join
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n
4
+ module Processes
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # define all the modules to be able to use ::
4
+ module I18n
5
+ module Processes
6
+ class << self
7
+ def gem_path
8
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
9
+ end
10
+
11
+ def verbose?
12
+ @verbose
13
+ end
14
+
15
+ attr_writer :verbose
16
+
17
+ # Add a scanner to the default configuration.
18
+ #
19
+ # @param scanner_class_name [String]
20
+ # @param scanner_opts [Hash]
21
+ # @return self
22
+ def add_scanner(scanner_class_name, scanner_opts = {})
23
+ scanners = I18n::Processes::Configuration::DEFAULTS[:search][:scanners]
24
+ scanners << [scanner_class_name, scanner_opts]
25
+ scanners.uniq!
26
+ self
27
+ end
28
+
29
+ # Add commands to i18n-processes
30
+ #
31
+ # @param commands_module [Module]
32
+ # @return self
33
+ def add_commands(commands_module)
34
+ ::I18n::Processes::Commands.send :include, commands_module
35
+ self
36
+ end
37
+ end
38
+
39
+ @verbose = !ENV['VERBOSE'].nil?
40
+
41
+ module Data
42
+ end
43
+ end
44
+ end
45
+
46
+ require 'active_support/inflector'
47
+ require 'active_support/core_ext/hash'
48
+ require 'active_support/core_ext/array/access'
49
+ require 'active_support/core_ext/array/extract_options'
50
+ require 'active_support/core_ext/module/delegation'
51
+ require 'active_support/core_ext/object/blank'
52
+ begin
53
+ # activesupport >= 3
54
+ require 'active_support/core_ext/object/try'
55
+ rescue LoadError => _e
56
+ # activesupport ~> 2.3.2
57
+ require 'active_support/core_ext/try'
58
+ end
59
+ require 'rainbow'
60
+ require 'erubi'
61
+
62
+ require 'i18n/processes/version'
63
+ require 'i18n/processes/base_process'
64
+
65
+ # Add internal locale data to i18n gem load path
66
+ require 'i18n'
67
+ Dir[File.join(I18n::Processes.gem_path, 'config', 'locales', '*.yml')].each do |locale_file|
68
+ I18n.config.load_path << locale_file
69
+ end
@@ -0,0 +1,9 @@
1
+ # 文章
2
+ article.title=标题
3
+ article.shown=是否显示
4
+ article.showOrder=显示顺序(倒序)
5
+ article.channel=频道
6
+ article.category=文章栏目
7
+ article.category.id=文章栏目
8
+ article.friendlyName=英文名称
9
+ article.readCount=阅读数
@@ -0,0 +1,62 @@
1
+ # page
2
+ company.show.title=公司设置
3
+ company.show.subtitle=公司基本信息
4
+ company.show.save-button=保存修改
5
+
6
+ company.nav.show=基本信息
7
+ company.nav.advance=高级设置
8
+ company.nav.orders=订单中心
9
+
10
+ company.show.rightbar.invation=邀请
11
+ company.show.rightbar.invation.link=点击邀请同事加入
12
+ company.show.rightbar.payinfo=付费信息
13
+ company.show.rightbar.buy-link=购买授权
14
+ company.show.rightbar.price-link=版本功能比较
15
+ company.show.rightbar.expired-on=有效期至
16
+ company.show.rightbar.max-rooms-count=授权可开通会议室数
17
+ company.show.rightbar.current-rooms-count=已经开通会议室数量
18
+ company.show.rightbar.custom-domain=自定义域名
19
+ company.show.rightbar.custom-domain.hint1=%s 付费用户可支持自定义域名,请发邮件至
20
+ company.show.rightbar.custom-domain.hint2=申请,并提供您期望映射的公司域名。
21
+ company.show.rightbar.custom-domain.hint3=我们将在一个工作日内和您联系,完成配置工作。
22
+
23
+ company.show.rightbar.invate-code.line1=快速提示: 可让同事用微信搜索公众号《会议室助手》
24
+ company.show.rightbar.invate-code.line2=然后使用公司邀请码加入:
25
+
26
+ company.advance.subtitle=公司高级设置
27
+ company.advance.ext-meeting-type=会议扩展类型
28
+ company.advance.ext-meeting-type.hint=可以取消不使用的会议类型
29
+ company.advance.booking-begin=会议预订开始时间
30
+ company.advance.booking-end=会议预订结束时间
31
+ company.advance.booking-limit=会议预订时间限制
32
+ company.advance.booking-split=会议预订时间间隔
33
+ company.advance.booking-date-limit=预订日期限制
34
+ company.advance.booking-date-limit.hint=指定只能预订指定天数内的会, 过远的日期可能导致资源浪费;固定会议亦受此限制
35
+
36
+ # form
37
+ company.name=公司名称
38
+ company.corpName=公司名称
39
+ company.notice=公司简介
40
+ company.notice.placeholder=输入公司简介
41
+ company.notice.hint=200字以内
42
+ company.emailPostfix=公司邮箱后缀
43
+ company.emailPostfix.placeholder=用于确保只有公司员工才可以加入,不填写则不做邮箱限制
44
+ company.emailPostfix.hint=是邮箱@后面的值,例如公司邮箱tom@bigcompany.com,则填写bigcompany.com;可用逗号分隔设置多个
45
+ company.logoFile=公司Logo图片
46
+ company.logoFile.hint.please-select-file=请选择公司Logo图片文件
47
+ company.logoFile.hint.logo-usage=可以上传公司Logo, 显示在系统界面上。请确保使用正方形图片。
48
+ company.logoFile.hint.remove-logo.prefix=您也可以
49
+ company.logoFile.hint.remove-logo.title=删除公司Logo
50
+ company.logoFile.hint.remove-logo.confirm=确认删除公司Logo?
51
+ company.logoFile.alt=当前Logo
52
+ corpLang=公司默认界面语言
53
+ company.wxqyContactsSecret=企业微信通讯录Secret
54
+ company.wxqyContactsSecret.placeholder=用于同步企业微信组织架构到会议室系统
55
+ company.wxqyContactsSecret.hint=进入企业微信管理后台,在“管理工具”—“通讯录同步助手”开启“API接口同步”,然后复制界面上的Secret到这里
56
+
57
+ # flash
58
+ flash.company.notice.save-advance-succ=公司高级设置保存成功!
59
+ flash.company.notice.rm-logo-succ=删除公司Logo成功!
60
+ flash.company.warn.bad-time-split=时间间隔非法!
61
+ flash.company.warn.need-square-picture=请确保选择正方形的图片!
62
+ flash.company.warn.edu-cannot-change-name=您的授权不允许修改公司名称,已恢复原名称,有需要可联系客服修改
@@ -0,0 +1,40 @@
1
+ deviceType.ANDROID_PAD=原生Android屏
2
+ deviceType.H5_PAD=显示屏
3
+ deviceType.LIGHT=灯控
4
+ deviceType.POWER=电源控制
5
+ deviceType.DOOR=门禁
6
+ deviceType.HUMAN_INFRARED=人体红外探测
7
+ deviceType.HUMAN_DETECT=人体活动探测
8
+ deviceType.AGENT=设备代理
9
+
10
+ deviceInstruct.ANDROID_PAD=原生Android屏
11
+ deviceInstruct.H5_PAD=显示屏
12
+ deviceInstruct.LIGHT=灯光
13
+ deviceInstruct.LIGHT.true=开
14
+ deviceInstruct.LIGHT.false=关
15
+ deviceInstruct.LIGHT.null=未连接
16
+ deviceInstruct.POWER=电源
17
+ deviceInstruct.POWER.true=开
18
+ deviceInstruct.POWER.false=关
19
+ deviceInstruct.POWER.null=未连接
20
+ deviceInstruct.DOOR=门禁
21
+ deviceInstruct.DOOR.true=开
22
+ deviceInstruct.DOOR.false=关
23
+ deviceInstruct.DOOR.null=未连接
24
+ deviceInstruct.HUMAN_INFRARED=人体探测
25
+ deviceInstruct.HUMAN_INFRARED.true=有人
26
+ deviceInstruct.HUMAN_INFRARED.false=无人
27
+ deviceInstruct.HUMAN_INFRARED.null=未连接
28
+ deviceInstruct.HUMAN_DETECT=人体探测
29
+ deviceInstruct.HUMAN_DETECT.true=有人
30
+ deviceInstruct.HUMAN_DETECT.false=无人
31
+ deviceInstruct.HUMAN_DETECT.null=未连接
32
+ deviceInstruct.AGENT=连接
33
+ deviceInstruct.AGENT.true=在线
34
+ deviceInstruct.AGENT.false=离线
35
+ deviceInstruct.AGENT.null=未连接
36
+
37
+ # 界面
38
+ manager.devices.unbind=解除绑定
39
+ manager.devices.bind=绑定
40
+ manager.devices.confirm-unbind=确认解除%s的绑定?
@@ -0,0 +1,99 @@
1
+ # index页面元素
2
+ meeting-rooms.index.title=会议室管理
3
+ meeting-rooms.index.subtitle=维护公司可用的会议室
4
+ meeting-rooms.index.tablabel=会议室
5
+ meeting-rooms.index.drag-tips=拖动会议室可进行排序
6
+ meeting-rooms.operate.new=新增会议室
7
+
8
+ meeting-rooms.index.open-for-view=开放会议查看
9
+ meeting-rooms.index.open-for-view.hint=非注册用户,可以查看会议室安排
10
+
11
+ meeting-rooms.operate.print=打印二维码门牌
12
+
13
+ meeting-rooms.add.subtitle=添加新会议室
14
+
15
+ meeting-rooms.edit.subtitle=修改 - %s
16
+ meeting-rooms.pad.title=绑定Pad设备
17
+ meeting-rooms.pad.had-binded=当前会议室已经绑定了Pad设备。
18
+ meeting-rooms.pad.rebind=重新绑定Pad设备
19
+ meeting-rooms.pad.install-app-please=请安装Android App:
20
+ meeting-rooms.pad.open-and-input-bindcode=打开App后, 按提示输入绑定码:
21
+
22
+ meeting-rooms.show.top-hint1=临时使用会议室,请用微信扫一扫预订
23
+ meeting-rooms.show.top-hint2=以免与他人时间冲突
24
+ meeting-rooms.show.bottom-hint1=本页可直接打印,张贴在会议室门口,方便用微信扫一扫查看会议室状态
25
+ meeting-rooms.show.bottom-hint2=『打印』按钮之后的内容不会被打印
26
+ meeting-rooms.show.bottom-hint3=您也可以复制以上内容到Word编辑后再打印
27
+
28
+ meeting-rooms.device.title=会议室设备
29
+
30
+ # MeetingRoom Form
31
+ meetingRoom.name=会议室名称
32
+ meetingRoom.name.placeholder=请输入会议室名称
33
+ meetingRoom.location=位置
34
+ meetingRoom.location.placeholder=会议室位置
35
+ meetingRoom.location.hint=简单说明会议室所在的位置, 如『三号楼西侧』, 让大家可以知道在哪
36
+ meetingRoom.capacity=可容纳人数
37
+ meetingRoom.capacity.placeholder=请输入会议室可容纳人数
38
+ meetingRoom.facilities=会议设施
39
+ meetingRoom.facilities.hint=此会议室可用的会议设施
40
+ meetingRoom.officeArea=办公区域
41
+ disabledReason=禁用原因
42
+ meetingRoom.disableReason.placeholder=请用简短文字说明会议室禁用的原因
43
+ meetingRoom.disableReason.hint=50字以内, 将用于显示在微信会议室查看界面, 提示相关人员
44
+
45
+ meetingRoom.pricePerHour=每小时费用
46
+
47
+ meetingRoom.shortName=短名称
48
+ meetingRoom.shortName.placeholder=请输入会议室的简短名称,建议不超过10字,可不输入
49
+ meetingRoom.shortName.hint=短名称用于显示在会议通知、显示屏等有空间要求的地方;不输入则直接使用『名称』
50
+
51
+ meetingRoom.allowRoles=预订许可
52
+ meetingRoom.allowRoles.hint=如不勾选"所有人可预订", 则普通员工不能预订此会议室, 只有指定角色可以预定
53
+
54
+ meetingRoom.settings=会议室设置
55
+ meetingRoom.settings.not-public=不对外公开会议安排
56
+ meetingRoom.settings.not-public.hint=非注册用户,不能查看会议室安排
57
+
58
+ meetingRoom.notice=会议室注意事项
59
+ meetingRoom.notice.placeholder=输入会议室注意事项
60
+ meetingRoom.notice.hint=可选, 200字以内, 告知此会议室使用注意事项
61
+
62
+ meetingRoom.officeArea.id=办公区域
63
+ meetingRoom.officeArea.hint=可选, 地理位置区分的办公区域, 可以认为一个"前台接待工位"对应一个办公区域
64
+
65
+ meetingRoom.disable-for-short-time-no-reason=无理由,暂时禁用
66
+ meetingRoom.disable-for-short-time=暂时禁用
67
+ meetingRoom.disable-for-short-time.hint=暂时不允许预订
68
+ meetingRoom.booking.allowAll=所有人可预订
69
+ meetingRoom.capacity.number=可容纳%s人
70
+
71
+ # 限#{list room?.allowRoleSet(), as:'role'}&{'roles.' + role}#{if !role_isLast}、#{/if}#{/list}预订
72
+ meetingRoom.booking.limitFor.start=限
73
+ meetingRoom.booking.limitFor.end=预订
74
+
75
+ # flash
76
+ flash.notice.create-rooms-succ=会议室%s创建成功
77
+ flash.notice.save-rooms-succ=会议室%s修改成功
78
+ flash.notice.disable-rooms-succ=会议室%s禁用成功
79
+ flash.notice.disable-rooms-with-meetings=会议室%s禁用成功, 但还有%s个会议, 请线下通知相关人员
80
+ flash.notice.meeting-rooms.generate-pad-bind-key-succ=已经重新生成Pad绑定码, 请到Pad界面输入新的绑定码
81
+ flash.warn.meeting-room-deleted-with-meetings=%s还有会议安排,请先确保%s无会议安排后再删除会议室。
82
+ flash.warn.meeting-room-reach-max-count=已经达到允许可创建会议室数量上限(%s间), 不能再新增会议室, 请联系在线客服购买更多会议室许可
83
+
84
+ flash.notice.meeting-rooms.unbind-device-succ=解绑设备%s(编号:%s)成功
85
+
86
+ # validation
87
+ validate.meetingRoom.disableReason.require=禁用原因必填
88
+ validate.meetingRoom.disableReason.length=禁用原因不能超过50字
89
+
90
+
91
+ # 其它
92
+ meeting-rooms.bookingPermision=预订权限
93
+ meeting-rooms.not-exists=不存在的会议室
94
+ meeting-rooms.not-bind-door=没有绑定门禁设备
95
+ meeting-rooms.not-bind-light=没有绑定灯光控制设备
96
+ meeting-rooms.not-bind-power=没有绑定电源控制设备
97
+
98
+ # 交易
99
+ trade.changeAmount=交易金额
@@ -0,0 +1,18 @@
1
+ # HTML页面
2
+ meetingBooking.title.main=预订会议
3
+ meetingBooking.title.booking-by-date=按日期预订
4
+ meetingBooking.title.detail=会议详情
5
+ meetingBooking.title.find-room-by-time=按时间找会议室
6
+ meetingBooking.title.find-time-by-room=按会议室找时间
7
+
8
+
9
+ meetingBooking.booking=预订
10
+
11
+
12
+ # 确认状态
13
+ meeting.joinStatus.notMember=非会议参与人员不能设置参与状态
14
+ meeting.joinStatus.invalidStatus=非法参与状态
15
+
16
+ # 会议审批
17
+ meetingApprove.approval=是否通过
18
+ meetingApprove.remark=备注