Localization class

Hi all,
while trying to write a multi-language sketch, I've putted together a class to simplify my code.
Here's what I've come up with.

(arduino 1.0)

The sketch:

// Arduino localization example.

#include <LiquidCrystal.h>
#include "ArduL10n.h"

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);    // lcd pins for nuelectronics lcd+keypad shield
const short LCD_ROWS = 2;
const short LCD_COLS = 16;

// localization object
ArduL10n l10n;

// print some (localized) strings
void printStrings() {
    lcd.clear();
    lcd.print(l10n.getString(l10n.STR_SELECTED_LANGUAGE));
    lcd.setCursor(0, 1);
    lcd.print(l10n.getString(l10n.STR_LANG));
    lcd.print(" ");
    lcd.print(l10n.getString(l10n.STR_WAIT));
}


void setup() {
    lcd.begin(LCD_COLS, LCD_ROWS);
}


void loop() {
    byte l = l10n.getCurrLang();
    l++;
    if (l >= l10n.NUM_LANGS) {
        l = 0;
    }
    l10n.setCurrLang(l);

    printStrings();
    delay(1000);
}

ArduL10n.h:

#ifndef _ARDU_L10N
#define _ARDU_L10N

#include <Arduino.h>

// Localization for Arduino sketches.
// Author: tuxduino @ arduino.cc forum
// Date: 2012-11-12
// Version: 0.1

class ArduL10n {
public:
    // --- start of user defined section --- //

    // language identifiers
    static const byte LANG_FIRST = 0;        // alias for looping through all languages
    
    static const byte LANG_ITA = 0;
    static const byte LANG_ENG = 1;
    // ...define more languages...
    // don't forget to update LANG_LAST
    
    static const byte LANG_LAST = 1;        // alias for looping through all languages
    static const byte NUM_LANGS = LANG_LAST + 1;        // number of defined languages
    
    static const byte DEFAULT_LANG = LANG_ITA;

    // string identifiers
    static const byte STR_FIRST = 0;        // alias to loop through all the strings
    
    static const byte STR_LANG = 0;
    static const byte STR_SELECTED_LANGUAGE = 1;
    static const byte STR_WAIT = 2;
    // ...define more strings...
    // don't forget to update STR_LAST
    
    static const byte STR_LAST = 2;         // alias to loop through all the strings
    
    // --- end of user-defined section --- //
    
    // specify one of the string ids above, and automatically get
    // the translated string for the currently selected language
    __FlashStringHelper* getString(byte stringId) const;
    
    // get a specific translation, regardless of the currently
    // selected language
    __FlashStringHelper* getString(byte stringId, byte langId) const;
    
    // get the currently selected language
    byte getCurrLang() const;

    // select a language
    void setCurrLang(byte lang);
    
    // constructor: specify the initially selected language
    ArduL10n(byte lang = DEFAULT_LANG);
    
private:
    // currently selected language
    byte _currLang;

    // lenght of the longest string (as in strlen)
    static const byte MAX_STR_LEN = 16;
    
    // array of localized strings
    static const char strings[][NUM_LANGS][MAX_STR_LEN + 1];
};


#endif

ArduL10n.cpp:

#include "ArduL10n.h"

// Localization for Arduino sketches.
// Author: tuxduino @ arduino.cc forum
// Date: 2012-11-12
// Version: 0.1

// this is where the actual text goes.
// notice the + 1 after MAX_STR_LEN to make room for the null-byte string terminator.
const char ArduL10n::strings[][NUM_LANGS][MAX_STR_LEN + 1] PROGMEM = {
    // LANG_ITA strings   // LANG_ENG strings
    // 012345678901235 ,  0123456789012345    // string length guides to ensure we don't go beyond MAX_STR_LEN
    { "Italiano"       , "English"          },
    { "Lingua selez."  , "Sel. language"    },
    { "Attendi"        , "Wait"             },
    // ...add more string couples...
    // don't forget to add their string identifiers in ArduL10n.h too...
};


ArduL10n::ArduL10n(byte lang) {
    if (lang < NUM_LANGS) {
        _currLang = lang;
    }
    else {
        _currLang = DEFAULT_LANG;
    }
};



__FlashStringHelper* ArduL10n::getString(byte stringId) const {
    return getString(stringId, getCurrLang());
}


__FlashStringHelper* ArduL10n::getString(byte stringId, byte langId) const {
    if (stringId <= STR_LAST && langId <= LANG_LAST) {
        return (__FlashStringHelper*)ArduL10n::strings[stringId][langId];
    }
    else {
        return NULL;
    }
}


byte ArduL10n::getCurrLang() const {
    return _currLang;
}


void ArduL10n::setCurrLang(byte lang) {
    if (lang < NUM_LANGS) {
        _currLang = lang;
    }
}

The translated strings are stored into progmem, to avoid wasting precious RAM.
Currently supports up to 256 string ids. This limit could be easily raised by changing byte with unsigned int.

To add a new string couple follow these steps:

  1. add the two(*) strings to the strings[] array at the top of the cpp file
  2. ensure their length is below MAX_STR_LEN, or increase that constant to make room for the longest string
  3. add the named constant for the string id: its value must be equal to the greatest string id so far + 1
  4. update the STR_LAST named constant

(*) or three, or four, etc., depending on the number of languages defined (NUM_LANGS)

I admit adding strings is not as straightforward as I'd like. But the sketch code is quite clean IMHO.

Any questions, comments or suggestions are welcome, of course.

tuxduino:
Any questions, comments or suggestions are welcome, of course.

Given that this post isn't asking a programming question, it might be more appropriate under the Exhibition / Gallery section.

Wherever the mod feels it's more appropriate...

Please could a mod move this topic in Other software development ? Thanks in advance.

That's 100 lines of code to replace

lcd.print (strings[STRING][LANG]);

There's a small amount of bounds checking but is it worth it?

// get a specific translation, regardless of the currently
    // selected language
    __FlashStringHelper* getString(byte stringId, byte langId) const;

Bad comment, if you are supplying the langid it's not "regardless of the currently selected language", this func probably should be private.


Rob

(Damn, I wrote a 50 lines reply but session expired and the entire post vaporized! :0 /me bangs head on the wall)

Graynomad:
That's 100 lines of code to replace

lcd.print (strings[STRING][LANG]);

That would be

lcd.print (strings[STRING][currLang]);

because I want to be able to change language at runtime. :wink:

Graynomad:
There's a small amount of bounds checking but is it worth it?

I admit I have a tendency to think too much about the general case rather than coding for the particular problem I'm facing, but...

Instead of directly accessing currLang, let's put getter and setter methods in place:

void setCurrLang(langId) { _currLang = langId; }
byte getCurrLang(langId) { return _currLang; }

Pretty pointless at first sight, but:

  • we can put range checks in setCurrLang() only, instead of everywhere currLang is accessed; an easy and reliable way to make sure _currLang never gets an invalid value; (provided no part of the code accesses _currLang directly, of course)
  • if we later want to remember the language selection across resets, we don't have to search for every place where _currLang is modified and add save code, instead we can just add EEPROM.write(LANG_ADDR, _currLang); to setCurrLang(), and an eeprom read() line in setup()

This quickly leads to a class... :wink:

Graynomad:

// get a specific translation, regardless of the currently

// selected language
    __FlashStringHelper* getString(byte stringId, byte langId) const;




Bad comment, if you are supplying the langid it's not "regardless of the currently selected language", this func probably should be private.

______
Rob

getString(strId, langId) doesn't change language id one has selected with setCurrLang().

Great work marcello.romani

While I was trying to write a multi-language sketch, I came up with a different method and am not trying to make a library of my own.

A link to my code: Select Languages at Compile-time for dynamic-multi-lingual-array - Programming Questions - Arduino Forum

I am attempting to facilitate adding languages by including header files.

I plan on using your library as a starting point as my post was not getting the kind of replies I was expecting.

Any pointers would be appreciated. Grazie!

Looking at your code:

const char ArduL10n::strings[][NUM_LANGS][MAX_STR_LEN + 1] PROGMEM = {
    // LANG_ITA strings   // LANG_ENG strings
    // 012345678901235 ,  0123456789012345    // string length guides to ensure we don't go beyond MAX_STR_LEN
    { "Italiano"       , "English"          },
    { "Lingua selez."  , "Sel. language"    },
    { "Attendi"        , "Wait"             },
    // ...add more string couples...
    // don't forget to add their string identifiers in ArduL10n.h too...
};

How would you be able to get the following to work?

const char ArduL10n::strings[][NUM_LANGS][MAX_STR_LEN + 1] PROGMEM = {
/* LANG_ENG strings => */    { "English"   , "Sel. language"  , "Wait"     },
/* LANG_ITA strings  => */    { "Italiano"  , "Lingua selez."  , "Attendi"  },      
};

Essentially have the lookup table be Row based not column based.
Explanation:
Each row would be a language
Each column would be the index of "translated word/phrase" you want to lookup