I have a bare bones class:
internal class CLMExplorerTranslations
{
// LBL_BROTHER
public string Brother { get; set; }
// LBL_SISTER
public string Sister { get; set; }
// LBL_ABBREV_CHAIR
public string Chairman { get; set; }
// LBL_ABBREV_TREAS_TALK
public string TreasuresTalk { get; set; }
// LBL_ABBREV_TREAS_DIG
public string SpiritualGems { get; set; }
// LBL_ABBREV_TREAS_READ
public string BibleReading { get; set; }
// LBL_TALK
public string Talk { get; set; }
// LBL_ABBREV_DEMO
public string Demonstration { get; set; }
// LBL_ABBREV_ASST
public string Assistant { get; set; }
// LBL_ABBREV_LIVING
public string Living { get; set; }
// LBL_ABBREV_CBS
public string ConductorCBS { get; set; }
// LBL_ASSIGNMENT_CBS_READ
public string ReaderCBS { get; set; }
// LBL_ABBREV_PRAY
public string Prayer { get; set; }
public CLMExplorerTranslations()
{
Brother = "Brother";
Sister = "Sister";
Chairman = "Chair";
TreasuresTalk = "Treas. Talk";
SpiritualGems = "Spiritual Gems";
BibleReading = "Bible Read";
Talk = "Talk";
Demonstration = "Demos";
Assistant = "Asst";
Living = "Living";
ConductorCBS = "CBS";
ReaderCBS = "CBS Reader";
Prayer = "Pray";
}
}
As you can see, it is very simple. The constructor initializes the properties with English values. I then have a public method which is passed a language code as a string. This in turn updates the properties. For example:
public void InitTranslations(string langCode)
{
if (langCode == "AFK")
{
Brother = "Broer";
Sister = "Suster";
Chairman = "Voors.";
TreasuresTalk = "Skatte toespr.";
SpiritualGems = "Skatte soek";
BibleReading = "Skatte leesged.";
Talk = "Toespraak";
Demonstration = "Demon.";
Assistant = "Asst";
Living = "Lewe";
ConductorCBS = "GBS";
ReaderCBS = "GBS leser";
Prayer = "Geb.";
return;
}
if (langCode == "CHS")
{
Brother = "弟兄";
Sister = "姊妹";
Chairman = "主席";
TreasuresTalk = "宝藏";
SpiritualGems = "挖掘";
BibleReading = "朗读";
Talk = "演讲";
Demonstration = "示范";
Assistant = "助手";
Living = "生活";
ConductorCBS = "研经班";
ReaderCBS = "课文朗读者";
Prayer = "祷告";
return;
}
// More
}
There are a total of 26 if
clauses for 26 different languages. The translations get set once and then don't need changing again.
It functions fine but is there a simpler way to manage this that does not end up with a function being some 500 lines long?
This class is part of a DLL library/
Context
It isn't for a GUI. A part of my DLL is using CvsHelper
to read a CSV document. One of the fields in the CSV has several values with its own delimiter. I split this single field into a list of values and then need to parse the values to identify what each are. The user states what the language of the CSV file will be, so that I know what values to test for. Then when I find the matches I can convert into my own enumerated values for my own classes to use.
In the comments it has been suggested that I use embedded resources. But it is not clear how to do this given the above context. Eg. if I pass CHS
to the function then it would need to retreive the CHS
embedded resource values.
I see there are more comments just added for me to review.
CodePudding user response:
I would use resource files, here's how you would set it up:
In your project, create a folder for your resource files
Inside this folder, add one JSON file for each language you want to support, content like
{ "Brother": "Broer", "Sister": "Suster", ... }
Name them
language-AFK.json
using all the language codes as appropriate.In your project, right-click each file and go to properties and set build action to "EmbeddedResource"
NOTE! It's important that you don't forget to do this for one file, as this would leave the file on disk at compile time and it would not be part of the output assembly. (see bonus tips below for a way to ensure this is done also for future language files)
Then somewhere add code like this:
using var stream = typeof(SomeClass).Assembly.GetManifestResourceStream( $"Namespace.To.Your.Folder.language-{languageCode}.json"); using var reader = new StreamReader(stream, Encoding.UTF8); return JsonSerializer.Deserialize<CLMExplorerTranslations>(reader.ReadToEnd());
Note that you shouldn't use the dot .
to separate your resource file prefix, like "language" from the language code as this will actually only keep one of those files due to how resource naming conventions are used. Instead I used the minus sign -
above.
Hint If you can't seem to get the naming of the resource files correct, like you double-check everything and you get errors that streams are null
and similar, you can run code like this to inspect what your resources were actually named, and then adjust accordingly:
foreach (string name in typeof(SomeClass).Assembly.GetManifestResourceNames())
Console.WriteLine($"resource: {name}");
Bonus tips:
- You can now even add unit tests to verify that no JSON file is either missing a key or having extra keys, to ensure you always translate everything (I call these quality tests, though they are using a unit test framework)
- You can also use quality tests to ensure the files on disk in that folder actually exists as embedded resources in the assembly you're testing, to ensure you never forget to embed one of the files, for instance if you add a new one in the future
- If the resource files are big you can also compress them, though this will require you to do some extra legwork at buildtime, generally it's not worth it but at least it's an option
CodePudding user response:
you can add 26 json files and then load them by naming convention (ie: us.json/chs.json/afk.json) Then you can create and initialize like this (using Newtonsoft.Json in code)
public static CLMExplorerTranslations Load(string langCode)
{
return JsonConvert.DeserializeObject<CLMExplorerTranslations>(File.ReadAllText($"{langCode.ToLowerInvariant()}.json"));
}
Given the code you have now is easy to copy paste and create json files with some search/replace in notepad
CodePudding user response:
Use a single CSV configuration file
This is not necessarily the most elegant but is a very pragmatic approach.
Add a language code property to your class
internal class CLMExplorerTranslations { public string LangCode { get; set: } //etc }
Create an Excel spreadsheet with one row for each language and a column for each property (including
LangCode
). Make sure to include a header row with the property names.Save the spreadsheet as CSV
Modify your code to import the spreadsheet using GetRecords (since you are using CsvHelper anyway).
CLMExplorerTranslations GetTranslation(string langCode) { using (var reader = new StreamReader("ColumnDefinitions.csv")) using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csv.GetRecords<CLMExplorerTranslations>(); var translation = records.SingleOrDefault( x => x.LangCode == langCode ); if (translation == null) throw ArgumentException("Invalid language code"); return translation; } }
I think you can see this is only a very small amount of work, does not introduce any new dependencies (you're already using CsvHelper
), and has the additional benefit of being able to add and modify languages without touching code. And personally I think it is much easier to edit a spreadsheet than edit a series of resources.