Skip to content

How I programmed multi language support in Wheeler's Wort Works

James Blackburn edited this page Oct 2, 2021 · 1 revision

GNU Get Text

Firstly, I thought, maybe it would be a good idea to follow a system that I've worked with in the past. That being Localazy in combination with GNU GetText. Clearly this API is very stable, and I thought it would be a good idea... But then I realised there was an issue with this, this meant more and more code in Wheeler's Wort Works was moving away from what it was meant to be in the past: a very simple community program, with understandable code.. The point was to hopefully release an alternative to Wheeler's Beer Engine, with an open-source nature that would allow more people to access it or reprogram it for their own needs.

One can see that my code, although it doesn't use GNU GetText, it was heavily influenced by the basic system applied within GNU GetText:

image

I could probably have used it with GNU GetText, but personally, I didn't like the way I had to structure my program.. As a mathematician, and a programmer, seeing those many layers of brackets (_('dkskads)9)0(-')) , just didn't seem like what I wanted, and it would have had complications with the update.py system too, as things such as "folder" creation would need to be implemented. And you know what happens when trying to make folders in a root scenario? That's right, unnecessary write permissions. So instead, to help my fellow Ubuntu/Debian users, I have created this software to use a JSON file, instead of a complicated folder system!

JSON File System

The JSON file system, basically works by creating a Python Dictionary, and taking the English phrases, and giving them another value returned. I hope this makes sense to my fellow developers? So the system works by, giving the dictionary (named _ after GetText), a key (_['Hello']) and in response, the system returns the value from the locale (i.e. _['Hello'] -> 'Guten Tag')

An extract from the JSON file:

{
    "en":{
       "Recipe Name:":"Recipe Name:",
       "Volume:":"Volume:",
       "Boil Volume:":"Boil Volume:",
       "Remove":"Remove",
       "Add New":"Add New",
       "Adjust Weight":"Adjust Weight",
       "Original Gravity":"Original Gravity",
       ...
     }
}

How did I generate this JSON file?

AUTOMATION! AUTOMATION! AUTOMATION! As it says on thinkautomation:

Just because you can automate something, it doesn’t follow that it should be automated. I disagree, because this is something that should be automated, let's look at my rubbish code to automate this:

# coding=utf8
# the above tag defines encoding for this document and is for Python 2.x compatibility

import re
import itertools

regexes = [r"(text=)'''(.*)'''", r'(text=)\"(.+?)\"', r"(text=)\'(.+?)\'", r"(label=)'''(.*)'''", r'(label=)\"(.+?)\"', r"(label=)\'(.+?)\'"]
test_str = open('beer_engine.py', 'r', encoding='utf8').read()

def get_text(regex, text):
    matches = re.finditer(regex, text, re.MULTILINE)

    return [match.groups() for matchNum, match in enumerate(matches, start=1) if match.groups()[1][0].isalpha()]
        
            
        
translatable = list(itertools.chain.from_iterable([get_text(regex, test_str) for regex in regexes]))
translate_dict = {'en': {}}
remove = ['L/kg', 'LDK', 'EBC']
add = ['Final Volume:', 'Original Gravity:', 'Final Gravity:', 'Alcohol Content:', 'Mash Efficiency:', 'Bitterness:', 'Colour:', 'Notes', 'Efficiency:', 'Alcohol (ABV):', 'Mash Liquor:']
add += ['Use the debian mode (only use on a Debian/Ubuntu system)', 'Use the local mode', 'Pull `update.py` from GitHub, then download the latest GitHub files', 'Using the current `update.py`, download the latest GitHub files', 'The file to open `--file file_name.berf[x]`']
# ln: 1795
# ln: 1423
for prg, text in translatable:
    if text not in remove:
        translate_dict['en'][text] = text

for text in add:
    translate_dict['en'][text] = text

print(translate_dict)
text = test_str
for trans in translate_dict['en'].values():
    r_trans = rf"(text|label)=('''|'|\")({trans})('''|'|\")"
    text = re.sub(r_trans, r"\1=_[\2\3\4]", text)

# Manual Replacements 
lines = [
    "start += '<p><b>Final Volume: </b>{volume} Litres</p>'.format(volume=self.volume.get())",
	"start += '<p><b>Original Gravity: </b>{og}</p>'.format(og=round(self.og, 1))",
	"start += '<p><b>Final Gravity: </b>{fg}</p>'.format(fg=round(self.fg, 1))",
	"start += '<p><b>Alcohol Content: </b>{abv}% ABV</p>'.format(abv=round(self.abv, 1))",
	"start += '<p><b>Mash Efficiency: </b>{efficiency}</p>'.format(efficiency=brew_data.constants['Efficiency']*100)",
	"start += '<p><b>Bitterness: </b>{bitterness} IBU</p>'.format(bitterness=round(self.ibu))",
	"start += '<p><b>Colour: </b>{colour} EBC</p>'.format(colour=round(self.colour, 1))",
    "start += '''<hr><h2>Notes</h2>\n{notes}'''.format(notes=notes) if len(notes) >= 1 else ''",
    "start += '''<hr><h2>Notes</h2>\n<p>{notes}</p>'''.format(notes=notes.replace('\n', '<br>')) if len(notes) >= 1 else ''",
    "self.calc_lbl.insert('end', '''Efficiency: {efficiency}%{enter}Final Gravity: {final_gravity}{enter}Alcohol (ABV): {abv}{enter}Colour: {colour}EBC{enter}Mash Liquor: {mash_liquor}L{enter}IBU:GU: {ibu_gu}'''.format("
]
line_replacement = [
    "start += '<p><b>{rep} </b>{volume} Litres</p>'.format(volume=self.volume.get(), rep=_['Final Volume:'])",
	"start += '<p><b>{rep} </b>{og}</p>'.format(og=round(self.og, 1), rep=_['Original Gravity:'])",
	"start += '<p><b>{rep} </b>{fg}</p>'.format(fg=round(self.fg, 1), rep=_['Final Gravity:'])",
	"start += '<p><b>{rep} </b>{abv}% ABV</p>'.format(abv=round(self.abv, 1), rep=_['Alcohol Content:'])",
	"start += '<p><b>{rep} </b>{efficiency}</p>'.format(efficiency=brew_data.constants['Efficiency']*100, rep=_['Mash Efficiency:'])",
	"start += '<p><b>{rep} </b>{bitterness} IBU</p>'.format(bitterness=round(self.ibu), rep=_['Bitterness:'])",
	"start += '<p><b>{rep} </b>{colour} EBC</p>'.format(colour=round(self.colour, 1), rep=_['Colour:'])",
    "start += '''<hr><h2>{rep}</h2>\n{notes}'''.format(notes=notes, rep=_['Notes:']) if len(notes) >= 1 else ''",
    "start += '''<hr><h2>{rep}</h2>\n<p>{notes}</p>'''.format(notes=notes.replace('\n', '<br>'), rep=_['Notes:']) if len(notes) >= 1 else ''",
    "self.calc_lbl.insert('end', '''{rep1} {efficiency}%{enter}{rep2} {final_gravity}{enter}{rep3} {abv}{enter}{rep4} {colour}EBC{enter}{rep5} {mash_liquor}L{enter}IBU:GU: {ibu_gu}'''.format(rep1=_['Efficiency:'], rep2=_['Final Gravity:'], rep3=_['Alcohol (ABV):'], rep4=_['Colour:'], rep5=_['Mash Liquor:'],"
]


for idx, line in enumerate(lines):
    text = text.replace(line, line_replacement[idx])

# print(text)    
with open('beer_engine_lang.py', 'w', encoding='utf8') as f:
    f.write(text)

And yes I know, before anyone comments on this, I know that this is bad code, I mean for a start, why did I make a list of regexes, when I literally have a regex that does the exact same job a couple of lines later??? Well, bad programming obviously! But it works, so that's what I care about, and it is not a critical bit of software, so it can be as shoddy as I like :).

So basically, I fully used regexes to substitute and generate this JSON file.

Have an amazing day!