inochi 0.2.0 → 0.3.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.
@@ -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