Home > Mobile >  Can this C# code be simplified so that my method is not over 500 lines long and easier to maintain?
Can this C# code be simplified so that my method is not over 500 lines long and easier to maintain?

Time:01-04

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:

  1. In your project, create a folder for your resource files

  2. 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.

  3. 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)

  4. 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.

  1. Add a language code property to your class

     internal class CLMExplorerTranslations
     {
         public string LangCode { get; set: }
         //etc
     }
    
  2. 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.

  3. Save the spreadsheet as CSV

  4. 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.

  •  Tags:  
  • Related