I have been doing Java programming for over ten years and when I work with Rails, I miss some of the conventions Java has. One of those convention is to use resource bundles for all user messages. Java provides ResourceBundle for that, but other MVC frameworks such as Struts and Spring MVC provides nice support for that. One of the thing that I like in Ruby is expressive and succint syntax, so it took me about an hour to whip up the functionality I needed. I wanted to use the resources file using method invocation or hash syntax, e.g.
MessageResource.my_message
Optionally I could pass in arguments, e.g. if I had message like:
errors_range: "{0} is not in the range {1} through {2}."
which takes in three arguments then I can invoke:
MessageResource.errors_range('credit-card', 10, 20, 30)
In addition, I could use hash access syntax, e.g.
MessageResource[:errors_range, 'credit-card', 10, 20, 30]
The other thing I needed was that if I specify locale then it
should find the message from that locale, e.g.
MessageResource.es.errors_range('credit-card', 10, 20, 30)
Finally, I wanted this to be robust so that if the key-code for the
message or locale is not found then it returns key-code instead of
throwing exception or passing nil back (this can waste a lot of time
when developing web applications.)
So without further delay, here is the code:
1 require 'yaml' 2 require 'resource_bundle.rb' 3 4 class MessageResource 5 @@bundles = {} 6 def self.[](code, *args) 7 self.populate unless @@bundles.size > 0 8 key = code.to_s 9 if key.length == 2 and @@bundles.has_key? key 10 bundle = @@bundles[key] || "??#{code}-#{args}??" 11 else 12 bundle = @@bundles['en'] 13 if key.length == 2 14 bundle 15 else 16 bundle[key, *args] || "??#{code}??" 17 end 18 end 19 end 20 21 ################### 22 # Populates all resource bundles and stores them by locale. 23 ################### 24 def self.populate 25 unless @@bundles.size > 0 26 files = Dir.glob(File.dirname(__FILE__) + "/../../../config/messages*") 27 files.each do |f| 28 locale = 'en' 29 locale = $&.slice(1,2) if f =~ /_...yml/ 30 begin 31 messages = YAML.load_file(f) 32 rescue ArgumentError => e 33 raise ArgumentError, "Invalid resource file #{f} -- #{e}", caller 34 end 35 raise ArgumentError, "Invalid resource file #{f}", caller if !messages 36 @@bundles[locale] = ResourceBundle.new(locale, messages) 37 end 38 end 39 end 40 41 ################### 42 # Defines method_missing for class that simply invokes array operator. 43 ################### 44 def self.method_missing(sym, *args) 45 self[sym, *args] 46 end 47 48 protected 49 def initialize;end 50 end 51 52 # 53 ################################################################# 54 class ResourceBundle 55 def initialize(locale, messages) 56 @locale = locale 57 @messages = messages 58 end 59 # 60 ################### 61 # Overloads brackets to access messages by keys. 62 ################### 63 def [](code, *args) 64 message = @messages[code.to_s] 65 if message && args 66 args.each_with_index {|arg, i| 67 message.gsub!(/{#{i}}/, arg.to_s) 68 } 69 end 70 message || "???#{code}-#{args.join(',')}???" 71 end 72 ################### 73 # Defines method_missing for this instance that simply invokes array operator. 74 ################### 75 def method_missing(key, *args) 76 self[key, *args] 77 end 78 end
Note, I am using YAML syntax to store messages, here is a sample file:
—
notice_password_changed_wrong: “Your old password did not match, please try again.”
notice_password_mismatch: “Your new password did not match confirmed password, please try again.”
notice_password_changed: “Password was changed successfully.”
notice_password_reset_failed: “Password could not be reset, please try again.”
notice_password_reset_success: “Password was reset successfully.”