I'm developing a webapp with Nim as the backend. I need to support multiple languages with an easy and quick way to add further language support. Besides supporting multiple languages, I also need the return to be based on the host.
I have found a way which works quite well, but I don't think it's an optimal way, if my languagefile scales too much. So I'm looking for performance suggestions or another approach, if this isn't the perfect solution.
languagefile.cfg
title_ENh = Hubanize
title_DKh = Hubanize
title_ENc = Cx Manager
title_DKc = Cx Manager
help_ENh = Help
help_DKh = Hjælp
help_ENc = Help
help_DKc = Hjælp
languageparser.nim
proc langInit(cfgFilename: string): Table[string, string] =
## Parse the languagefile
let inputString = readFile(cfgFilename)
result = initTable[string, string]()
for line in inputString.splitLines:
result[chunks[0]] = chunks[1]
let langGet* = langInit("languagefile.cfg")
proc langGen*(identifier, pageID: string, lang = "EN"): string =
## Retrive language item
## identifier => The item to retrieve
## pageID => c or h based on the host address. Accessed per user with c.pageID
## lang => Language. Accessed per user with c.lang - test users can have empty lang, therefor standard "EN"
try:
return langGet[identifier & "_" & lang & pageID]
except:
return ""
echo langGen("title", "c", "EN") # = Cx Manager
echo langGen("help", "h", "DK") # = Hjælp
After rework this exmaple https://nim-lang.org/docs/tut2.html#building-your-first-macro-generating-source-code
import macros, strutils
var cfgFile = """title_ENh = Hubanize
title_DKh = Hubanize
title_ENc = Cx Manager
title_DKc = Cx Manager
help_ENh = Help
help_DKh = Hjælp
help_ENc = Help
help_DKc = Hjælp"""
let
inputString = cfgFile
macro readCfgAndBuildSource(cfgFilename: string): typed =
let
inputString = cfgFile
var source = ""
for line in inputString.splitLines:
# Ignore empty lines
if line.len < 1: continue
var chunks = split(line, '=')
if chunks.len != 2:
error("Input needs '=' split values, got: " & line)
source &= "const lng" & chunks[0].strip & "= \"" & chunks[1].strip & "\"\n"
if source.len < 1: error("Input file empty!")
result = parseStmt(source)
readCfgAndBuildSource("languagefile.cfg")
echo lnghelp_DKc
I hope it help you.
Thank you for the suggestion alexsad. But when using constants, I can't figure out how to access them with a dynamic reference.
With my current tables solution, I can access the item based on the host and language by concentrating the users elements:
# langGet[identifier & "_" & lang & pageID]
echo langGet["title" & "_" & "EN" & "c"] # => Cx Manager
I have multiple HTML pages, where the page title and other items needs to based on the host and language. The user needs to see the same webpage, but the language-items needs to reflect the host and language. The only solution I've got to work with constants, is just a simple if else, which would bloat my code
if c.pageID == "c":
$lngtitle_DKc
elif c.pageID == "h":
$lngtitle_DKh
Can I somehow concentrate multiple strings into a constants reference, to get the right language-item?
Hi, maybe this:
import macros, strutils, tables
var cfgFile = """title_ENh = Hubanize
title_DKh = Hubanize
title_ENc = Cx Manager
title_DKc = Cx Manager
help_ENh = Help
help_DKh = Hjælp
help_ENc = Help
help_DKc = Hjælp"""
var multiLang = initTable[string, string]()
macro readCfgAndBuildSource(cfgFilename: string): typed =
let
inputString = cfgFile
#...or from file by compile time
#inputString = slurp(cfgFilename.strVal)
var source = ""
for line in inputString.splitLines:
# Ignore empty lines
if line.len < 1: continue
var chunks = split(line, '=')
if chunks.len != 2:
error("Input needs '=' split values, got: " & line)
source &= "multiLang[\"" & chunks[0].strip & "\"] = \"" & chunks[1].strip & "\"\n"
#echo source
if source.len < 1: error("Input file empty!")
result = parseStmt(source)
proc langGen*(identifier, pageID: string, lang = "EN"): string =
## Retrive language item
## identifier => The item to retrieve
## pageID => c or h based on the host address. Accessed per user with c.pageID
## lang => Language. Accessed per user with c.lang - test users can have empty lang, therefor standard "EN"
try:
return multiLang[identifier & "_" & lang & pageID]
except:
return ""
readCfgAndBuildSource("languagefile.cfg")
echo langGen("title", "c", "EN") # = Cx Manager
echo langGen("help", "h", "DK") # = Hjælp
Thank you for the suggestions. I was not aware of NimYAML - I'll give it a look.
My main purpose of the question was to ensure, that my solution using tables had "the best" performance. When doing a simple comparing of tables, const and parsecfg with echo, constants is the winner. But I have not yet figured out, how to access them with concentrated strings.
import strutils, times, tables, parsecfg
# First run
# Tables
const langFile = "help_DKc=Hjælp"
var data1 = initTable[string, string]()
var dead1 = ""
for line in langFile.splitLines:
var chunks = split(line, '=')
data1[chunks[0]] = chunks[1]
let t1 = epochTime()
for i in countup(1, 1000):
#echo data1["help_DKc"]
dead1 = data1["help_DKc"]
let t1done = epochTime() - t1
# Second run
# Constants
const help_DKc = "Hjælp"
var dead2 = ""
let t2 = epochTime()
for i in countup(1, 1000):
#echo help_DKc
dead2 = help_DKc
let t2done = epochTime() - t2
# First run
# Parsecfg
var dict = newConfig()
dict.setSectionKey("help","DKc","Hjælp")
var dead3 = ""
let t3 = epochTime()
for i in countup(1, 1000):
#echo dict.getSectionValue("help","DKc")
dead3 = dict.getSectionValue("help","DKc")
let t3done = epochTime() - t3
echo "Tables: " & $t1done
echo "Constant: " & $t2done
echo "Parsecfg: " & $t3done
# Tables: 0.000804901123046875
# Constant: 0.0003900527954101562
# Parsecfg: 0.001749038696289062