Pages: [1]   Go Down
Author Topic: Localization class  (Read 928 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Edison Member
*
Karma: 26
Posts: 1339
You do some programming to solve a problem, and some to solve it in a particular language. (CC2)
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
Code:
// 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:
Code:
#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:
Code:
#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.
« Last Edit: November 11, 2012, 07:51:42 pm by tuxduino » Logged

UK
Offline Offline
Shannon Member
****
Karma: 223
Posts: 12631
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
« Last Edit: November 12, 2012, 08:56:21 am by PeterH » Logged

I only provide help via the forum - please do not contact me for private consultancy.

Offline Offline
Edison Member
*
Karma: 26
Posts: 1339
You do some programming to solve a problem, and some to solve it in a particular language. (CC2)
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Offline Offline
Edison Member
*
Karma: 26
Posts: 1339
You do some programming to solve a problem, and some to solve it in a particular language. (CC2)
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

nr Bundaberg, Australia
Offline Offline
Tesla Member
***
Karma: 126
Posts: 8501
Scattered showers my arse -- Noah, 2348BC.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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?

Code:
// 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

Logged

Rob Gray aka the GRAYnomad www.robgray.com

Offline Offline
Edison Member
*
Karma: 26
Posts: 1339
You do some programming to solve a problem, and some to solve it in a particular language. (CC2)
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

That's 100 lines of code to replace

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

That would be
Code:
lcd.print (strings[STRING][currLang]);
because I want to be able to change language at runtime. smiley-wink

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:
Code:
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... smiley-wink

Code:
// 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().
Logged

Pages: [1]   Go Up
Jump to: