(1) Never, ever override Ruby core housekeeping methods without a non-override clause for normal exception handling when the scenario has nothing to do with why you are overriding the method.
You can simply use super
, which calls the const_missing()
method in the ancestor chain of superclasses. Or, alternatively, you can just raise a NameError
exception right there within your override, ie:
if module_name.const_defined?(name)
return module_name.const_get(name)
else
fail(NameError, "uninitialized constant #{name}", caller)
end
… or …
if module_name.const_defined?(name)
return module_name.const_get(name)
else
super # pass up to superclasses with all arguments
end
(2) You must be careful so as not to get Ruby into a endless loop of calling const_missing
. This line:
module_name = (language == 0) ? English : French
… has the potential to cause Ruby to recursively call const_missing
from itself, when one of the module identifiers has not yet been defined.
Also, the const_get()
method will also call const_missing()
when the constant is not defined, in order to decide whether a NameError
should be raised. Calling const_get()
method then can also set up a recursive loop (so you were correct in calling it conditionally by first checking if it was defined.)
(3) I probably would not have the get_current_language
return an Integer
. Instead, I would have it hold a string or symbol representing the language module name.
Or I might just set an extension wide LANG
constant that points at the language module …
begin
load File.join(__dir__, 'lang', Sketchup.get_locale<<'.rb')
rescue LoadError
load File.join(__dir__, 'lang', 'en-US.rb')
rescue
raise
end
Within each language file, at the level of the extension module I would set the LANG
constant …
# encoding: UTF-8
# File: lang/en-US.rb
module MyName
module Tool
module English
# translatable constants
end
LANG = English
end
end
This could simplify your Tool.const_missing
method somewhat …
module MyName
module Tool
# Load the language file:
begin
load File.join(__dir__, 'lang', Sketchup.get_locale<<'.rb')
rescue LoadError
load File.join(__dir__, 'lang', 'en-US.rb')
rescue
raise
end
# the LANG constant is now defined.
def self.const_missing(name)
if LANG.const_defined?(name)
LANG.const_get(name)
else
super
end
end
# ... other code ...
end
end
I find it a bit “hacky”. Ruby is multi-paradigm so there are several ways of doing things.
The LanguageHandler
class I find is memory intensive and uses String key lookups. String comparison in Ruby is slow compared to other equality tests. It’s use means there are 2 copies of every String object.
Since LanguageHandler
is just a wrapper class around a Ruby hash, I find it easier to just define a LANG
hash in the language files but I use short symbol keys. (Loading a .rb
file that just defines a hash is fast because it uses Ruby’s compiled C interpreter instead of that which is coded in Ruby within the LanguageHandler
class.)
So in my code LANG
points at a hash loaded at the top of the extension module.
Then I often define a say()
method that does the lookup in the hash and handles situations where the hash has no value for the key …
def say(arg)
if arg.is_a?(Symbol)
text = LANG[sym]
text.nil? arg.to_s : text
else
arg # return unchanged
end
end
Then in the code I use the method …
nifty_command = UI:Command(say(:nifty_name)) { cmd_nifty() }
UI.menu('Plugins').add_item(nifty_command)
But in practice, during testing I make sure all lookups into the LANG
hash have keys and corresponding values. Only when I am done testing using an English hash do I copy the hash file and translate into other languages.
Anyway, back to the subject of using const_missing
.
I think it is just another more complicated way of doing a lookup. Ie, causing the const_missing
method to load a laguage file just adds complication to the code and testing it.
What I mean is that the normal behavior of const_missing
is a valuable tool during testing that tells you your code has not loaded it’s resources properly. Changing how it works can work against you.
Yes, really I do not see the need for const_missing
to be called many times during runtime. The extension’s language resource should be loaded once and then just used.
I’ve been doing it at the top of the extension module as one of the initial load tasks. But recently we’ve been discussing various ways to defer loading of large blocks of code or resources until the extension actually needs them as indicated by the user purposefully activating a feature of the extension. (ie, clicking a menu item or toolbar button, etc.)
As part of this, we have been talking about const_missing
loading parts of the extension code “on demand”.
See: Extension Warehouse - set limit for number of startup files - Developers / Ruby API - SketchUp Community
So, it seems that your code is attempting to defer the loading and definition of a language module. When the translated constant is not accessible in the extension submodule (Tool
in the example,) your const_missing
will seek to load the file.
- But, I do not think
const_missing
should be used continuously as a lookup method. Instead I would think it better to include
the loaded language module into the parent extension module thereby making the needed constants available from that point forward. begin
load File.join(__dir__, 'lang', Sketchup.get_locale<<'.rb')
rescue LoadError
load File.join(__dir__, 'lang', 'en-US.rb')
rescue
raise
else
# LANG set in the language file evaluation
include LANG
# The constants of the module pointed at by LANG are now available
end
Also, require
and require_relative
do not need to be used as they search $LOADED_FEATURES
(which is unnecessary in your case as your code determines that the language module has not been loaded,) and then bloats the $LOADED_FEATURES
array further by stuffing the load path into it. (There are some speed issues with lookups into the $LOADED_FEATURES
array and it’s best not to bloat it when you know it will never need to be searched for a particular file path.)
Note: load()
does not use the $LOAD_PATH
array to search for the file, so an absolute pathname must be passed to a valid existing file. This also means the filetype must be appended.
Regarding language files, one of the ways I’ve limited what was loaded, is to divide the strings into at least two files.
One I call the “info” file which only has the translated strings for the SketchupExtension
instance’s properties that are displayed in the Extension Manager.
The other string file I’ve talked about above, which is the runtime translations that are only needed if the user has the extension set to load in the manager.
Now, regarding runtime translations. There will be a need for minimum translated text for the extension’s command menu texts, tooltips and statusbar texts and perhaps any messagebox error texts if some command is not evoked in the proper scenario.
In summation, I am not yet sure if I’ll use const_missing
or just keep a hash containing flags for loaded resources that can be checked before a certain resource is needed, then loaded on demand if not and the flag in the hash then set true
.
Just as the LanguageHandler
class is a wrapper around a hash, extension authors could wrap or subclass Hash
and change the behavior when a key is not found to load that key/value pair “on demand”. (Just another idea in my head.)