ASP.NET8 using DataTables.net – Part4 – Multilingual

Mark PelfMark Pelf
6 min read

ASP.NET8 using DataTables.net – Part4 – Multilingual

A practical guide to using jQuery DataTables.net component in Asp.Net 8 MVC application.

Abstract: A practical guide to building an Asp.Net 8 MVC application that uses jQuery component DataTables.net. This is a continuation of article Part3.

1 ASP.NET8 using jQuery DataTables.net

I was evaluating the jQuery DataTables.net component [1] for usage in ASP.NET8 projects and created several prototype (proof-of-concept) applications that are presented in these articles.

1.1 Articles in this series

Articles in this series are:

  • ASP.NET8 using DataTables.net – Part1 – Foundation
  • ASP.NET8 using DataTables.net – Part2 – Action buttons
  • ASP.NET8 using DataTables.net – Part3 – State saving
  • ASP.NET8 using DataTables.net – Part4 – Multilingual
  • ASP.NET8 using DataTables.net – Part5 – Passing additional parameters in AJAX
  • ASP.NET8 using DataTables.net – Part6 – Returning additional parameters in AJAX
  • ASP.NET8 using DataTables.net – Part7 – Buttons regular
  • ASP.NET8 using DataTables.net – Part8 – Select rows
  • ASP.NET8 using DataTables.net – Part9 – Advanced Filters

2 Final result

The goal of this article is to create a proof-of-concept application that demos DataTables.net component in ASP.NET multi-language application. Let us present the result of this article.

Here is the English version:

Here is the German version:

So, DataTables.net component can be used in multilingual applications. Please note that we need to set up translations on two levels:

  1. Translations for DataTables.net component itself
  2. Translations for Strings used by DataTables.net component

It is a bit tricky to set up it all properly, but it is doable.

3 Multi-language setup

If you are not familiar with the multi-language setup for ASP.NET8, please read my articles [2]-[5]. I am not going to copy-paste the same text again here, I will just use the same code from those examples here. As explained in those articles, the main trick is to set up AspNetCore.Culture cookie properly for the particular language. All is explained in detail in those articles [2]-[5].

Here is the controller code that is doing that:

//HomeController.cs

 public IActionResult ChangeLanguage(ChangeLanguageViewModel model)
{
    if (model.IsSubmit)
    {
        HttpContext myContext = this.HttpContext;
        ChangeLanguage_SetCookie(myContext, model.SelectedLanguage);
        //doing funny redirect to get new Request Cookie
        //for presentation
        return LocalRedirect("/Home/ChangeLanguage");
    }

    //prepare presentation
    ChangeLanguage_PreparePresentation(model);
    return View(model);
}

private void ChangeLanguage_PreparePresentation(ChangeLanguageViewModel model)
{
    model.ListOfLanguages = new List<SelectListItem>
                {
                    new SelectListItem
                    {
                        Text = "English",
                        Value = "en"
                    },

                    new SelectListItem
                    {
                        Text = "German",
                        Value = "de",
                    },

                    new SelectListItem
                    {
                        Text = "French",
                        Value = "fr"
                    },

                    new SelectListItem
                    {
                        Text = "Italian",
                        Value = "it"
                    }
                };
}

private void ChangeLanguage_SetCookie(HttpContext myContext, string? culture)
{
    if (culture == null) { throw new Exception("culture == null"); };

    //this code sets .AspNetCore.Culture cookie
    CookieOptions cookieOptions = new CookieOptions();
    cookieOptions.Expires = DateTimeOffset.UtcNow.AddMonths(1);
    cookieOptions.IsEssential = true;

    myContext.Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
        cookieOptions
    );
}

Here is the footer in the view that I use for debugging purposes

@* _Debug.AspNetCore.CultureCookie.cshtml ===================================================*@
@using Microsoft.AspNetCore.Localization;

@{
    string text = String.Empty;

    try
    {
        var myContext = Context;
        string? cultureCookieValue = null;
        myContext.Request.Cookies.TryGetValue(
            CookieRequestCultureProvider.DefaultCookieName, out cultureCookieValue);
        string? text1 = "Request Cookie was (Refresh might be needed if changing language) " +
            CookieRequestCultureProvider.DefaultCookieName + "[" + cultureCookieValue + "]";

        text = text1;
    }
    catch (Exception ex)
    {
        text = ex.Message;
    }

    <span>
        @text
    </span>
}

Here are translations for DataTables.net component itself

Here are translations for strings in the application

4 Client-side DataTables.net component

Here I will just show what the ASP.NET view using DataTables component looks like.

<!-- Employees.cshtml -->
<partial name="_LoadingDatatablesJsAndCss" />

@{
    <div class="text-center">
        <h3 class="display-4">Employees table</h3>
    </div>

    <!-- Here is our table HTML element defined. JavaScript library Datatables
    will do all the magic to turn it into interactive component -->
    <table id="EmployeesTable01" class="table table-striped table-bordered ">
    </table>
}

@{
    <!-- Method to tell DatTables.net component which 
    language file to load -->
    string GetUrlForDatatablesLanguageFile()
    {
        string urlResult = String.Empty;

        try
        {
            string culture = Thread.CurrentThread.CurrentUICulture.ToString();

            if (culture.Length > 2)
            {
                culture = culture.Substring(0, 2).ToLower();
            }

            string baseUrl = Url.Content("~/lib/datatables/i18n/");

            switch (culture)
            {
                case "de":
                    urlResult = baseUrl + "de-DE.json";
                    break;
                case "fr":
                    urlResult = baseUrl + "fr-FR.json";
                    break;
                case "it":
                    urlResult = baseUrl + "it-IT.json";
                    break;
                default:
                    urlResult = String.Empty;
                    break;
            }
        }
        catch
        {
            urlResult = String.Empty;
        }

        return urlResult;
    }
}

<script>
    // Datatables script initialization=========================================
    // we used defer attribute on jQuery so it might not be available at this point
    // so we go for vanilla JS event

    document.addEventListener("DOMContentLoaded", InitializeDatatable);

    function InitializeDatatable() {
        $("#EmployeesTable01").dataTable({
            //processing-Feature control the processing indicator.
            processing: true,
            //paging-Enable or disable table pagination.
            paging: true,
            //info-Feature control table information display field
            info: true,
            //ordering-Feature control ordering (sorting) abilities in DataTables.
            ordering: true,
            //searching-Feature control search (filtering) abilities
            searching: true,
            //search.return-Enable / disable DataTables' search on return.
            search: {
                return: true
            },
            //autoWidth-Feature control DataTables' smart column width handling.
            autoWidth: true,
            //lengthMenu-Change the options in the page length select list.
            lengthMenu: [10, 15, 25, 50, 100],
            //pageLength-Change the initial page length (number of rows per page)
            pageLength: 10,

            //order-Initial order (sort) to apply to the table.
            order: [[1, 'asc']],

            //serverSide-Feature control DataTables' server-side processing mode.
            serverSide: true,

            //stateSave-State saving - restore table state on page reload.
            stateSave: true,
            //stateDuration-Saved state validity duration.
            //-1 sessionStorage will be used, while for 0 or greater localStorage will be used.
            stateDuration: -1,

            //language.url- Load language information from remote file.
            language: {
                url: '@GetUrlForDatatablesLanguageFile()'
            },

            //Load data for the table's content from an Ajax source.
            ajax: {
                "url": "@Url.Action("EmployeesDT", "Home")",
                "type": "POST",
                "datatype": "json"
            },

            //Set column specific initialization properties
            columns: [
                //name-Set a descriptive name for a column
                //data-Set the data source for the column from the rows data object / array
                //title-Set the column title
                //orderable-Enable or disable ordering on this column
                //searchable-Enable or disable search on the data in this column
                //type-Set the column type - used for filtering and sorting string processing
                //visible-Enable or disable the display of this column.
                //width-Column width assignment.
                //render-Render (process) the data for use in the table.
                //className-Class to assign to each cell in the column.

                {   //0
                    name: 'id',
                    data: 'id',
                    title: "@Example04.Resources.SharedResource.EmployeeId",
                    orderable: true,
                    searchable: false,
                    type: 'num',
                    visible: true
                },
                {
                    //1
                    name: 'givenName',
                    data: "givenName",
                    title: "@Example04.Resources.SharedResource.GivenName",
                    orderable: true,
                    searchable: true,
                    type: 'string',
                    visible: true
                },
                {
                    //2
                    name: 'familyName',
                    data: "familyName",
                    title: "@Example04.Resources.SharedResource.FamilyName",
                    orderable: true,
                    searchable: true,
                    type: 'string',
                    visible: true
                },
                {
                    //3
                    name: 'town',
                    data: "town",
                    title: "@Example04.Resources.SharedResource.Town",
                    orderable: true,
                    searchable: true,
                    type: 'string',
                    visible: true
                },
                {
                    //4
                    name: 'country',
                    data: "country",
                    title: "@Example04.Resources.SharedResource.Country",
                    orderable: true,
                    searchable: true,
                    type: 'string',
                    visible: true,
                    width: "150px",
                    className: 'text-center '
                },
                {
                    //5
                    name: 'email',
                    data: "email",
                    title: "@Example04.Resources.SharedResource.Email",
                    orderable: true,
                    searchable: true,
                    type: 'string',
                    visible: true
                },
                {
                    //6
                    name: 'phoneNo',
                    data: "phoneNo",
                    title: "@Example04.Resources.SharedResource.PhoneNumber",
                    orderable: false,
                    searchable: true,
                    type: 'string',
                    visible: true
                },
                {
                    //7
                    name: 'actions',
                    data: "actions",
                    title: "@Example04.Resources.SharedResource.Actions",
                    orderable: false,
                    searchable: false,
                    type: 'string',
                    visible: true,
                    render: renderActions
                },
                {
                    //8
                    name: 'urlForEdit',
                    data: "urlForEdit",
                    title: "urlForEdit",
                    orderable: false,
                    searchable: false,
                    type: 'string',
                    visible: false
                }
            ]
        });

        function renderActions(data, type, row, meta) {
            //for Edit button we get Url from the table data
            let html1 =
                '<a class="btn btn-info" href="' +
                row.urlForEdit +
                '"> @Example04.Resources.SharedResource.Edit</a>';

            //for Info button we create Url in JavaScript
            let editUrl = "@Url.Action("EmployeeInfo", "Home")" +
                "?EmployeeId=" + row.id;
            let html2 =
                '<a class="btn btn-info"  style="margin-left: 10px" href="' +
                editUrl + '"> @Example04.Resources.SharedResource.Info</a>';
            return html1 + html2;
        }
    }
</script>

Please note that we need to set up translations on two levels:

  1. Translations for DataTables.net component itself
  2. Translations for Strings used by DataTables.net component

For 1), we use the method GetUrlForDatatablesLanguageFile() to tell DatTables.net component which language file to load.

For 2) we use Resource Manager, as explained in [5].

5 Conclusion

The full example code project can be downloaded at GitHub [99].

6 References

[1] https://datatables.net/

[2] https://www.codeproject.com/Articles/5378651/ASP-NET-8-Multilingual-Application-with-Single-Res

[3] https://www.codeproject.com/Articles/5378997/ASP-NET-8-Multilingual-Application-with-Single-R-2

[4] https://www.codeproject.com/Articles/5379125/ASP-NET-8-Multilingual-Application-with-Single-R-3

[5] https://www.codeproject.com/Articles/5379436/ASP-NET-8-Multilingual-Application-with-Single-R-4

[99] https://github.com/MarkPelf/ASPNET8UsingDataTablesNet

0
Subscribe to my newsletter

Read articles from Mark Pelf directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mark Pelf
Mark Pelf

A Software Engineer from Belgrade, Serbia.