0

I want to implement language XML into my project and change all hard-code strings into language.xml references based on the way Android uses string resources. (I have not found anything that does this)

en_gb.xml:

<resource>
  <section1>
    <string name="hello">Hello</string>
    <string name="bye">Goodbye</string>
  </section1>
  <section2>
    <string name="world">World</string>
    <string name="end">!</string>
  </section2>
</resource>

jp_tr.xml

<resource>
  <section1>
    <string name="hello">こんにちは</string>
    <string name="bye">さようなら</string>
  </section1>
  <section2>
    <string name="world">世界</string>
    <string name="end">!</string>
  </section2>
</resource>

Not using this anymore See Edit-> now using ElementTree and exec() i can build classes based on these files

class en_gb:
    tr = ET.parse(r'.\assets\local\en_gb.xml')
    for rs in tr.getroot():
        exec(f'class {rs.tag}:pass')
        for c in rs:
            exec(f'{rs.tag}.{c.attrib["name"]}=str("{c.text}")')

This creates a structure as 'lang.section.strname' which i can then use in code

from localization import en_gb, jp_tr
lang = en_gb
print(lang.section1.hello) #> Hello
lang = jp_tr
print(lang.section2.world) #> 世界

Now i want to programically create all the base classes to automatically create the structure. However i cannot find a method to add a class to another class programically and construct it in the same way.

import xml.etree.ElementTree as ET
import os
from pathlib import Path
for lang in os.listdir('.\\assets\local\\'):
    lang = Path(language).stem
    exec(f'class {lang}:pass')
    for rs in ET.parse(fr'.\assets\local\{lang}.xml').getroot():
        exec(f'class{lang}.{rs.tag} = class {rs.tag}:pass')
        for c in rs:
            exec(f'{rs.tag}.{c.attrib["name"]}=str("{c.text}")')

however en_gb.base = class base:pass is not a valid syntax and so I'm stuck.

After creation here is how it should look (as in the raw code):

class en_gb:
    class section1:
        hello = "Hello"
        bye = "Goodbye"
    class section2:
        world = "World"
        end = "!"
class jp_tr:
    class section1:
        hello = "こんにちは"
        bye = "さようなら"
    class section2:
        world = "世界"
        end = "!"

Edit: I have replaced the previous method with SimpleNamespace

class local(object):
    def __init__(self, lang="en_gb"):
        l = localization()
        self = getattr(l.langs, lang)
    def set_lang(self, lang:str):
        self = getattr(l.langs, lang)

class localization:
    def __init__(self, lang="en_gb"):
        langs = {}
        for language in list(Path('./assets/local/').glob('*.xml')):
            l_name = Path(language).stem
            tree = ElementTree.parse(f".\\assets\\local\\{l_name}.xml")
            d = {}
            for child in tree.getroot():
                tmp = {}
                for c in child:
                    tmp[c.attrib['name']] = c.text
                d[child.tag] = SimpleNamespace(**tmp)
            langs[l_name] = SimpleNamespace(**d)
        self.langs = SimpleNamespace(**langs)
    def __get__(self):
        print("getting")
        return self.langs

However i cannot change the language using the locals class.

l = localization()
print(l.langs.en_gb.section1.hello) ##this works

lg = local()
print(lg.section1.hello) ##this doesnt work
#this should be able to change on the flu
lg.set_lang('jp_tr')
print(lg.section1.hello) ##should now be こんにちは
3
  • This exec(f'class {rs.tag}:pass') is wide open for code injection, can you not do with nested dicts ? Commented Jan 12, 2023 at 15:24
  • @ljmc I am trying to avoid hardcoded strings. dictionary's are just wrapped hard-coded stings. rather than using a.b.c you have to fiddle with symbols a["b"]["c"], even with IDE optimization its still a much more fiddley task. I know eval() and exec() are bad ways of doing this but i cannot find anything close to a solution that does not require strings on top of strings. Commented Jan 12, 2023 at 15:51
  • I should clarify, using exec() is not a requirement, it was just the first thing that got close to a solution for me. I want to programmatically create the last section of output code based on the xml files show. Commented Jan 12, 2023 at 15:55

1 Answer 1

0

I would avoid using exec like that as you get little benefit from nested dicts while opening your code to code injection.

This iterates through the directory containing the XML langpacks in a similar fashion as you described, and puts them in the I18N_PACKS dict.

import xml.etree.ElementTree as ET
from pathlib import Path

lang_pack_loc = Path("so/75098403/")

I18N_PACKS = {}

for lang_pack_path in lang_pack_loc.glob("*.xml"):
    lang = lang_pack_path.stem
    lang_dict = I18N_PACKS[lang] = {}
    resource = ET.parse(lang_pack_path).getroot()
    for section in resource:
        lang_dict[section.tag] = {}
        for translation in section:
            lang_dict[section.tag].update(
                {translation.attrib["name"]: translation.text}
            )

I18N_PACKS

# gives
# {'jp_tr': {'section1': {'hello': 'こんにちは', 'bye': 'さようなら'},
#   'section2': {'world': '世界', 'end': '!'}},
#  'en_gb': {'section1': {'hello': 'Hello', 'bye': 'Goodbye'},
#   'section2': {'world': 'World', 'end': '!'}}}

I18N_PACKS["jp_tr"]["section1"]["hello"]
# 'こんにちは'
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.