Language Handler Problems

I have it precisely like this.

To test it I put the medeek_truss.strings file (french version) into the en-US folder but it won’t load the values.

I tried installing the French version of SketchUp on my same computer that I already have the english version installed, that does not seem to work, only the english version will load.

it should after a restart…

any line with any error will fail silently to english…

john

I’ve implemented the array technique, that will eventually save me a lot of time:


if @Roofslope_db == "PITCH"
			prompts1 = ['Truss Type', "Out-to-out Span (ft.): ", "Top chord Pitch (x/12): ", "Overhang Left (in.): ", "Overhang Right (in.): ", "TC Size (in.): ", "BC Size (in.): ", "Web Size (in.): ", "Ply Thickness (in.): ", "Raised Heel: ", "Heel Height (in.): "]
 			defaults1 = ["#{@Trusstype}", "#{@TrussSpan_ft.round(5)}", "#{@Pitch}", "#{@Overhangl}", "#{@Overhangr}", "#{@Tcd}", "#{@Bcd}", "#{@Webd}", "#{@Ply}", "#{@Raisedheel}", "#{@Usrhh}"]
 			list1 = ["King Post|Queen Post|Fink|Howe|Fan|Mod Queen|Double Fink|Double Howe|Mod Fan|Triple Fink|Triple Howe|Quad Fink", "", "2.5|3.0|3.5|4.0|4.5|5.0|5.5|6.0|6.5|7.0|7.5|8.0|8.5|9.0|9.5|10.0|10.5|11.0|11.5|12.0|12.5|13.0|13.5|14.0|14.5|15.0|15.5|16.0", "", "", "3.5|5.5|7.25|9.25|9.5|11.25|11.875|14.0|16.0", "3.5|5.5|7.25|9.25|9.5|11.25|11.875|14.0|16.0", "3.5|5.5|7.25|9.25|11.25", "", "NO|YES", ""]
 		elsif @Roofslope_db == "ANGLE"
			prompts1 = ['Truss Type', "Out-to-out Span (ft.): ", "Top chord Angle (deg): ", "Overhang Left (in.): ", "Overhang Right (in.): ", "TC Size (in.): ", "BC Size (in.): ", "Web Size (in.): ", "Ply Thickness (in.): ", "Raised Heel: ", "Heel Height (in.): "]
 			defaults1 = ["#{@Trusstype}", "#{@TrussSpan_ft.round(5)}", "#{@Pitch_deg}", "#{@Overhangl}", "#{@Overhangr}", "#{@Tcd}", "#{@Bcd}", "#{@Webd}", "#{@Ply}", "#{@Raisedheel}", "#{@Usrhh}"]
 			list1 = ["King Post|Queen Post|Fink|Howe|Fan|Mod Queen|Double Fink|Double Howe|Mod Fan|Triple Fink|Triple Howe|Quad Fink", "", "", "", "", "3.5|5.5|7.25|9.25|9.5|11.25|11.875|14.0|16.0", "3.5|5.5|7.25|9.25|9.5|11.25|11.875|14.0|16.0", "3.5|5.5|7.25|9.25|11.25", "", "NO|YES", ""]
 		
		else
			UI.messagebox("Non-valid roof slope type, action aborted.") 
			exit 0
		end

		@prompts1 = prompts1.each do |prompt|
			LH[prompt]
			puts LH[prompt]
		end
	
		@Inputs1 = UI.inputbox(@prompts1, defaults1, list1, "Medeek Truss Plugin - Geometry")

Still can’t get it to work though. Must be the way I have all these multiple nested modules and classes that is messing things up.

This language handler does not seem very robust, I might just have to build my own like Garry K. has done.

No you cannot have two editions of the same year release on the same computer. (Both will be overwriting each others registry entries also, possibly causing havoc.)

In related thread: Creating language resource files

So you can create a series of desktop icons for testing in the various supported languages, by editing the “Target:” field, of the “Shortcut” tab, of the icon’s “Properties”.

I think you would just add a space and then "-lang=fr" after the pathname to a copy of the standard icon made by the installer. Then of course you’d change the shortcut’s name to indicate French.

Repeat for other languages.

Only run one language test application instance at a time (as they’ll be sharing the same registry hive.)

Note what Thomas says concerning Sketchup::get_locale():


FYI:

Swedish has been added recently. NL was dropped after v8. Russian may have been dropped for a few years after v8.

Related posts elsewhere:

Related topic threads:

… and from those, other nuggets you should know:

As I mentioned in some of the other threads …

Keep in mind the interpreter parser is compiled C while the LanguageHandler class’ parser is interpreted Ruby.

I’m not sure if I have posted an example, but basically I just create a “lang” subfolder of my plugin folder, and populate it with .rb scripts that use a naming syntax like “#{LANG}.rb”, where the plugin constant LANG is either a specific language the user has selected in my plugin’s options dialog (which can be even languages the SketchUp doesn’t even ship using,) or (upon first run) the string returned by Sketchup::get_locale().

In the main plugin file, I’d define some lang constants used by all languages:

# encoding: UTF-8
module Author::SomePlugin

  KEY ||= Module.nesting.name[0].gsub('::','_') # qualified registry / plist key

  LANGCODES ||= Hash[
    :english,'en-US', :french,'fr', :italian,'it',
    :german,'de', :spanish,'es', :japan,'ja',
    :korean,'ko', :china,'zh-CN', :taiwan,'zh-TW',
    :portbraz,'pt-BR', :dutch,'nl', :russian,'ru'
  ]

  LANG ||= Sketchup.read_default(KEY,'lang') 
  if LANG.nil? # has not been set (1st run)
    LANG = Sketchup::get_locale # set it
    Sketchup.write_default(KEY,'lang',LANG) # store it
  end
  LANGFILE ||= File.join( File.dirname(__FILE__),"lang/#{LANG}.rb" )
  load(LANGFILE) rescue load( File.join( File.dirname(__FILE__),'lang/en-US.rb' ))

end

Then the contents of the language ruby scripts for "lang/en-US.rb" would be like:

# encoding: UTF-8
module Author::SomePlugin

  OPTIONS_PROMPTS ||= {
    :lang => 'Language',
    :data => 'Data Path',
    :save => 'Save Path:',
    # etc., etc., ...
  }

  OPTIONS_ITEMS ||= {
    :lang => LANGCODES.join('|'),
  }

  STATUSBAR ||= {
    :tool1 => 'This tool does such and so forth',
    :tool2 => 'This tool does more than any other'
    # etc., etc., ...
  }

  # ETC., ETC., ...

end

Then I copy the english RB file a number of times and edit it and rename it for other languages.

2 Likes

I really like how this approach uses identifiers different from the English string. A lot of the faulty translations in SketchUp is because things are translated out of context. E.g. Extension could both be a Ruby extension or the style setting of extending visually extending edges (View > Edges > Extension). Group could both be a verb and a noun (though newer SU versions call the action “Make Group in the context menu”).

Using identifiers such as tool_status_pick_entity, tool_status_modify, ro_extend_edges etc is safer, especially when the translators are sloppy.

1 Like

I like what I see here but I’m trying to figure out if I can implement the system you’ve described above and still somehow use the array technique given by John so that I don’t need to completely rewrite all of my prompts. Unfortunately, I have a lot of them so any time saver is huge.

I and many other people have rolled our own in the past…

it’s the main reason a generic translation tool has never been successfully developed…

The ‘biggest’ problem with LanguageHandler was addressed in SU v14…

The ‘small’ change was the ability to have our own ‘Resources’ folder structure, removing the need to store files in SU’s application folder…

LanguageHandler has a few ‘gotcha’s’, but they can be circumvented…

one is it uses a existing file format that doesn’t play well with ruby…

Associated file extensions: .strings
i18n type: STRINGS
Encoding: UTF-16
.strings files are used for localizing macOS and iOS apps.

I’m not sure if/how this effects windows…

it is an issue on mac [at least], but as LanguageHandler discounts any line containing / or */ you can add an encoding to the file and save to match…

# encoding: UTF-8 // langhandlers completely skips any line with // or */
another is it documentation implies the need for ‘one to one’ en-US strings, which makes updating in code text difficult…

Apples example uses keywords and comments to aid translation…

/* registration information: Username*/
“username” = “Username:”;
/* registration information: Password*/
“password” = “Password:”;
/* registration information: Email Address*/
“email_address” = “Email Address:”;
/* registration information: User Role*/
“user_role” = “User Role”;
/* registration information: Phone*/
“phone” = “Phone”;
/* registration information: Country*/
“country” = “Country”;

I should probably pushed the prompts in a ruby fashion…

      @prompts = []
      prompts.each do |prompt|
        @prompts << TRANSL8[prompt]
      end

may be why your test didn’t work…

john

Or

@promts = prompts.map { |p| TRANSL8[p] }
1 Like

I have a personal dislike for using |p| but :map is possibly a better approach…

EDIT: rubocop tells me this is ‘best’ for the strings…

    prompts = %w(
      radius
      segments
      frequency
      height
      line_only
      with_circle
      with_surface
      with_cylinder
      with_castellation
      surface_thickness
      explode_groups
    )

     @prompts = prompts.map { |prompt| TRANSL8[prompt] }

and then in the strings remembering the other 'LanguageHandler ‘gotcha’…

keyvalue = entryString.split('"="')

hardcoded to NOT accept whitespace around the =

# encoding: UTF-8 // langhandlers completely skips any line with // or */
"The string file can be translated"="The string file can be translated";
"Sine Circle Details"="Sine Circle Details";
"radius"="Radius of Circle";
"segments"="Circle Segments";
"frequency"="Wave Count";
"height"="Wave Height";
"line_only"="Create Single Wave";
"with_surface"="with Surface";
"with_circle"="with Circle";
"with_cylinder"="with Cylinder";
"with_castellation"="with Castellation";
"surface_thickness"="Face Thickness";
"explode_groups"="Explode Grouping";
"parram_legend"="Set parrameters for Wave";

one advantage this has is you can see if the en-US file works, before trying other languages…

john

I would really like to encourage everyone to using Langhandler to make translation of any extensions easier for the User base…

I started this in 2013 and it has been used by many developers and users since then…

but it only works on SU’s version of .string files…

if you don’t ‘understand’ a language, using phrases or word combinations gets better matches…

when a native speaker uses this and sends you the file, it’s even better…

it is also not limited to the ‘SU’ supported languages…

john

Yes that is nifty, but it can be used to translate strings, and then later still use hash or another author specific format, by using a Ruby utility to read .strings files and convert to something else.

There are two separate issues here. The translation of strings, and the workings of LanguageHandler class objects.


The latter is what is “clunky”. All example code shows creating and using perpetual LanguageHandler instance objects, which waste memory (2 copies of every string,) and are extremely slow (ie, Ruby String comparison.)

The examples encourage coders to look up the translated string in the LanguageHandler instance object each time it is needed. This is what I mainly take issue with.

Instead an extension (if it uses LanguageHandler instance objects,) … should do most it’s lookups when it first loads up, and store the translated strings in extension local objects (arrays, hashes, openstructs or sets, etc.) Then dispose of the LanguageHandler instance object(s).

One of my longest “pet peeves” are the perpetual global LanguageHandler instance objects created by all of the Trimble extensions. They sit there in memory even when the extensions are not being used !

this is the trace from loading Sine Circle…

"start"
[54, LanguageHandler, :[], "Sine Circle Details"]
[54, LanguageHandler, :[], "Click to Close"]
"HtmlDialog"
[54, LanguageHandler, :[], "Radius of Circle"]
[54, LanguageHandler, :[], "Circle Segments"]
[54, LanguageHandler, :[], "Wave Count"]
[54, LanguageHandler, :[], "Wave Height"]
[54, LanguageHandler, :[], "Create Single Wave"]
[54, LanguageHandler, :[], "with Circle"]
[54, LanguageHandler, :[], "with Surface"]
[54, LanguageHandler, :[], "with Cylinder"]
[54, LanguageHandler, :[], "with Castellation"]
[54, LanguageHandler, :[], "Face Thickness"]
[54, LanguageHandler, :[], "Explode Grouping"]
[54, LanguageHandler, :[], "Help"]
[50, Length, :initialize, 196.8503937007874]
[54, LanguageHandler, :[], "Set parrameters for Wave"]
[31, Length, :to_f, 196.8503937007874]
[54, LanguageHandler, :[], "Cancel"]
[54, LanguageHandler, :[], "OK"]

it appears to use the key, once…

john

If you refer to how you coded in this post …

… then I’m sorry, but that is “hard-wired” to use a single language (and a single unit of measure, which is a separate issue.)


You need to think about multilanguage support at the beginning, and code in a more flexible way.

Since you did not, then you’ll definitely need to do some code refactoring, but it will be educational, and perhaps a bit “painful”, but that’ll just provide more encouragement to do things better from the start, next time.

Build your prompt, default and choice arrays at the loadup of your extension (or sub-modules), and store them referenced by local constants, ie, PITCH_PROMPTS, PITCH_DEFAULTS, PITCH_CHOICES, ANGLE_PROMPTS, etc.

if @Roofslope_db == "PITCH"
  @Inputs1 = UI.inputbox( PITCH_PROMPTS, PITCH_DEFAULTS, PITCH_CHOICES, CAPTION )
elsif @Roofslope_db == "ANGLE"
  @Inputs1 = UI.inputbox( ANGLE_PROMPTS, ANGLE_DEFAULTS, ANGLE_CHOICES, CAPTION )
else
  UI.messagebox( ERROR_TEXT["Non-valid roof slope type, action aborted."] )
  raise InvaildRoofTypeError  # <-- local RuntimeError subclass
end

This makes code much more readable and hence easier to maintain.

(Notice that I replaced "exit 0" with a raising of a plugin specific runtime exception. SystemExit has no real use in an embedded Ruby interpreter, and used to cause a BugSplat in older SketchUp versions.)

I have no idea what “Sine Circle” is, nor how it works, nor who coded it, nor what you are “tracing” …

If this is the menu building code, then that type of code is usually only run once anyway.


Are you disputing something I said above, John ?

I’ve recently started to use Crowdin - uploading a JSON with the original language strings. Then set up desired languages I want and invite translators to join.

They are faced with an interface the shows the translation progress as well as featuring contextual screenshots and descriptions.

2 Likes

Wow, a tonne of information.

Now I’m simply confused. What is the suggested way of providing language support? Write my own system or use the SketchUp builtin system.

I realize a lot of my menus are hard coded for units etc… yes I have some work ahead of me. When I started creating this plugin about two years ago I was completely new to the SketchUp API, ruby and plugins in general, who would have thought I would need language support. This is simply a growing pain that must be dealt with.

I’d say that one factor is whether you want to support languages other than what SketchUp ship with. For my own extensions I used my custom language handler mainly because I wanted to provide more languages than SketchUp itself.
I also wanted some additional flexibility in terms of interpolating strings.

Yea, localisation is a challenge. You must adjust your UI such a way it fit everything. And if you start diving into pluralism in your string that opens up a whole new can of worms.