motiro 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +280 -0
- data/README +28 -0
- data/README.en +175 -0
- data/README.pt-br +175 -0
- data/Rakefile +10 -0
- data/app/controllers/account_controller.rb +62 -0
- data/app/controllers/application.rb +72 -0
- data/app/controllers/edition_controller.rb +13 -0
- data/app/controllers/javascript_controller.rb +21 -0
- data/app/controllers/report_controller.rb +79 -0
- data/app/controllers/root_controller.rb +7 -0
- data/app/controllers/wiki_controller.rb +102 -0
- data/app/core/cache_reporter.rb +53 -0
- data/app/core/cache_reporter_fetcher.rb +33 -0
- data/app/core/chief_editor.rb +69 -0
- data/app/core/reporter.rb +105 -0
- data/app/core/reporter_driver.rb +36 -0
- data/app/core/reporter_fetcher.rb +39 -0
- data/app/core/settings.rb +43 -0
- data/app/core/version.rb +1 -0
- data/app/core/wiki_page_not_found.rb +33 -0
- data/app/core/wiki_reporter.rb +83 -0
- data/app/helpers/account_helper.rb +2 -0
- data/app/helpers/application_helper.rb +68 -0
- data/app/helpers/default_page_provider.rb +28 -0
- data/app/helpers/report_helper.rb +2 -0
- data/app/helpers/root_helper.rb +2 -0
- data/app/helpers/wiki_helper.rb +3 -0
- data/app/models/change.rb +96 -0
- data/app/models/diff_table_builder.rb +285 -0
- data/app/models/event.rb +18 -0
- data/app/models/headline.rb +109 -0
- data/app/models/page.rb +114 -0
- data/app/models/page_sweeper.rb +26 -0
- data/app/models/user.rb +85 -0
- data/app/ports/chdir_runner.rb +36 -0
- data/app/ports/reporter_loader.rb +9 -0
- data/app/ports/runner.rb +64 -0
- data/app/reporters/darcs_connection.rb +54 -0
- data/app/reporters/darcs_reporter.rb +104 -0
- data/app/reporters/darcs_settings.rb +9 -0
- data/app/reporters/darcs_temp_repo.rb +40 -0
- data/app/reporters/events_reporter.rb +28 -0
- data/app/reporters/features_reporter.rb +24 -0
- data/app/reporters/subversion_reporter.rb +203 -0
- data/app/reporters/svn_connection.rb +62 -0
- data/app/reporters/svn_settings.rb +40 -0
- data/app/views/account/_authorization.rhtml +54 -0
- data/app/views/account/_availability.rhtml +5 -0
- data/app/views/account/availability.rhtml +1 -0
- data/app/views/account/login.rhtml +22 -0
- data/app/views/account/logout.rhtml +10 -0
- data/app/views/javascript/motiro.rjs +132 -0
- data/app/views/javascript/niftycube.rjs +300 -0
- data/app/views/layouts/_bottom.rhtml +5 -0
- data/app/views/layouts/_header.rhtml +7 -0
- data/app/views/layouts/_top.rhtml +25 -0
- data/app/views/layouts/application.rhtml +3 -0
- data/app/views/layouts/scaffold.rhtml +13 -0
- data/app/views/layouts/wiki_edit.rhtml +26 -0
- data/app/views/report/list.rhtml +32 -0
- data/app/views/report/older.rhtml +26 -0
- data/app/views/report/rss.rxml +29 -0
- data/app/views/report/show.rhtml +20 -0
- data/app/views/root/index.rhtml +33 -0
- data/app/views/wiki/_editbar.rhtml +26 -0
- data/app/views/wiki/_properties_edit.rhtml +5 -0
- data/app/views/wiki/_properties_show.rhtml +5 -0
- data/app/views/wiki/edit.rhtml +56 -0
- data/app/views/wiki/properties_edit.rhtml +1 -0
- data/app/views/wiki/show.rhtml +9 -0
- data/bin/motiro +44 -0
- data/config/boot.rb +19 -0
- data/config/database.yml +14 -0
- data/config/environment.rb +64 -0
- data/config/environments/development.rb +20 -0
- data/config/environments/production.rb +19 -0
- data/config/environments/test.rb +19 -0
- data/config/motiro.yml +43 -0
- data/config/routes.rb +79 -0
- data/db/migrate/005_globalize_migration.rb +11363 -0
- data/db/migrate/006_remove_headline_title.rb +14 -0
- data/db/migrate/007_stretch_rid.rb +11 -0
- data/db/migrate/008_add_page_editors.rb +12 -0
- data/db/migrate/009_add_page_original_author.rb +12 -0
- data/db/migrate/010_remove_empty_string_defaults_from_pages.rb +17 -0
- data/db/migrate/011_add_title_and_kind_to_pages.rb +13 -0
- data/db/migrate/012_page_modification_info.rb +13 -0
- data/db/migrate/013_nullify_initial_page_attributes.rb +21 -0
- data/db/migrate/014_events_are_wiki_pages.rb +13 -0
- data/db/migrate/015_migrate_previous_event_data.rb +23 -0
- data/db/migrate/1_initial_structure.rb +36 -0
- data/db/migrate/2_add_authentication.rb +12 -0
- data/db/migrate/3_drop_articles.rb +26 -0
- data/db/migrate/4_add_page_editing.rb +14 -0
- data/db/motirodb.sqlite.initial +0 -0
- data/db/schema_version +1 -0
- data/db/translation/pt-BR.rb +76 -0
- data/doc/README_FOR_APP +2 -0
- data/installer/rails_installer_defaults.yml +5 -0
- data/lib/import_translations.rb +154 -0
- data/lib/login_system.rb +89 -0
- data/lib/relative_time.rb +48 -0
- data/lib/string_extensions.rb +10 -0
- data/lib/stub_hash.rb +22 -0
- data/lib/tasks/packaging.rake +93 -0
- data/lib/tasks/testing.rake +32 -0
- data/lib/tick_daemon.rb +41 -0
- data/lib/translator.rb +67 -0
- data/lib/wiki_renderer.rb +42 -0
- data/lib/wiki_url_generator.rb +29 -0
- data/log/.keepdir +0 -0
- data/public/404.html +8 -0
- data/public/500.html +8 -0
- data/public/dispatch.cgi +10 -0
- data/public/dispatch.fcgi +24 -0
- data/public/dispatch.rb +10 -0
- data/public/favicon.ico +0 -0
- data/public/images/calendar.png +0 -0
- data/public/images/rss.png +0 -0
- data/public/javascripts/controls.js +750 -0
- data/public/javascripts/dragdrop.js +584 -0
- data/public/javascripts/effects.js +854 -0
- data/public/javascripts/prototype.js +1785 -0
- data/public/robots.txt +1 -0
- data/public/stylesheets/motiro.css +269 -0
- data/public/stylesheets/niftyCorners.css +35 -0
- data/public/stylesheets/scaffold.css +74 -0
- data/script/about +3 -0
- data/script/breakpointer +3 -0
- data/script/console +3 -0
- data/script/destroy +3 -0
- data/script/generate +3 -0
- data/script/performance/benchmarker +3 -0
- data/script/performance/profiler +3 -0
- data/script/plugin +3 -0
- data/script/process/reaper +3 -0
- data/script/process/spawner +3 -0
- data/script/process/spinner +3 -0
- data/script/runner +3 -0
- data/script/server +3 -0
- data/script/ticker +29 -0
- data/test/acceptance/account_test.rb +186 -0
- data/test/acceptance/darcs_test.rb +62 -0
- data/test/acceptance/events_test.rb +47 -0
- data/test/acceptance/main_page_test.rb +92 -0
- data/test/acceptance/subversion_test.rb +319 -0
- data/test/acceptance/ts_all_suites.rb +27 -0
- data/test/acceptance/wiki_test.rb +202 -0
- data/test/contract/darcs_test.rb +51 -0
- data/test/contract/remote_darcs_test.rb +61 -0
- data/test/contract/svn_test.rb +53 -0
- data/test/fixtures/changes.yml +25 -0
- data/test/fixtures/headlines.yml +45 -0
- data/test/fixtures/pages.yml +98 -0
- data/test/fixtures/users.yml +31 -0
- data/test/functional/account_controller_test.rb +96 -0
- data/test/functional/report_controller_test.rb +113 -0
- data/test/functional/report_features_test.rb +38 -0
- data/test/functional/report_subversion_test.rb +79 -0
- data/test/functional/root_controller_test.rb +127 -0
- data/test/functional/wiki_controller_test.rb +280 -0
- data/test/lib/acceptance_test_case.rb +43 -0
- data/test/lib/configuration.rb +53 -0
- data/test/lib/darcs_excerpts.rb +181 -0
- data/test/lib/darcs_repo.rb +77 -0
- data/test/lib/live_mode_test.rb +51 -0
- data/test/lib/local_svn.rb +157 -0
- data/test/lib/netutils.rb +42 -0
- data/test/lib/platform_thread.rb +62 -0
- data/test/lib/repoutils.rb +23 -0
- data/test/lib/selenium_extensions.rb +32 -0
- data/test/lib/stubio.rb +37 -0
- data/test/lib/svn_excerpts.rb +288 -0
- data/test/lib/test_configuration.rb +14 -0
- data/test/lib/webserver.rb +71 -0
- data/test/meta/configuration_test.rb +72 -0
- data/test/meta/darcs_repo_test.rb +118 -0
- data/test/meta/local_svn_test.rb +125 -0
- data/test/meta/platform_thread_test.rb +46 -0
- data/test/meta/stubio_test.rb +44 -0
- data/test/mocks/headline.rb +34 -0
- data/test/mocks/svn_reporter.rb +29 -0
- data/test/stubs/svn_settings.rb +19 -0
- data/test/stubs/url_generator.rb +24 -0
- data/test/test_helper.rb +36 -0
- data/test/unit/cache_reporter_fetcher_test.rb +46 -0
- data/test/unit/cache_reporter_test.rb +97 -0
- data/test/unit/caching_test.rb +78 -0
- data/test/unit/change_test.rb +152 -0
- data/test/unit/chdir_runner_test.rb +77 -0
- data/test/unit/chief_editor_test.rb +234 -0
- data/test/unit/darcs_connection_test.rb +109 -0
- data/test/unit/darcs_reporter_test.rb +146 -0
- data/test/unit/darcs_settings_test.rb +37 -0
- data/test/unit/darcs_temp_repo_test.rb +51 -0
- data/test/unit/default_page_provider_test.rb +46 -0
- data/test/unit/diff_table_builder_test.rb +602 -0
- data/test/unit/headline_test.rb +259 -0
- data/test/unit/page_test.rb +145 -0
- data/test/unit/relative_time_test.rb +56 -0
- data/test/unit/reporter_driver_test.rb +85 -0
- data/test/unit/reporter_fetcher_test.rb +31 -0
- data/test/unit/reporter_test.rb +81 -0
- data/test/unit/runner_test.rb +93 -0
- data/test/unit/settings_test.rb +55 -0
- data/test/unit/string_extensions_test.rb +10 -0
- data/test/unit/svn_connection_test.rb +183 -0
- data/test/unit/svn_reporter_interaction_test.rb +38 -0
- data/test/unit/svn_reporter_test.rb +286 -0
- data/test/unit/svn_settings_test.rb +86 -0
- data/test/unit/translator_test.rb +96 -0
- data/test/unit/user_test.rb +125 -0
- data/test/unit/wiki_renderer_test.rb +87 -0
- data/test/unit/wiki_reporter_test.rb +94 -0
- data/test/unit/wiki_url_generator_test.rb +31 -0
- data/vendor/motiro-installer.rb +159 -0
- data/vendor/plugins/globalize/LICENSE +9 -0
- data/vendor/plugins/globalize/README +49 -0
- data/vendor/plugins/globalize/data/country_data.csv +240 -0
- data/vendor/plugins/globalize/data/language_data.csv +188 -0
- data/vendor/plugins/globalize/data/translation_data.csv +3421 -0
- data/vendor/plugins/globalize/generators/globalize/USAGE +10 -0
- data/vendor/plugins/globalize/generators/globalize/globalize_generator.rb +42 -0
- data/vendor/plugins/globalize/generators/globalize/templates/migration.rb.gz +0 -0
- data/vendor/plugins/globalize/generators/globalize/templates/tiny_migration.rb.gz +0 -0
- data/vendor/plugins/globalize/init.rb +30 -0
- data/vendor/plugins/globalize/lib/globalize/localization/core_ext.rb +170 -0
- data/vendor/plugins/globalize/lib/globalize/localization/core_ext_hooks.rb +33 -0
- data/vendor/plugins/globalize/lib/globalize/localization/db_translate.rb +494 -0
- data/vendor/plugins/globalize/lib/globalize/localization/db_view_translator.rb +152 -0
- data/vendor/plugins/globalize/lib/globalize/localization/locale.rb +173 -0
- data/vendor/plugins/globalize/lib/globalize/localization/rfc_3066.rb +40 -0
- data/vendor/plugins/globalize/lib/globalize/models/country.rb +24 -0
- data/vendor/plugins/globalize/lib/globalize/models/currency.rb +188 -0
- data/vendor/plugins/globalize/lib/globalize/models/language.rb +84 -0
- data/vendor/plugins/globalize/lib/globalize/models/model_translation.rb +4 -0
- data/vendor/plugins/globalize/lib/globalize/models/translation.rb +9 -0
- data/vendor/plugins/globalize/lib/globalize/models/view_translation.rb +14 -0
- data/vendor/plugins/globalize/lib/globalize/rails/action_mailer.rb +125 -0
- data/vendor/plugins/globalize/lib/globalize/rails/action_view.rb +79 -0
- data/vendor/plugins/globalize/lib/globalize/rails/active_record.rb +129 -0
- data/vendor/plugins/globalize/lib/globalize/rails/active_record_helper.rb +33 -0
- data/vendor/plugins/globalize/populators/pop_dates.rb +81 -0
- data/vendor/plugins/globalize/populators/pop_migration.rb +18 -0
- data/vendor/plugins/globalize/populators/pop_pluralization.rb +26 -0
- data/vendor/plugins/globalize/populators/pop_seps.rb +32 -0
- data/vendor/plugins/globalize/tasks/data.rake +130 -0
- data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.en-US.plain.text.rhtml +1 -0
- data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.en.plain.text.rhtml +1 -0
- data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.he.plain.text.rhtml +1 -0
- data/vendor/plugins/globalize/test/action_mailer_test/globalize_mailer/test.plain.text.rhtml +1 -0
- data/vendor/plugins/globalize/test/action_mailer_test.rb +54 -0
- data/vendor/plugins/globalize/test/config/database.yml.default +16 -0
- data/vendor/plugins/globalize/test/config/database.yml.example +22 -0
- data/vendor/plugins/globalize/test/core_ext_test.rb +61 -0
- data/vendor/plugins/globalize/test/currency_test.rb +141 -0
- data/vendor/plugins/globalize/test/date_helper_test.rb +634 -0
- data/vendor/plugins/globalize/test/db/schema.rb +90 -0
- data/vendor/plugins/globalize/test/db_translation_test.rb +374 -0
- data/vendor/plugins/globalize/test/fixtures/globalize_categories.yml +7 -0
- data/vendor/plugins/globalize/test/fixtures/globalize_categories_products.yml +7 -0
- data/vendor/plugins/globalize/test/fixtures/globalize_countries.yml +41 -0
- data/vendor/plugins/globalize/test/fixtures/globalize_languages.yml +64 -0
- data/vendor/plugins/globalize/test/fixtures/globalize_manufacturers.yml +5 -0
- data/vendor/plugins/globalize/test/fixtures/globalize_products.yml +29 -0
- data/vendor/plugins/globalize/test/fixtures/globalize_simples.yml +5 -0
- data/vendor/plugins/globalize/test/fixtures/globalize_translations.yml +354 -0
- data/vendor/plugins/globalize/test/locale_test.rb +27 -0
- data/vendor/plugins/globalize/test/mime_responds_test.rb +358 -0
- data/vendor/plugins/globalize/test/model_test.rb +17 -0
- data/vendor/plugins/globalize/test/standard_data_test_helper.rb +33 -0
- data/vendor/plugins/globalize/test/test_helper.rb +19 -0
- data/vendor/plugins/globalize/test/test_standard_data.rb +54 -0
- data/vendor/plugins/globalize/test/validation_test.rb +29 -0
- data/vendor/plugins/globalize/test/view_picking_test.rb +49 -0
- data/vendor/plugins/globalize/test/view_translation_test.rb +237 -0
- data/vendor/plugins/globalize/test/views/layouts/standard.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/all_types_with_layout.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/all_types_with_layout.rjs +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rjs +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.en.rxml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rjs +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.fr.rxml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rjs +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults.rxml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rjs +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.en.rxml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rjs +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.fr.rxml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rjs +1 -0
- data/vendor/plugins/globalize/test/views/respond_to/using_defaults_with_type_list.rxml +1 -0
- data/vendor/plugins/globalize/test/views/test.he-IL.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/test.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/test2.he.rhtml +1 -0
- data/vendor/plugins/globalize/test/views/test2.rhtml +1 -0
- data/vendor/plugins/test_xml/MIT-LICENSE +20 -0
- data/vendor/plugins/test_xml/README +20 -0
- data/vendor/plugins/test_xml/Rakefile +22 -0
- data/vendor/plugins/test_xml/init.rb +7 -0
- data/vendor/plugins/test_xml/lib/xml_assertions.rb +22 -0
- data/vendor/plugins/test_xml/test/test_xml_test.rb +40 -0
- metadata +505 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
module Globalize
|
2
|
+
|
3
|
+
class NoBaseLanguageError < StandardError; end
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
Locale defines the currenctly active _locale_. You'll mostly use it like this:
|
7
|
+
Locale.set("en-US")
|
8
|
+
|
9
|
+
+en+ is the code for English, and +US+ is the country code. The country code is
|
10
|
+
optional, but you'll need to define it to get a lot of the localization features.
|
11
|
+
=end
|
12
|
+
class Locale
|
13
|
+
attr_reader :language, :country, :code
|
14
|
+
attr_accessor :date_format, :currency_format, :currency_code,
|
15
|
+
:thousands_sep, :decimal_sep, :currency_decimal_sep,
|
16
|
+
:number_grouping_scheme
|
17
|
+
|
18
|
+
@@cache = {}
|
19
|
+
@@translator_class = DbViewTranslator
|
20
|
+
@@translator = {}
|
21
|
+
@@active = nil
|
22
|
+
@@base_language = nil
|
23
|
+
@@base_language_code = nil
|
24
|
+
@@translator = @@translator_class.instance
|
25
|
+
|
26
|
+
# Is there an active locale?
|
27
|
+
def self.active?; !@@active.nil? end
|
28
|
+
|
29
|
+
# This is the focal point of the class. Sets the locale in the familiar
|
30
|
+
# RFC 3066 format (see: http://www.faqs.org/rfcs/rfc3066.html). It can
|
31
|
+
# also take a Locale object. Set it to the +nil+ object, to deactivate
|
32
|
+
# the locale.
|
33
|
+
def self.set(locale)
|
34
|
+
if locale.kind_of? Locale
|
35
|
+
@@active = locale
|
36
|
+
elsif locale.nil?
|
37
|
+
@@active = nil
|
38
|
+
else
|
39
|
+
@@active = ( @@cache[locale] ||= Locale.new(locale) )
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Clears the locale cache -- used mostly for testing.
|
44
|
+
def self.clear_cache
|
45
|
+
@@cache.clear
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the active locale.
|
49
|
+
def self.active; @@active end
|
50
|
+
|
51
|
+
# Sets the base language. The base language is the language that has
|
52
|
+
# complete coverage in the database. For instance, if you have a +Category+
|
53
|
+
# model with a +name+ field, the base language is the language in which names
|
54
|
+
# are stored in the model itself, and not in the translations table.
|
55
|
+
#
|
56
|
+
# Takes either a language code (valid RFC 3066 code like +en+ or <tt>en-US</tt>)
|
57
|
+
# or a language object.
|
58
|
+
#
|
59
|
+
# May be set with a language code in environment.rb, without accessing the db.
|
60
|
+
def self.set_base_language(lang)
|
61
|
+
if lang.kind_of? Language
|
62
|
+
@@base_language = lang
|
63
|
+
else
|
64
|
+
@@base_language_code = RFC_3066.parse lang
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the base language. Raises an exception if none is set.
|
69
|
+
def self.base_language
|
70
|
+
@@base_language ? @@base_language :
|
71
|
+
(@@base_language_code ?
|
72
|
+
(@@base_language = Language.pick(@@base_language_code)) :
|
73
|
+
raise(NoBaseLanguageError, "base language must be defined"))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Is the currently active language the base language?
|
77
|
+
def self.base?
|
78
|
+
active ? active.language == base_language : true
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the currently active language model or +nil+.
|
82
|
+
def self.language
|
83
|
+
active? ? active.language : nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the currently active language code or +nil+.
|
87
|
+
def self.language_code
|
88
|
+
active? ? language.code : nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the currently active country model or +nil+.
|
92
|
+
def self.country
|
93
|
+
active? ? active.country : nil
|
94
|
+
end
|
95
|
+
|
96
|
+
# Creates a new locale object by looking up an RFC 3066 code in the database.
|
97
|
+
def initialize(code)
|
98
|
+
if code.nil?
|
99
|
+
return
|
100
|
+
end
|
101
|
+
|
102
|
+
rfc = RFC_3066.parse(code)
|
103
|
+
@code = rfc.locale
|
104
|
+
|
105
|
+
@language = Language.pick(rfc)
|
106
|
+
@country = Country.pick(rfc)
|
107
|
+
|
108
|
+
setup_fields
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sets the translation for +key+.
|
112
|
+
#
|
113
|
+
# :call-seq:
|
114
|
+
# Locale.set_translation(key, language, *translations)
|
115
|
+
# Locale.set_translation(key, *translations)
|
116
|
+
#
|
117
|
+
# If +language+ is given, define a translation using that language
|
118
|
+
# model, otherwise use the active language.
|
119
|
+
#
|
120
|
+
# Multiple translation strings may be given, in order to define plural forms.
|
121
|
+
# In English, there are only two plural forms, singular and plural, so you
|
122
|
+
# would provide two strings at the most. The order is determined by the
|
123
|
+
# formula in the languages database. For English, the order is: singular form,
|
124
|
+
# then plural.
|
125
|
+
#
|
126
|
+
# Example:
|
127
|
+
# Locale.set_translation("There are %d items in your cart",
|
128
|
+
# "There is one item in your cart", "There are %d items in your cart")
|
129
|
+
def self.set_translation(key, *options)
|
130
|
+
key = key.to_s.gsub('_', ' ') if key.kind_of? Symbol
|
131
|
+
if options.first.kind_of? Language
|
132
|
+
language = options.shift
|
133
|
+
else
|
134
|
+
language = self.language
|
135
|
+
end
|
136
|
+
|
137
|
+
raise ArgumentError, "No translations given" if options.empty?
|
138
|
+
translator.set(key, language, *options)
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.set_pluralized_translation(key, *options)
|
142
|
+
key = key.to_s.gsub('_', ' ') if key.kind_of? Symbol
|
143
|
+
if options.first.kind_of? Language
|
144
|
+
language = options.shift
|
145
|
+
else
|
146
|
+
language = self.language
|
147
|
+
end
|
148
|
+
|
149
|
+
raise ArgumentError, "No translations given" if options.empty?
|
150
|
+
translator.set_pluralized(key, language, *options)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.translate(key, default = nil, arg = nil) # :nodoc:
|
154
|
+
key = key.to_s.gsub('_', ' ') if key.kind_of? Symbol
|
155
|
+
|
156
|
+
translator.fetch(key, self.language, default, arg)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns the translator object -- mostly for testing and adjusting the cache.
|
160
|
+
def self.translator; @@translator end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def setup_fields
|
165
|
+
return if !@country
|
166
|
+
|
167
|
+
[:date_format, :currency_format, :currency_code, :thousands_sep,
|
168
|
+
:decimal_sep, :currency_decimal_sep, :number_grouping_scheme
|
169
|
+
].each {|f| instance_variable_set "@#{f}", @country.send(f) }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Globalize
|
2
|
+
class RFC_3066 # :nodoc:
|
3
|
+
attr_accessor :country, :language, :locale
|
4
|
+
def self.parse(locale)
|
5
|
+
# check for validity
|
6
|
+
raise ArgumentError, "bad format for #{locale}, not rfc-3066 compliant" if
|
7
|
+
locale !~ /^[a-zA-Z0-9\-]+$/
|
8
|
+
|
9
|
+
# split/assign
|
10
|
+
lang, country, remain = locale.split('-', 3)
|
11
|
+
raise ArgumentError, "bad format for #{locale}, not rfc-3066 compliant" if
|
12
|
+
lang !~ /^[a-zA-Z]{1,8}$/
|
13
|
+
new_language = lang if lang.size == 2 || lang.size == 3
|
14
|
+
raise ArgumentError, "bad format for #{locale}, not rfc-3066 compliant" if
|
15
|
+
!new_language && lang != 'i' && lang != 'x'
|
16
|
+
|
17
|
+
raise ArgumentError, "bad format for #{locale}, not rfc-3066 compliant" if
|
18
|
+
country && (country.size < 2 || country.size > 8)
|
19
|
+
new_country = country if country && country.size == 2
|
20
|
+
|
21
|
+
rfc = self.new
|
22
|
+
rfc.country = new_country
|
23
|
+
rfc.language = new_language
|
24
|
+
rfc.locale = locale
|
25
|
+
|
26
|
+
return rfc
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.valid?(locale)
|
30
|
+
begin
|
31
|
+
parse(locale)
|
32
|
+
rescue ArgumentError
|
33
|
+
return false
|
34
|
+
else
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Globalize
|
2
|
+
class Country < ActiveRecord::Base # :nodoc:
|
3
|
+
set_table_name "globalize_countries"
|
4
|
+
|
5
|
+
def self.reloadable?; false end
|
6
|
+
|
7
|
+
def self.pick(rfc)
|
8
|
+
|
9
|
+
# assume it's a country code string
|
10
|
+
if rfc.kind_of?(String)
|
11
|
+
find_by_code(rfc)
|
12
|
+
elsif rfc.kind_of?(RFC_3066)
|
13
|
+
rfc.country ? find_by_code(rfc.country) : nil
|
14
|
+
else
|
15
|
+
raise ArgumentError, "argument must be String or RFC_3066 object"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def number_grouping_scheme
|
20
|
+
attr = read_attribute(:number_grouping_scheme)
|
21
|
+
attr ? attr.intern : nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Globalize
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
This is what you use for representing money in your ActiveRecord models.
|
5
|
+
It stores values as integers internally and in the database, to safeguard
|
6
|
+
precision and rounding.
|
7
|
+
|
8
|
+
More importantly for globalization freaks, it prints out the amount correctly
|
9
|
+
in the current locale, via the handy #format method. Try it!
|
10
|
+
|
11
|
+
Example:
|
12
|
+
|
13
|
+
class Product < ActiveRecord::Base
|
14
|
+
composed_of :price, :class_name => "Globalize::Currency",
|
15
|
+
:mapping => [ %w(price cents) ]
|
16
|
+
end
|
17
|
+
|
18
|
+
product.price -> "SFr. 483'232.43"
|
19
|
+
=end
|
20
|
+
class Currency
|
21
|
+
include Comparable
|
22
|
+
|
23
|
+
attr_reader :cents
|
24
|
+
|
25
|
+
class CurrencyError < StandardError# :nodoc:
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates a new Currency object, given cents. This is used to tie into
|
29
|
+
# the ActiveRecord aggregation stuff.
|
30
|
+
def initialize(cents)
|
31
|
+
@cents = cents.nil? ? nil : cents.to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
@@no_cents = false
|
35
|
+
def self.no_cents; @@no_cents end
|
36
|
+
def self.no_cents=(val); @@no_cents = val end
|
37
|
+
|
38
|
+
@@free = Currency.new(0)
|
39
|
+
@@free.freeze
|
40
|
+
|
41
|
+
# Returns the Currency object representing a price of 0.
|
42
|
+
def self.free
|
43
|
+
@@free
|
44
|
+
end
|
45
|
+
|
46
|
+
@@na = Currency.new(nil)
|
47
|
+
@@na.freeze
|
48
|
+
|
49
|
+
# Returns the Currency object representing an unknown price.
|
50
|
+
def self.na
|
51
|
+
@@na
|
52
|
+
end
|
53
|
+
|
54
|
+
def <=>(other)
|
55
|
+
if other.respond_to? :cents
|
56
|
+
if na?
|
57
|
+
other.na? ? 0 : 1
|
58
|
+
else
|
59
|
+
other.na? ? -1 : cents <=> other.cents
|
60
|
+
end
|
61
|
+
elsif other.kind_of? Integer
|
62
|
+
na? ? 1 : cents <=> other
|
63
|
+
else
|
64
|
+
raise "can only compare with money or integer"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def +(other_money)
|
69
|
+
raise TypeError, "parameter to Currency#+ must be Currency object" unless
|
70
|
+
other_money.kind_of? Currency
|
71
|
+
(na? || other_money.na?) ? Currency.na :
|
72
|
+
Currency.new(cents + other_money.cents)
|
73
|
+
end
|
74
|
+
|
75
|
+
def -(other_money)
|
76
|
+
raise TypeError, "parameter to Currency#+ must be Currency object" unless
|
77
|
+
other_money.kind_of? Currency
|
78
|
+
(na? || other_money.na?) ? Currency.na :
|
79
|
+
Currency.new(cents - other_money.cents)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Multiply money by amount
|
83
|
+
def *(amount)
|
84
|
+
return Currency.new(nil) if na?
|
85
|
+
new_cents = amount * cents;
|
86
|
+
new_cents = new_cents.round if new_cents.respond_to? :round
|
87
|
+
Currency.new(new_cents)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Divide money by amount
|
91
|
+
def /(amount)
|
92
|
+
return Currency.new(nil) if na?
|
93
|
+
new_cents = cents / amount;
|
94
|
+
new_cents = new_cents.round if new_cents.respond_to? :round
|
95
|
+
Currency.new(new_cents)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the formatted version of the amount in local currency.
|
99
|
+
# If <tt>:code => true</tt> is specified, format using international
|
100
|
+
# 3-letter currency code. If <tt>:country</tt> is specified as well,
|
101
|
+
# use that country's currency code for formatting.
|
102
|
+
def format(options = {})
|
103
|
+
return :no_price_available.t("call for price") if na?
|
104
|
+
|
105
|
+
force_cents = options[:force_cents]
|
106
|
+
if options[:code]
|
107
|
+
currency_code = options[:country] ?
|
108
|
+
options[:country].currency_code :
|
109
|
+
( Locale.active? ? Locale.active.currency_code : nil )
|
110
|
+
currency_code ?
|
111
|
+
self.amount(false, force_cents) + " " + currency_code :
|
112
|
+
self.amount(false, force_cents)
|
113
|
+
else
|
114
|
+
if Locale.active?
|
115
|
+
fmt = Locale.active.currency_format || '$%n'
|
116
|
+
fmt.sub('%n', self.amount(false, force_cents))
|
117
|
+
else
|
118
|
+
self.amount false, force_cents
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns the formatted version of the amount, but without the currency symbol.
|
124
|
+
# If +unlocalized+ is true, do not format the number according to the current locale,
|
125
|
+
# so <tt>Currency.new(1234567) -> 12345.67</tt>. This is useful for sending the
|
126
|
+
# amount to payment gateways.
|
127
|
+
def amount(unlocalized = false, force_cents = false)
|
128
|
+
return nil if na?
|
129
|
+
decimal_sep = unlocalized ?
|
130
|
+
'.' :
|
131
|
+
(Locale.active? ?
|
132
|
+
(Locale.active.currency_decimal_sep || Locale.active.decimal_sep || '.') :
|
133
|
+
'.')
|
134
|
+
dollar_str = unlocalized ? dollar_part.to_s : dollar_part.localize
|
135
|
+
result = dollar_str
|
136
|
+
result << decimal_sep + sprintf("%02d", cent_part) unless
|
137
|
+
self.class.no_cents && !force_cents
|
138
|
+
return result
|
139
|
+
end
|
140
|
+
|
141
|
+
# Same as #format with no arguments.
|
142
|
+
def to_s
|
143
|
+
self.format
|
144
|
+
end
|
145
|
+
|
146
|
+
# Parse a string or number into a currency object. Easier to use than #new.
|
147
|
+
def self.parse(num)
|
148
|
+
case
|
149
|
+
when num.is_a?(String)
|
150
|
+
raise ArgumentError, "Not an amount (#{num})" if num.delete("^0-9").empty?
|
151
|
+
_dollars, _cents = num.delete("^0-9.").split('.', 2)
|
152
|
+
_cents = _cents ? _cents[0,2] : 0
|
153
|
+
Currency.new(_dollars.to_i * 100 + _cents.to_i)
|
154
|
+
when num.is_a?(Numeric)
|
155
|
+
Currency.new(num * 100)
|
156
|
+
when num.is_a?(NilClass)
|
157
|
+
Currency.na
|
158
|
+
else
|
159
|
+
raise ArgumentError, "Unrecognized object #{num.class.name} for Currency"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Conversion to self
|
164
|
+
def to_currency
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
# Is the value 0? Is it free?
|
169
|
+
def empty?
|
170
|
+
cents == 0
|
171
|
+
end
|
172
|
+
|
173
|
+
# Is the value unknown?
|
174
|
+
def na?
|
175
|
+
cents.nil?
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def dollar_part
|
180
|
+
na? ? nil : cents / 100
|
181
|
+
end
|
182
|
+
|
183
|
+
def cent_part
|
184
|
+
na? ? nil : cents % 100
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Globalize
|
2
|
+
class Language < ActiveRecord::Base # :nodoc:
|
3
|
+
set_table_name "globalize_languages"
|
4
|
+
|
5
|
+
validates_presence_of :english_name
|
6
|
+
|
7
|
+
validates_uniqueness_of :iso_639_1, :if => :iso_639_1
|
8
|
+
validates_uniqueness_of :iso_639_2, :if => :iso_639_2
|
9
|
+
validates_uniqueness_of :iso_639_3, :if => :iso_639_3
|
10
|
+
validates_uniqueness_of :rfc_3066, :if => :rfc_3066
|
11
|
+
|
12
|
+
validates_length_of :pluralization, :maximum => 200, :if => :pluralization
|
13
|
+
validates_format_of :pluralization, :with => /^[c=\d?:%!<>&|() ]+$/, :if => :pluralization,
|
14
|
+
:message => " has invalid characters. Allowed characters are: " +
|
15
|
+
"'c', '=', 0-9, '?', ':', '%', '!', '<', '>', '&', '|', '(', ')', ' '."
|
16
|
+
|
17
|
+
def self.reloadable?; false end
|
18
|
+
|
19
|
+
def after_initialize
|
20
|
+
if !pluralization.nil? && pluralization.size > 200
|
21
|
+
raise SecurityError, "Pluralization field for #{self.english_name} language " +
|
22
|
+
"contains potentially harmful code. " +
|
23
|
+
"Must be less than 200 characters in length. Was #{pluralization.size} characters."
|
24
|
+
end
|
25
|
+
|
26
|
+
if !pluralization.nil? && pluralization !~ /^[c=\d?:%!<>&|() ]+$/
|
27
|
+
raise SecurityError, "Pluralization field ('#{pluralization}') for #{self.english_name} language " +
|
28
|
+
"contains potentially harmful code. " +
|
29
|
+
"Must only use the characters: 'c', '=', 0-9, '?', ':', " +
|
30
|
+
"'%', '!', '<', '>', '&', '|', '(', ')', ' '."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.pick(rfc)
|
35
|
+
if rfc.kind_of? String then rfc = RFC_3066.parse(rfc) end
|
36
|
+
if rfc.locale.include? '-'
|
37
|
+
lang = find_by_rfc_3066(rfc.locale)
|
38
|
+
return lang if lang
|
39
|
+
end
|
40
|
+
|
41
|
+
code = rfc.language
|
42
|
+
if code.size == 2
|
43
|
+
lang = find_by_iso_639_1(code)
|
44
|
+
elsif code.size == 3
|
45
|
+
lang = find_by_iso_639_3(code)
|
46
|
+
end
|
47
|
+
|
48
|
+
lang
|
49
|
+
end
|
50
|
+
|
51
|
+
def code; iso_639_1 || iso_639_3 || rfc_3066; end
|
52
|
+
|
53
|
+
def code=(new_code)
|
54
|
+
if new_code =~ /-/
|
55
|
+
self.rfc_3066 = new_code
|
56
|
+
else
|
57
|
+
raise ArgumentError,
|
58
|
+
"code must be in rfc_3066 format, with a hyphen character; was #{new_code}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def native_name; self['native_name'] || self['english_name'] end
|
63
|
+
|
64
|
+
def ==(other)
|
65
|
+
return false if !other.kind_of? Language
|
66
|
+
self.code == other.code
|
67
|
+
end
|
68
|
+
|
69
|
+
def plural_index(num)
|
70
|
+
|
71
|
+
# number is not defined, so we assume no pluralization
|
72
|
+
return 1 if num.nil?
|
73
|
+
|
74
|
+
c = num
|
75
|
+
expr = pluralization || 'c == 1 ? 2 : 1'
|
76
|
+
|
77
|
+
instance_eval(expr)
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s; english_name end
|
81
|
+
def inspect; english_name end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Globalize
|
2
|
+
# Represents a view translation in the DB, which is a translation of a string
|
3
|
+
# in the rails application itself. This includes error messages, controllers,
|
4
|
+
# views, and flashes, but not database content.
|
5
|
+
class ViewTranslation < Translation # :nodoc:
|
6
|
+
|
7
|
+
def self.pick(key, language, idx)
|
8
|
+
find(:first, :conditions => [
|
9
|
+
'tr_key = ? AND language_id = ? AND pluralization_index = ?',
|
10
|
+
key, language.id, idx ])
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# Search for translated templates or fall back to the default one
|
2
|
+
module ActionMailer # :nodoc:
|
3
|
+
|
4
|
+
# Globalize overrides the create! method to support multiple templates
|
5
|
+
# for different locales. For example, for English it will select the template:
|
6
|
+
# signup_notification.en-US.text.html.rhtml
|
7
|
+
#
|
8
|
+
# It will look for the currently active locale code (en-US) first,
|
9
|
+
# then the language code (en).
|
10
|
+
#
|
11
|
+
# If neither of those are found, it will use the regular name:
|
12
|
+
# signup_notification.text.html.rhtml
|
13
|
+
#
|
14
|
+
# It is fully backwards compatible with the original Rails version.
|
15
|
+
class Base
|
16
|
+
|
17
|
+
# Initialize the mailer via the given +method_name+. The body will be
|
18
|
+
# rendered and a new TMail::Mail object created.
|
19
|
+
#
|
20
|
+
# This method is overriden by Globalize to support multiple templates
|
21
|
+
# for different locales. For example, use:
|
22
|
+
# signup_notification.en-US.text.html.rhtml
|
23
|
+
# It is fully backwards compatible with the original rails version.
|
24
|
+
def create!(method_name, *parameters) #:nodoc:
|
25
|
+
initialize_defaults(method_name)
|
26
|
+
send(method_name, *parameters)
|
27
|
+
|
28
|
+
# If an explicit, textual body has not been set, we check assumptions.
|
29
|
+
unless String === @body
|
30
|
+
# First, we look to see if there are any likely templates that match,
|
31
|
+
# which include the content-type in their file name (i.e.,
|
32
|
+
# "the_template_file.text.html.rhtml", etc.). Only do this if parts
|
33
|
+
# have not already been specified manually.
|
34
|
+
if @parts.empty?
|
35
|
+
append_localized_parts
|
36
|
+
unless @parts.empty?
|
37
|
+
@content_type = "multipart/alternative"
|
38
|
+
@charset = nil
|
39
|
+
@parts = sort_parts(@parts, @implicit_parts_order)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Then, if there were such templates, we check to see if we ought to
|
44
|
+
# also render a "normal" template (without the content type). If a
|
45
|
+
# normal template exists (or if there were no implicit parts) we render
|
46
|
+
# it.
|
47
|
+
render_localized_normal_template
|
48
|
+
|
49
|
+
# Finally, if there are other message parts and a textual body exists,
|
50
|
+
# we shift it onto the front of the parts and set the body to nil (so
|
51
|
+
# that create_mail doesn't try to render it in addition to the parts).
|
52
|
+
if !@parts.empty? && String === @body
|
53
|
+
@parts.unshift Part.new(:charset => charset, :body => @body)
|
54
|
+
@body = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# If this is a multipart e-mail add the mime_version if it is not
|
59
|
+
# already set.
|
60
|
+
@mime_version ||= "1.0" if !@parts.empty?
|
61
|
+
|
62
|
+
# build the mail object itself
|
63
|
+
@mail = create_mail
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def append_localized_parts
|
69
|
+
codes = locale_codes
|
70
|
+
codes.each do |code|
|
71
|
+
if code
|
72
|
+
templates = Dir.glob("#{template_path}/#{@template}.#{code}.*")
|
73
|
+
else
|
74
|
+
templates = Dir.glob("#{template_path}/#{@template}.*")
|
75
|
+
end
|
76
|
+
templates.each do |path|
|
77
|
+
sections = File.basename(path).split(".")[0..-2] || []
|
78
|
+
|
79
|
+
# skip if this is some other language
|
80
|
+
next if !code && Globalize::RFC_3066.valid?(sections[1])
|
81
|
+
|
82
|
+
# skip either template name and locale, or just template name
|
83
|
+
type_sections = code ? sections[2..-1] : sections[1..-1]
|
84
|
+
type = type_sections.join("/")
|
85
|
+
|
86
|
+
next if type.empty?
|
87
|
+
@parts << Part.new(:content_type => type,
|
88
|
+
:disposition => "inline", :charset => charset,
|
89
|
+
:body => render_message(sections.join('.'), @body))
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# if we found templates at this stage, no need to continue to defaults
|
94
|
+
break if !templates.empty?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def render_localized_normal_template
|
99
|
+
template_exists = @parts.empty?
|
100
|
+
codes = locale_codes
|
101
|
+
codes.each do |code|
|
102
|
+
localized_name = @template
|
103
|
+
if !template_exists
|
104
|
+
if code
|
105
|
+
localized_name = [ @template, code ].join(".")
|
106
|
+
template_exists ||=
|
107
|
+
Dir.glob("#{template_path}/#{localized_name}.*").any? { |i| i.split(".").length == 3 }
|
108
|
+
else
|
109
|
+
template_exists ||=
|
110
|
+
Dir.glob("#{template_path}/#{@template}.*").any? { |i| i.split(".").length == 2 }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
@body = render_message(localized_name, @body) if template_exists
|
114
|
+
break if template_exists
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def locale_codes
|
119
|
+
loc = Globalize::Locale.active
|
120
|
+
lang = Globalize::Locale.language
|
121
|
+
codes = [ loc, lang ].compact.map {|o| o.code }.uniq
|
122
|
+
codes << nil # look for default path, with no localization
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|