X
dnn.blog.
Back

Creating multilanguage enabled modules

Multilanguage If you want to create your modules for an international audience you must be aware of maintaining different languages. This is no problem for static content because DNN has his concept of resource files, which is very easy to handle. But when it comes to dynamic data like product information in an ecommerce module, you have to handle this in the database. This is no problem at all but which is the best way of entering these different language information in your edit-control ?

Building the UI in the edit control

We build our language entering control with the help of a formview control which is set to edit mode. The HeaderTemplate has a placeholder inside where the flags for the different portal languages are rendered. The EditItemTemplate holds all the Controls we need to save for every language:

Template editing

<asp:formview id="formViewLang" runat="server" allowpaging="false" defaultmode="Edit" onitemcreated="formViewLang_ItemCreated" cssclass="grdCartRow">
    <headertemplate>
        <edititemtemplate>
        </edititemtemplate>
        <footertemplate>
        </footertemplate>
        <table cellspacing="0" cellpadding="2" border="0">
        <tbody>
        <tr>
            <td class="SubHead"><dnn:label id="lblLanguage" runat="server" suffix=":"></dnn:label> </td>
            <td><span style="border-bottom: 1px solid; border-width: 1px; vertical-align: bottom;"></span>
                <asp:placeholder id="phLanguage" runat="server"></asp:placeholder>
                <span style="border-bottom: 1px solid; border-width: 1px; text-align: left; vertical-align: bottom;"></span>
            </td>
        </tr>
        <tr>
            <td valign="top" style="width: 200px;" class="SubHead"><dnn:label id="lblName" runat="server" controlname="txtName" suffix=":"></dnn:label> </td>
            <td><asp:textbox id="txtName" runat="server" columns="40" text="<%# Bind("Name") %>"></asp:textbox> </td>
        </tr>
        <tr> 
             <td valign="top" style="width: 200px;" class="SubHead"><dnn:label id="lblName" runat="server" controlname="txtName" suffix=":"></dnn:label> </td>
             <td><asp:textbox id="txtShort" runat="server" text="<%# Bind("ShortDescription") %>" columns="40" textmode="MultiLine" rows="2" maxlength="80"></asp:textbox> </td>
        </tr> 
        <tr>
            <td valign="top" style="width: 200px;" class="SubHead"><dnn:label id="lblContent" runat="server" controlname="txtContent" suffix=":"></dnn:label> </td>
            <td> <dnn:texteditor id="txtContent" runat="server" height="600" width="500" htmlencode="false" text="<%# Bind("ProductDescription") %>"></dnn:texteditor> </td>
        </tr>
        </tbody>
        </table>
    </headertemplate>
</asp:formview>

DataBase Implementation

For every table xyz which has language related information, you need to create an additional “xyzLang” table which holds the translated strings for every language. Common information, which is equal for every language like itemno or price is stored in the main table.

The Language field in the xyzLang table contains the 5 letter ISO code for the language (eg. en-US, de-DE)

Database scheme

Furthermore, we need the DAL implementation :

1.) Info class for the Lang-Tabelle:

[Serializable()]
public class ProductLangInfo
{
    public ProductLangInfo()
    {
        ProductId = -1;
        Language = "";
        ProductDescription = "";
        ShortDescription = "";
        Name = "";
    }

    public int ProductId { get; set; }
    public string Language { get; set; }
    public string ProductDescription { get; set; }
    public string ShortDescription { get; set; }
    public string Name { get; set; }
}

2.) The CRUD-Methods:

public abstract IDataReader GetProductLangs(int ProductId);
public abstract void NewProductLang(ProductLangInfo ProductLang, string Language);
public abstract void DeleteProductLangs(int ProductId);</pre>

(This is shown here only as abstract definition. The implementation should be no problem for you Winking Smile)

CodeBehind

Properties:

We need a property ProductLangs. Within this property, we save all the language related values in viewstate to persist information between postbacks.

public List <productlanginfo>ProductLangs
{
    get 
    {
        if (ViewState["ProductsLang"] != null)
            return (List<productlanginfo>)ViewState["ProductsLang"];
        else
            return new List<productlanginfo>();
    }
    set 
    {
        ViewState["ProductsLang"] = value; 
    }
}

Page_Load:

Starting the page life cycle we first create a List and add one element for every portal language:

Dictionary <string,>loc = Localization.GetLocales(PortalId);
List <productlanginfo>memLangs = new List<productlanginfo>();
foreach (KeyValuePair<string,locale>item in loc)  
{
    ProductLangInfo li = new ProductLangInfo();
    li.Language = item.Key;
    memLangs.Add(li);
}

Next we read all language records from database for a specific productId, fill all the values into our List and save the result to our Property ProductLangs

List <productlanginfo>dbLangs = Controller.GetProductLangs(productId);
foreach (ProductLangInfo dbLang in dbLangs)
{
    foreach (ProductLangInfo li in memLangs)
    {
        li.ProductId = dbLang.ProductId;
        if (li.Language == dbLang.Language)
        {
            li.Name = dbLang.Name;
            li.ProductDescription = dbLang.ProductDescription;
            li.ShortDescription = dbLang.ShortDescription;
            break;
        }
    }
}
SimpleProductLangs = memLangs;

Page_PreRender:

In Page_Prerender we databind the formview to our List property :

formViewLang.DataSource = SimpleProductLangs;
formViewLang.DataBind();

FormViewLang_ItemCreated:

When the FormViewLang is created, we look into DNN to get the portal languages. Then we search our PlaceHolder in the HeaderTemplate. For every portal language we create a new ImageButton-Control, add a litlle bit styling, the correspondig flag-gif and an ImageClickEventhandler. When a button is clicked, our method “Language_Selected” should be called. All created buttons are added to the Placeholder control.

protected void formViewLang_ItemCreated(object sender, EventArgs e)
{
    Dictionary <string,>loc = Localization.GetLocales(PortalId);
    FormViewRow row = formViewLang.HeaderRow;
    PlaceHolder phLanguage = row.FindControl("phLanguage") as PlaceHolder;
    int i = 0;
    foreach (KeyValuePair <string,>item in loc)
    {
        ImageButton imgBtn = new ImageButton();
        imgBtn.ID = "imgLanguage" + i.ToString();
        imgBtn.Click += new System.Web.UI.ImageClickEventHandler(Language_Selected);
        imgBtn.ImageUrl = "~\\images/\Flags\\" + item.Key + ".gif";
        imgBtn.Style.Add("padding", "3px 10px 5px 10px");

        imgBtn.Style.Add("border-width", "1px");
        imgBtn.Style.Add("border-color", "#000000");
        imgBtn.Style.Add("margin", "0 px");
        if (i == formViewLang.PageIndex)
        {
            imgBtn.Style.Add("border-style", "solid solid none solid");
            imgBtn.Style.Add("background-color", "White");
        }
        else
        {
            imgBtn.Style.Add("border-style", "solid solid solid solid");
        }
        phLanguage.Controls.Add(imgBtn);
        i++;
    }
}

Language_Selected:

When a flag button is clicked, we save all values from the EditItemTemplate in our List property. Then we change the PageIndex of the Formview to the selected language (We can determine the selected language ISO code from the name of the gif file)

protected void Language_Selected(object sender, System.Web.UI.ImageClickEventArgs e)
{
    TextBox txtName = formViewLang.FindControl("txtName") as TextBox;
    ProductLangs[formViewLang.PageIndex].Name = txtName.Text;
    TextBox txtShort = formViewLang.FindControl("txtShort") as TextBox; 
    ProductLangs[formViewLang.PageIndex].ShortDescription = txtShort.Text; 
    TextEditor txtContent = formViewLang.FindControl("txtContent") as TextEditor;
    ProductLangs[formViewLang.PageIndex].ProductDescription = txtContent.Text;

    ImageButton btn = sender as ImageButton;
    string url = btn.ImageUrl;
    string language = url.Substring(url.LastIndexOf('\\') + 1);
    language = language.Replace(".gif", "");
    int i = 0;
    foreach (ProductLangInfo li in ProductLangs)
    {
        if (li.Language == language)
        {
            formViewLang.PageIndex = i;
            break;
        }
        i++;
    }
}

cmdUpdate_Click:

When we leave the edit control with the help of an update button, we have to save all language information in the database. Easiest way is to delete all language information first and then store everything new (so you don’t have to deal with inserts,updates and deletes). Don't forget to read out the latest information from your control in your property before saving (see Language_Selected):

Controller.DeleteProductLangs(productId);
foreach (ProductLangInfo si in ProductLangs)
{
    si.ProductId = productId;
    Controller.NewProductLang(si, si.Language);
}

Hope I could have helped you with this. Comments, corrections and advancements are appreciated!

Back

about.me.

Torsten WeggenMy name is Torsten Weggen and I am CEO of indisoftware GmbH in Hanover, Germany. I'm into DNN since 2008. Before this, I did a lot of desktop stuff mainly coded with Visual Foxpro (see http://www.auktionsbuddy.de). 

I'm programmer, husband, father + born in 1965.

Please feel free to contact me if you have questions.

Latest Posts

DNN module development with Angular 2+ (Part 7)
6/10/2018 1:43 PM | Torsten Weggen
DNN module development with AngularJS (Part 6)
12/16/2016 7:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 5)
12/16/2016 6:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 4)
12/16/2016 5:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 3)
12/16/2016 4:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 2)
12/16/2016 3:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 1)
12/15/2016 7:19 AM | Torsten Weggen
Blogging in DNN with Markdown Monster by Rick Strahl
11/27/2016 1:14 PM | Torsten Weggen
Creating a global token engine
11/18/2016 10:25 AM | Torsten Weggen
DnnImagehandler - Hot or not ?
2/21/2015 11:52 PM | Torsten Weggen

My Twitter

Torsten Weggen 7/24/2021

@JohannesFinke https://t.co/3WGmhg3eN0 #keinrechterweg

Torsten Weggen 7/24/2021

@RolandHanske @Stefani38312507 @YouTube https://t.co/3WGmhg3eN0 #keinrechterweg

Torsten Weggen 7/24/2021

@zardorak https://t.co/3WGmhg3eN0 #keinrechterweg