Back to Insights

Localizing SharePoint pages with resource files

Recently I was involved with a localization project that also required us to localize ASPX pages in our SharePoint solution. I have read this very good article demonstrating how to copy resource files to the "App_GlobalResources" folder of the web application so they can be consumed by the ASPX pages. I really like this approach, but every time a new resource file is introduced or an existing resource file is updated we need to re-run the timer job feature to refresh my resource files. This introduces additional deployment and management costs.

Hence we would like to take another step to the approach. As long as the resource files are delivered through a SharePoint solution file, SharePoint will automatically deploy all the files to the 12 hive folder across the farm. Once the resource files are in the "12\Resources" folder, the "SPUtility.GetLocalizedString" API can consume them. To encapsulate this implementation our GetString method can be:

using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace ASPXResources
{
    public static class SPResources
    {
        public static string GetString(string className, string fieldName)
        {
            SPWeb web = SPContext.Current.Web;

            int langID = (web == null) ? 1033 : web.Locale.LCID;

            return SPUtility.GetLocalizedString(String.Format("$Resources:{0},{1}", className, fieldName), className, (uint)langID);
        }
    }
}
The method automatically uses the current site locale to display text in the right language. In order for ASPX / ASCX pages to display resource string as ASP.NET expression, we can use a custom ResourceExpressionBuilder.
using System;
using System.Collections.Generic;
using System.CodeDom;
using System.Web;
using System.Web.Compilation;
using System.Web.UI;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace ASPXResources
{
    public class SPResourceExpressionBuilder : ResourceExpressionBuilder
    {
        ///
        /// A simple internal class to represent a parsed resource string key
        ///
        class ResourceEntry
        {
            public string Class;
            public string Field;
        }

        ///
        /// Return the CodeDOM expression which simply calls to MyResources.GetString method
        ///
        public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
        {
            ResourceEntry resEntry = parsedData as ResourceEntry;

            if (resEntry == null)
            {
                throw new InvalidOperationException();
            }

            CodeMethodInvokeExpression invokeExp = new CodeMethodInvokeExpression(
                new CodeTypeReferenceExpression(typeof(SPResources)), "GetString",
                new CodePrimitiveExpression(resEntry.Class), new CodePrimitiveExpression(resEntry.Field)
                );

            return invokeExp;
        }

        ///
        /// Parse the input expression string from page to ResourceEntry class
        ///
        public override object ParseExpression(string expression, Type propertyType, ExpressionBuilderContext context)
        {
            if (String.IsNullOrEmpty(expression))
            {
                throw new HttpException("SPResource: [" + expression + "] is not found");
            }

            string[] parts = expression.Split(',');

            string className;
            string fieldName;

            if (parts.Length == 1)
            {
                className = "";
                fieldName = parts[0].Trim();
            }
            else
            {
                className = parts[0].Trim();
                fieldName = parts[1].Trim();
            }

            ResourceEntry field = new ResourceEntry() { Class = className, Field = fieldName };

            return field;
        }
    }
}

The last thing you need to do is register the resource expression builder in web.config:

<compilation batch="false" debug="false">
  <assemblies>
    ...
    <add assembly="ASPXResources, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..." />
  </assemblies>
  <expressionBuilders>
    ...
    <add expressionPrefix="SPRes" type="ASPXResources.SPResourceExpressionBuilder, ASPXResources, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..." />
  </expressionBuilders>
</compilation>

The web.config modification should be automated using the feature receiver. Once all these framework pieces are in place, any new or updated resource files updated through SharePoint solution will automatically be available for use.

<asp:Label runat='server' Text='<%$SPRes: ResourceFile1, ResourceEntry1 %>' />

Share