inochi 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  <% chapter "Introduction" do %>
2
2
  <% project_summary do %>
3
- **<%= $project %>** is an infrastructure for [RubyGems](http://www.rubygems.org)-based software projects that encourages good documentation, reduces programming effort, and automates common tasks.
3
+ **<%= $project %>** is an infrastructure for [RubyGems](http://www.rubygems.org)-based software projects that encourages good documentation and reduces programming effort by automating common tasks.
4
4
  <% end %>
5
5
 
6
6
  **<%= $project %>** is exciting because:
@@ -22,27 +22,21 @@
22
22
  * [hoe](http://seattlerb.rubyforge.org/hoe/)
23
23
  * [newgem](http://newgem.rubyforge.org)
24
24
  * [Mr Bones](http://codeforpeople.rubyforge.org/bones/)
25
-
26
- <!--
27
- Say goodbye to the days of manually requiring libraries:
28
-
29
- require File.join(File.dirname(__FILE__), 'project', 'library')
30
-
31
- With **<%= $project %>** you can require any library in your project:
32
-
33
- require 'project/library'
34
- -->
25
+ * [Gemify](http://gitorious.org/projects/gemify/)
26
+ * [Jeweler](http://technicalpickles.github.com/jeweler/)
27
+ * [GemMake](https://simonmenke.github.com/gm/)
28
+ * [SimpleGem](http://github.com/reagent/simple-gem/tree/master)
35
29
 
36
30
  <% paragraph "Etymology" do %>
37
31
  In the past, software development was thought to be like mathematical modeling or building construction: the assembly of inanimate objects. Nowadays, it is thought to be more like gardening: the cultivation of life!
38
32
 
39
33
  In this manner, I consider this project not as a generator of skeletons or as a builder of scaffolds, but as a *giver of life*. That is why I named this project "inochi", the Japanese word for *life*.
40
34
 
41
- Happy gardening!
35
+ Happy cultivation!
42
36
  <% end %>
43
37
 
44
38
  <% section "Logistics" do %>
45
- * <%= xref "history", "Release notes" %> --- history of project releases.
39
+ * <%= xref "History", "Release notes" %> --- history of project releases.
46
40
  * [Source code](http://github.com/sunaku/<%= $program %>) --- obtain via [Git](http://git.or.cz) or browse online.
47
41
  * [API reference](api/index.html) --- documentation for source code.
48
42
  * [Project home](<%= $website %>) --- the **<%= $project %>** project home page.
@@ -99,5 +93,11 @@
99
93
  <% section "Credits" do %>
100
94
  <%= $logo = "![project logo](#{$program}.png)".to_inline_xhtml %>
101
95
  <%# include README #%>
96
+
97
+ **<%= $project %>** is made possible by
98
+ <%= xref "History", "contributions" %>
99
+ from users like you:
100
+
101
+ * Florian Gilcher
102
102
  <% end %>
103
- <% end %>
103
+ <% end %>
@@ -2,18 +2,17 @@
2
2
  <% section "Requirements" do %>
3
3
  Your system needs the following software to run **<%= $project %>**.
4
4
 
5
- | Software | Description | Notes |
6
- | -------- | ----------- | ----- |
7
- | [Ruby](http://ruby-lang.org) | Ruby language interpreter | Version 1.8.6 or 1.8.7 is required. |
8
- | [RubyGems](http://rubygems.org) | Ruby packaging system | Version 1.x.x is required. |
9
- | [<%= ERBook::PROJECT %>](<%= ERBook::WEBSITE %>) | <%= ERBook::TAGLINE %> | Version <%= ERBook::VERSION.series %> is required. |
10
- | [Lynx](http://lynx.isc.org) | Text-mode web browser | Version 2.8.6 or newer is required to convert HTML into plain text. |
5
+ | Software | Description | Notes |
6
+ | -------- | ----------- | ----- |
7
+ | [Ruby](http://ruby-lang.org) | Ruby language interpreter | Version 1.8.6 or 1.8.7 is required. |
8
+ | [RubyGems](http://rubygems.org) | Ruby packaging system | Version 1.x.x is required. |
9
+ | [Lynx](http://lynx.isc.org) | Text-mode web browser | Version 2.8.6 or newer is required to convert HTML into plain text. |
11
10
  <% end %>
12
11
 
13
12
  <% section "Installation" do %>
14
13
  You can install **<%= $project %>** by running this command:
15
14
 
16
- gem install <%= ERBook::PROGRAM %> <%= $program %>
15
+ gem install -f <%= $program %>
17
16
 
18
17
  To check whether the installation was sucessful, run this command:
19
18
 
@@ -47,6 +46,8 @@
47
46
 
48
47
  * <tt>index.erb</tt> --- source of this user manual.
49
48
 
49
+ * <tt>lang/</tt> --- translations of language phrases.
50
+
50
51
  * <tt>LICENSE</tt> --- copyright notice and legal conditions.
51
52
  <% end %>
52
53
  <% end %>
@@ -1,3 +1,5 @@
1
+ <% yaml_addr = "http://yaml.kwiki.org/?YamlInFiveMinutes" %>
2
+
1
3
  <% part "Usage" do %>
2
4
  <% section "Command-line interface" do %>
3
5
  When you run this command:
@@ -312,7 +314,7 @@
312
314
  <% paragraph "Units and tests" do %>
313
315
  Every Ruby source file in your project's <tt>lib/</tt> directory is considered to be a **unit**. Likewise, every Ruby source file in your project's <tt>test/</tt> is considered to be a **test**.
314
316
 
315
- As a result, your project's <tt>test/</tt> directory structure *mirrors* the structure of your project's <tt>lib/</tt> directory. For example, if your project has a <tt>lib/foo/bar.rb</tt> unit, then <tt>test/foo/bar.rb</tt> would be its the corresponding test.
317
+ As a result, your project's <tt>test/</tt> directory structure *mirrors* the structure of your project's <tt>lib/</tt> directory. For example, if your project has a <tt>lib/foo/bar.rb</tt> unit, then <tt>test/foo/bar.rb</tt> would be its corresponding test.
316
318
  <% end %>
317
319
 
318
320
  <% paragraph "Test execution" do %>
@@ -326,7 +328,7 @@
326
328
 
327
329
  As for the details of test execution:
328
330
 
329
- * Tests are executed by the [minitest] library, which allows you to write unit tests in a combination of styles: traditional TDD, modern BDD, alternative rSpec BDD, and mock-based validation styles.
331
+ * Tests are executed by the [minitest] library, which allows you to write unit tests in a combination of styles: traditional xUnit TDD, alternative rSpec BDD, and mock-based validation styles.
330
332
 
331
333
  * Within each test, test cases are executed in random order. This is the default behavior of the [minitest] library.
332
334
 
@@ -344,13 +346,116 @@
344
346
  <% end %>
345
347
  <% end %>
346
348
 
347
- <% section "Publish your project" do %>
348
- This command performs all of the automated steps described in the following sections:
349
+ <% section "Translate your project" do %>
350
+ <% phrases_file = "<tt>lang/phrases.yaml</tt>" %>
351
+
352
+ Although English is the *lingua franca* of today, your project's users may prefer to interact with it in their native language. **<%= $project %>** makes it easy to translate your project and also makes it easy for users to correct and contribute translations to your project.
353
+
354
+ <% section "Language phrases" do %>
355
+ **<%= $project %>** equips your project module with a `PHRASES` constant (see the [`Inochi::Phrases` class](api/Inochi/Phrases.html)) which provides access to translations of language phrases used in your project.
356
+
357
+ The [`Inochi::Phrases#[]` method](api/Inochi/Phrases.html#[]-instance_method) translates a given language phrase into the user's preferred language, which is automatically inferred from their environment, but may be explictly overridden by the user via the <tt>--locale</tt> option of <%= xref "Run project executable", "your project's main executable" %>:
358
+
359
+ <code>
360
+ your_project::PHRASES['Have a nice day!']
361
+ </code>
362
+
363
+ If there is no <%= xref "Translation files", "translation file" %> for the user's preferred language, or it does not define a translation for a particular language phrase, then the language phrase will be used untranslated:
364
+
365
+ <code>
366
+ your_project::PHRASES['No translation for this']
367
+ #=> 'No translation for this'
368
+ </code>
369
+
370
+ <% paragraph "Parameterizing language phrases" do %>
371
+ Language phrases can be parameterized using [`printf` format placeholders](http://en.wikipedia.org/wiki/Printf#printf_format_placeholders) to ease translation:
372
+
373
+ <code>
374
+ your_project::PHRASES['Good afternoon, %s.', user_name]
375
+ your_project::PHRASES['You are %d years old.', user_age]
376
+ </code>
377
+ <% end %>
378
+
379
+ <% paragraph "Explicit translation into a language" do %>
380
+ If a language phrase must be translated into a specific language, regardless of the user's preference, you can invoke the respective method (whose name is the same as the [ISO-639 language code](http://en.wikipedia.org/wiki/ISO_639) of the language into which you wish to translate) on your `PHRASES` object:
381
+
382
+ <code>
383
+ # explictly translate into Japanese (ja)
384
+ your_project::PHRASES.ja('Goodbye %s!', user_name)
385
+
386
+ # explictly translate into French (fr)
387
+ your_project::PHRASES.fr('Farewell %s!', user_name)
388
+ </code>
389
+ <% end %>
390
+ <% end %>
391
+
392
+ <% section "Translation files" do %>
393
+ Translation files are [YAML documents](<%= yaml_addr %>) that reside in your project's <tt>lang/</tt> directory. They provide translations for <%= xref "Language phrases", "language phrases" %> used in your project.
394
+
395
+ For example, suppose that your language phrases are written in English, the <tt>lang/es.yaml</tt> (Spanish) translation file would appear like this:
396
+
397
+ <pre>
398
+ #
399
+ # (this is a comment)
400
+ #
401
+ # language phrase : translation
402
+ #
403
+ Hello %s! : ¡Hola %s!
404
+ Money : Dinero
405
+ Ticket : Tarjeta
406
+ See you later %s! : ¡Hasta la vista %s!
407
+ "%s: Quickly, please!" : "%s: ¡Rápidamente, por favor!"
408
+ </pre>
409
+
410
+ On each line, the original language phrase (as used in your project) appears first, then a single semicolon (:), followed by the translation.
411
+
412
+ Also, notice that if a language phrase contains a semicolon, then the entire phrase must be enclosed in quotation marks. The same rule applies to its corresponding translation.
413
+ <% end %>
414
+
415
+ <% section "Extracting language phrases" do %>
416
+ <pre>
417
+ # rake lang:dump
418
+ <%= verbatim `rake lang:dump` %>
419
+ </pre>
420
+
421
+ The above command exercises your project's code and extracts all *utilized* language phrases into the <%= phrases_file %> file. Continue reading to learn how this is accomplished.
422
+
423
+ <% paragraph "Dynamic analysis" do %>
424
+ Because Ruby is a dynamically interpreted language, the easiest way to extract language phrases is to evaluate the code that defines them and keep track of which phrases are defined.
425
+
426
+ But how can **<%= $project %>** exercise all Ruby code in your project? The answer is through *unit tests*. Because unit tests already exercise your project's code, **<%= $project %>** can use them to reach and extract all language phrases from your project.
427
+
428
+ However, note that if your unit tests *do not* exercise a part of your project that defines language phrases, then those phrases *will not* be extracted by **<%= $project %>**.
429
+
430
+ This gives you extra motivation to improve the coverage of your test suite---at least to the point where all code that defines language phrases is covered.
431
+ <% end %>
349
432
 
433
+ <% paragraph "Static analysis" do %>
434
+ In a future release, I plan to extract language phrases through static analysis of Ruby code. This approach will supplement the current practice of reaching language phrases through unit tests.
435
+
436
+ Patches are welcome! :-)
437
+ <% end %>
438
+ <% end %>
439
+
440
+ <% section "Translating language phrases" do %>
441
+ After you have extracted all language phrases from your project (either manually or via <%= xref "Extracting language phrases" %>) into the <%= phrases_file %> file, **<%= $project %>** can automatically translate them into various languages using the [Yahoo! BabelFish translation service](http://babelfish.yahoo.com):
442
+
443
+ <pre>
444
+ # rake lang:conv from=LANGUAGE_CODE
445
+ <%= verbatim `rake lang:conv from=LANGUAGE_CODE 2>&1` %>
446
+ </pre>
447
+
448
+ Notice that you must specify the language in which your phrases are written, via the <tt>from=</tt> parameter. **<%= $project %>** cannot determine this automatically.
449
+ <% end %>
450
+ <% end %>
451
+
452
+ <% section "Publish your project" do %>
350
453
  <pre>
351
454
  # rake pub
352
455
  </pre>
353
456
 
457
+ The above command performs all automated steps described in the following sections.
458
+
354
459
  <% section "Build a RubyGem" do %>
355
460
  Build a RubyGem by running:
356
461
 
@@ -391,7 +496,7 @@
391
496
 
392
497
  <% logins_file = "~/.config/inochi/logins.yaml" %>
393
498
 
394
- This information is expected to be stored in a <tt><%= logins_file %></tt> file (this location can be overridden by passing the `:logins_file` option to the [`Inochi.rake()` method](api/Inochi.html#rake-class_method)), where <tt>~</tt> denotes the path to your home directory. This file is a [YAML](http://www.yaml.org) document containing the following parameters:
499
+ This information is expected to be stored in a <tt><%= logins_file %></tt> file (this location can be overridden by passing the `:logins_file` option to the [`Inochi.rake()` method](api/Inochi.html#rake-class_method)), where <tt>~</tt> denotes the path to your home directory. This file is a [YAML document](<%= yaml_addr %>) containing the following parameters:
395
500
 
396
501
  <code lang="yaml">
397
502
  www.ruby-forum.com:
@@ -1,9 +1,18 @@
1
+ require 'rubygems'
2
+
3
+ module Inochi
4
+ end
5
+
1
6
  $LOAD_PATH << File.dirname(__FILE__)
2
- require 'inochi/inochi'
7
+ require 'inochi/init'
8
+ require 'inochi/main'
9
+ require 'inochi/rake'
10
+ require 'inochi/book'
11
+ require 'inochi/util'
3
12
 
4
13
  Inochi.init :Inochi,
5
- :version => '0.2.0',
6
- :release => '2009-01-25',
14
+ :version => '0.3.0',
15
+ :release => '2009-02-12',
7
16
  :website => 'http://snk.tuxfamily.org/lib/inochi',
8
17
  :tagline => 'Gives life to RubyGems-based software',
9
18
  :require => {
@@ -11,8 +20,10 @@ Inochi.init :Inochi,
11
20
  'rubyforge' => '~> 1', # for publishing gems to RubyForge
12
21
  'mechanize' => '~> 0', # for automating web browsing
13
22
  'trollop' => '~> 1', # for parsing command-line options
23
+ 'erbook' => '~> 6', # for processing the user manual
14
24
  'launchy' => '~> 0', # for launching a web browser
15
25
  'yard' => nil, # for generating API documentation
16
26
  'addressable' => '~> 2', # for parsing URIs properly
17
27
  'minitest' => ['>= 1.3.1', '< 2'], # for unit testing
28
+ 'babelfish' => '~> 0', # for human language translation
18
29
  }
@@ -0,0 +1,84 @@
1
+ class << Inochi
2
+ ##
3
+ # Provides a common configuration for the project's user manual:
4
+ #
5
+ # * Assigns the title, subtitle, date, and authors for the document.
6
+ #
7
+ # You may override these assignments by reassigning these
8
+ # document parameters AFTER this method is invoked.
9
+ #
10
+ # Refer to the "document parameters" for the XHTML
11
+ # format in the "erbook" user manual for details.
12
+ #
13
+ # * Provides the project's configuration as global variables in the document.
14
+ #
15
+ # For example, <%= $version %> is the same as
16
+ # <%= project_module::VERSION %> in the document.
17
+ #
18
+ # * Defines a "project_summary" node for use in the document. The body
19
+ # of this node should contain a brief introduction to the project.
20
+ #
21
+ # * Defines a "project_history" node for use in the document. The body
22
+ # of this node should contain other nodes, each of which represent a
23
+ # single set of release notes for one of the project's releases.
24
+ #
25
+ # It is assumed that this method is called
26
+ # from within the Inochi.rake() environment.
27
+ #
28
+ # @param [Symbol] project_symbol
29
+ # Name of the Ruby constant which serves
30
+ # as a namespace for the entire project.
31
+ #
32
+ # @param [ERBook::Document::Template] book_template
33
+ # The eRuby template which serves as the documentation for the project.
34
+ #
35
+ def book project_symbol, book_template
36
+ project_module = fetch_project_module(project_symbol)
37
+
38
+ # provide project constants as global variables to the user manual
39
+ project_module::INOCHI.each_pair do |param, value|
40
+ eval "$#{param} = value", binding
41
+ end
42
+
43
+ # set document parameters for the user manual
44
+ $title = project_module::DISPLAY
45
+ $subtitle = project_module::TAGLINE
46
+ $feeds = { File.join(project_module::DOCSITE, 'ann.xml') => :rss }
47
+ $authors = Hash[
48
+ *project_module::AUTHORS.map do |name, addr|
49
+ # convert raw e-mail addresses into URLs for the erbook XHTML format
50
+ addr = "mailto:#{addr}" unless addr =~ /^\w+:/
51
+
52
+ [name, addr]
53
+ end.flatten
54
+ ]
55
+
56
+ book_template.extend Manual
57
+ end
58
+
59
+ module Manual
60
+ ##
61
+ # Defines a brief summary of this project.
62
+ #
63
+ def project_summary
64
+ raise ArgumentError, 'block must be given' unless block_given?
65
+
66
+ node do
67
+ $project_summary_node = @nodes.last
68
+ yield
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Contains release notes for all project releases.
74
+ #
75
+ def project_history
76
+ raise ArgumentError, 'block must be given' unless block_given?
77
+
78
+ node do
79
+ $project_history_node = @nodes.last
80
+ yield
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,239 @@
1
+ require 'yaml'
2
+
3
+ class << Inochi
4
+ ##
5
+ # Establishes your project in Ruby's runtime environment by defining
6
+ # the project module (which serves as a namespace for all code in the
7
+ # project) and providing a common configuration for the project module:
8
+ #
9
+ # * Adds the project lib/ directory to the Ruby load path.
10
+ #
11
+ # * Defines the INOCHI constant in the project module. This constant
12
+ # contains the effective configuration parameters (@see project_config).
13
+ #
14
+ # * Defines all configuration parameters as constants in the project module.
15
+ #
16
+ # This method must be invoked from immediately within (that is, not from
17
+ # within any of its descendant directories) the project lib/ directory.
18
+ # Ideally, this method would be invoked from the main project library.
19
+ #
20
+ # @param [Symbol] project_symbol
21
+ # Name of the Ruby constant which serves
22
+ # as a namespace for the entire project.
23
+ #
24
+ # @param [Hash] project_config
25
+ # Project configuration parameters:
26
+ #
27
+ # [String] :project =>
28
+ # Name of the project.
29
+ #
30
+ # The default value is the value of the project_symbol parameter.
31
+ #
32
+ # [String] :tagline =>
33
+ # An enticing, single line description of the project.
34
+ #
35
+ # The default value is an empty string.
36
+ #
37
+ # [String] :website =>
38
+ # URL of the published project website.
39
+ #
40
+ # The default value is an empty string.
41
+ #
42
+ # [String] :docsite =>
43
+ # URL of the published user manual.
44
+ #
45
+ # The default value is the same value as the :website parameter.
46
+ #
47
+ # [String] :program =>
48
+ # Name of the main project executable.
49
+ #
50
+ # The default value is the value of the :project parameter
51
+ # in lowercase and CamelCase converted into snake_case.
52
+ #
53
+ # [String] :version =>
54
+ # Version of the project.
55
+ #
56
+ # The default value is "0.0.0".
57
+ #
58
+ # [String] :release =>
59
+ # Date when this version was released.
60
+ #
61
+ # The default value is the current time.
62
+ #
63
+ # [String] :display =>
64
+ # How the project name should be displayed.
65
+ #
66
+ # The default value is the project name and version together.
67
+ #
68
+ # [String] :install =>
69
+ # Path to the directory which contains the project.
70
+ #
71
+ # The default value is one directory above the parent
72
+ # directory of the file from which this method was called.
73
+ #
74
+ # [Hash] :require =>
75
+ # The names and version constraints of ruby gems required by
76
+ # this project. This information must be expressed as follows:
77
+ #
78
+ # * Each hash key must be the name of a ruby gem.
79
+ #
80
+ # * Each hash value must be either +nil+, a single version number
81
+ # requirement string (see Gem::Requirement) or an Array thereof.
82
+ #
83
+ # The default value is an empty Hash.
84
+ #
85
+ # @return [Module] The newly configured project module.
86
+ #
87
+ def init project_symbol, project_config = {}
88
+ project_module = fetch_project_module(project_symbol)
89
+
90
+ # this method is not re-entrant
91
+ @already_seen ||= []
92
+ return project_module if @already_seen.include? project_module
93
+ @already_seen << project_module
94
+
95
+ # put project on Ruby load path
96
+ project_file = File.expand_path(first_caller_file)
97
+ project_libs = File.dirname(project_file)
98
+ $LOAD_PATH << project_libs unless $LOAD_PATH.include? project_libs
99
+
100
+ # supply configuration defaults
101
+ project_config[:project] ||= project_symbol.to_s
102
+ project_config[:tagline] ||= ''
103
+ project_config[:version] ||= '0.0.0'
104
+ project_config[:release] ||= Time.now.strftime('%F')
105
+ project_config[:website] ||= ''
106
+ project_config[:docsite] ||= project_config[:website]
107
+ project_config[:display] ||= "#{project_config[:project]} #{project_config[:version]}"
108
+ project_config[:program] ||= calc_program_name(project_symbol)
109
+ project_config[:install] ||= File.dirname(project_libs)
110
+ project_config[:require] ||= {}
111
+
112
+ # establish gem version dependencies and
113
+ # sanitize the values while we're at it
114
+ src = project_config[:require].dup
115
+ dst = project_config[:require].clear
116
+
117
+ src.each_pair do |gem_name, version_reqs|
118
+ gem_name = gem_name.to_s
119
+ version_reqs = [version_reqs].flatten.compact
120
+
121
+ dst[gem_name] = version_reqs
122
+ gem gem_name, *version_reqs
123
+ end
124
+
125
+ # make configuration parameters available as constants
126
+ project_config[:inochi] = project_config
127
+ project_config[:phrases] = Phrases.new project_config[:install]
128
+ project_config[:version].extend Version
129
+
130
+ project_config.each_pair do |param, value|
131
+ project_module.const_set param.to_s.upcase, value
132
+ end
133
+
134
+ project_module
135
+ end
136
+
137
+ module Version
138
+ # Returns the major number in this version.
139
+ def major
140
+ to_s[/^\d+/]
141
+ end
142
+
143
+ # Returns a string describing any version with the current major number.
144
+ def series
145
+ "#{major}.x.x"
146
+ end
147
+
148
+ # Returns a Gem::Requirement expression.
149
+ def requirement
150
+ "~> #{major}"
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Interface to translations of human text used in a project.
156
+ #
157
+ class Phrases
158
+ def initialize project_install_dir
159
+ # load language translations dynamically
160
+ lang_dir = File.join(project_install_dir, 'lang')
161
+
162
+ @phrases_by_language = Hash.new do |cache, language|
163
+ # store the phrase upon failure so that
164
+ # the phrases() method will know about it
165
+ phrases = Hash.new {|h,k| h[k] = nil }
166
+
167
+ lang_file = File.join(lang_dir, "#{language}.yaml")
168
+ lang_data = YAML.load_file(lang_file) rescue nil
169
+ phrases.merge! lang_data if lang_data
170
+
171
+ cache[language] = phrases
172
+ end
173
+
174
+ # detect user's preferred locale
175
+ self.locale = ENV['LC_ALL'] || ENV['LC_MESSAGES'] || ENV['LANG']
176
+ end
177
+
178
+ # The locale into which the #[] method will translate phrases.
179
+ attr_reader :locale
180
+
181
+ def locale= locale
182
+ @locale = locale.to_s
183
+
184
+ # extract the language portion of the locale
185
+ language = @locale[/^[[:alpha:]]+/].to_s
186
+ @language = language =~ /^(C|POSIX)?$/i ? :en : language.downcase.to_sym
187
+ end
188
+
189
+ ##
190
+ # Returns all phrases that underwent (or
191
+ # attempted) translation via this object.
192
+ #
193
+ def phrases
194
+ @phrases_by_language.values.map {|h| h.keys }.flatten.uniq.sort
195
+ end
196
+
197
+ ##
198
+ # Translates the given phrase into the target
199
+ # locale (see #locale and #locale=) and then
200
+ # substitutes the given placeholder arguments
201
+ # into the translation (see Kernel#sprintf).
202
+ #
203
+ # If a translation is not available for the given phrase,
204
+ # then the given phrase will be used as-is, untranslated.
205
+ #
206
+ def [] phrase, *words
207
+ translate @language, phrase, *words
208
+ end
209
+
210
+ ##
211
+ # Provides access to translations in any language, regardless
212
+ # of the target locale (see #locale and #locale=).
213
+ #
214
+ # For example, you can access Japanese translations via
215
+ # the #jp method even if the target locale is French.
216
+ #
217
+ def method_missing meth, *args
218
+ # ISO 639 language codes come in alpha-2 and alpha-3 forms
219
+ if meth.to_s =~ /^[a-z]{2,3}$/
220
+ translate meth, *args
221
+ else
222
+ super
223
+ end
224
+ end
225
+
226
+ private
227
+
228
+ ##
229
+ # Translates the given phrase into the given language and then substitutes
230
+ # the given placeholder arguments into the translation (see Kernel#sprintf).
231
+ #
232
+ # If the translation is not available, then
233
+ # the given string will be used instead.
234
+ #
235
+ def translate language, phrase, *words
236
+ (@phrases_by_language[language][phrase.to_s] || phrase).to_s % words
237
+ end
238
+ end
239
+ end