Click here to Skip to main content
Click here to Skip to main content

Creating Multilingual Websites - Part 3

By , 1 Nov 2005
 
Prize winner in Competition "ASP.NET Oct 2005"

Table of Contents

Introduction

Over a year ago, I wrote a two part article on creating multilingual web applications in ASP.NET. The first article focused on a custom resource manager which solved a lot of problems associated with the built-in functionality, as well as a set of custom server controls which made creating multilingual websites painless. The second article covered a number of issues, including URL rewriting, data model design and enhanced custom server controls. This third part won't focus on the fundamental but rather enhancements to what we've already covered. Some of the features, notably the first, will greatly increase the complexity of our solution. If you don't need it, I recommend you don't implement it - none of the other features require it.

Readers who haven't read the first two parts of this series will be utterly left out, so go read them now! (Part 1, Part 2)

Database Driven

The first and most significant change will be to modify the ResourceManager to support a variety of sources for localized content. Making the ResourceManager support SQL Server had always been intended from the start, but in the end, a simpler codebase was chosen. In the projects I'm involved with, XML files are still my preference, but your project might have good reasons to use something else.

Provider Factory Pattern

The approach we'll use to make our ResourceManager agnostic of the underlying storage mechanism is the Provider Factory Pattern. This pattern is at the center of many ASP.NET 2.0 features, so you should get familiar with it. Without going into too much detail, the Provider Factory Pattern works by using an abstract class to publish an interface and relies on other classes to implement the functionality. In other words, we'll still have our ResourceManager class which will be a shell as well as a ResourceManagerXml class and a ResourceManagerSql class. The design pattern is powerful because it allows third parties to create their own implementation. Anyone could create a new class called ResourceManagerAccess which inherits from ResourceManager and implements the logic needed to work with an Access database. You can configure which implementation you'll use via the web.config. The code we'll go over should give you a good hands-on feel for the design pattern, but if you are interested in knowing more, make sure to visit MSDN.

ResourceManager and ResourceManagerXml

The first step is to turn our ResourceManager into an abstract class which defines what members our providers (those that implement the actual logic) will have to implement.

public abstract class ResourceManager
{
   protected abstract string RetrieveString(string key);
}

It'd be nice to reuse the name GetString, but since we want to keep our ResourceManager compatible with the previous version, we must use a new name. If you are unfamiliar with the abstract keyword on a member, it simply means that any classes which inherit from ResourceManager must implement the RetrieveString function. Note also that if any member of a class is abstract, the class itself must be marked as abstract. This means you can't create a new instance of the class. What would happen if you created a new instance of ResourceManager and then tried to call the unimplemented RetrieveString function? Having a class that you can't create an instance of might seem like a waste of bytes, but let's see how it's actually a solid use of OO design. We now create our ResourceManagerXml class, which is XML-aware.

public class ResourceManagerXml: ResourceManager
{
   protected override string RetrieveString(string key)
   {
      NameValueCollection messages = GetResource();
      if (messages[key] == null)
      {
         messages[key] = string.Empty;
#if DEBUG
         throw new ApplicationException("Resource value not found for key: " + key);
#endif
      }
      return messages[key];
   }
}

As you can see, all we've done is create a layer of abstraction and move the GetString functionality from the previous version of the ResourceManager here. We've also moved all supporting functions out of the ResourceManager and into the ResourceManagerXml class, namely the private functions GetResource and LoadResource (not shown here).

Since ResourceManagerXml inherits from ResourceManager, we can cast it to ResourceManager, much like a string can be cast to an object. This is where we tie the two classes together. Inside the ResourceManager class, we create a property called Instance:

public abstract class ResourceManager
{
   internal static ResourceManager Instance
   {
       get { return new ResourceManagerXml(); }
   }

   public static string GetString(string key)
   {
      return Instance.RetrieveKey(key);
   }

   protected abstract string RetrieveString(string key);
}

As is hopefully clear, GetString now calls RetrieveString of the ResourceManagerXml class, through the Instance method. If we now create a ResourceManagerSql and make the Instance property return an instance of it, the call to RetrieveString will be handled by the SQL implementation. To be truly useful, our ResourceManager's implementation is slightly more complicated. It dynamically creates the child class based on values in the web.config. That means if you want to switch from XML to SQL Server, you don't need to change the class and recompile, but simply change values in the configuration file. Here's the actual implementation of the relevant code:

public abstract class ResourceManager
{
   private static ResourceManager instance = null;
      
   static ResourceManager()
   {
      Provider provider = LocalizationConfiguration.GetConfig().Provider;
      Type type = Type.GetType(provider.Type);
      if (type == null)
      {
         throw new ApplicationException(string.Format("Couldn't" + 
                               " load type: {0}", provider.Type));
      }
      object[] arguments = new object[] {provider.Parameters};
      instance = (ResourceManager) Activator.CreateInstance(type, arguments);
   }

   internal static ResourceManager Instance
   {
      get { return instance; }
   }
}

The static construct (which is automatically private and guaranteed by .NET to be called only once when any member of the ResourceManager is first called) gets a provider from the configuration, tries to get the type and instantiates the object. The Type.GetType method is able to take a string in the form or Namespace.ClassName, AssemblyName and create a Type object, which can then be instantiated with the Activator.CreateInstance. There's a performance penalty to this type of dynamic invocation, so we store the instance in a private static variable which will be used throughout the lifetime of the application (in other words, the expensive code only fires once). We won't go over the changes made to the LocalizationConfiguration to support the provider (the downloadable code is well documented), but it basically supports the following type of data in our web.config:

<Localization 
   ...
   providerName="XmlLocalizationProvider"
 >
   <Provider> 
      <add 
         name="XmlLocalizationProvider"
         type="Localization.ResourceManagerXml, Localization"
         languageFilePath="c:\inetpub\wwwroot\localizedSample\Language"
      />
      <add 
         name="SqlLocalizationProvider"
         type="Localization.ResourceManagerSql, Localization"
         connectionString="Data Source=(local);Initial 
                           Catalog=DATABASE;User Id=sa;Password=PASSWORD;"
      />
   </Provider>
</Localization>

Multiple providers are supported, but only the one specified by the providerName will be loaded. The name and the type attributes of each provider is used to load the actual instance, all other attributes are passed to the constructor of the provider as a NameValueCollection. We'll see an example of this next, when we take a look at our ResourceManagerSql.

ResourceManagerSql

The framework is now in place to use any technology for storing localized content. Creating one to work with SQL Server requires only three steps: creating our database model, the ResourceManagerSql class and the necessary stored procedure. The model we'll use is similar to the approach for normalizing content discussed in Part 2 of this series. A simpler, less normalized model could also be adopted. Also, we've specified a value of 1024 characters, but we could pick a NVARCHAR up to 4196 or even a Text field. It would even be possible to use a VARCHAR and Text column and pull from one or the other (not the prettiest design, but it might be necessary and practical).

Data Model

Next we create the ResourceManagerSql class. This class is very similar to the XML one, except that the LoadResources method interacts with SQL Server via the System.Data classes rather than XML files. We start with the constructor which, as we saw in the previous section, is dynamically called and passed a NameValueCollection:

private string connectionString;
private int cacheDuration;

public ResourceManagerSql(NameValueCollection parameters)
{
   if (parameters == null || parameters["connectionString"] == null)
   {
      throw new ApplicationException("ResourceManagerSql" + 
            " requires connectionString attribute in configuraiton.");
   }
   connectionString = parameters["connectionString"];

   //load the optional cacheDuration parameter, 
   //else we'll cache for 30 minutes
   if (parameters["cacheDuration"] != null)
   {
      cacheDuration = Convert.ToInt32(parameters["cacheDuration"]);
    }
    else
    {
       cacheDuration = 30;
    }
}

No magic is happening here, read the mandatory connectionString parameter (or throw an exception if there isn't one) and read the optional cacheDuration parameter or use a default value.

Since our class inherits from ResourceManager, it must implement the RetrieveString method. This method is identical to the XML equivalent:

protected override string RetrieveString(string key)
{
   NameValueCollection messages = GetResources();
   if (messages[key] == null)
   {
      messages[key] = string.Empty;
#if DEBUG
      throw new ApplicationException("Resource value" + 
                         " not found for key: " + key);
#endif
   }
   return messages[key];
}

The real difference happens in the GetResources and LoadResources methods:

private NameValueCollection GetResources()
{
   string currentCulture = ResourceManager.CurrentCultureName;
   string defaultCulture = LocalizationConfiguration.GetConfig().DefaultCultureName;
   string cacheKey = "SQLLocalization:" + defaultCulture + ':' + currentCulture;
   NameValueCollection resources = (NameValueCollection) HttpRuntime.Cache[cacheKey];
   if (resources == null)
   {
      resources = LoadResources(defaultCulture, currentCulture);
      HttpRuntime.Cache.Insert(cacheKey, resources, null, 
                  DateTime.Now.AddMinutes(cacheDuration), 
                              Cache.NoSlidingExpiration);
   }
   return resources;
}

private NameValueCollection LoadResources(string defaultCulture, 
                                          string currentCulture)
{
   SqlConnection connection = null;
   SqlCommand command = null;
   SqlDataReader reader = null;
   NameValueCollection resources = new NameValueCollection();
   try
   {
      connection = new SqlConnection(connectionString);
      command = new SqlCommand("LoadResources", connection);
      command.CommandType = CommandType.StoredProcedure;
      command.Parameters.Add("@DefaultCulture", 
              SqlDbType.Char,5).Value = defaultCulture;
      command.Parameters.Add("@CurrentCulture", 
              SqlDbType.Char,5).Value = currentCulture;
      connection.Open();
      reader = command.ExecuteReader(CommandBehavior.SingleResult);
      int nameOrdinal = reader.GetOrdinal("Name");
      int valueOrdinal = reader.GetOrdinal("Value");
      while(reader.Read())
      {
         resources.Add(reader.GetString(nameOrdinal), 
                     reader.GetString(valueOrdinal));
      }
   }
   finally
   {
      if (connection != null)
      {
         connection.Dispose();
      }
      if (command != null)
      {
         command.Dispose();
      }
      if (reader != null && !reader.IsClosed)
      {
         reader.Close();
      }
   }
   return resources;
}

This should be similar to any SQL code you've written before. The main difference with this approach and the XML one is that we've pushed the fallback logic to the stored procedure. This helps reduce calls to the database. Also, since we can't add a FileDependency for our cache, we set an absolute expiration time, changeable via the web.config.

Finally, all that's left is the LoadResources stored procedure:

CREATE PROCEDURE LoadResources
(
  @DefaultCulture CHAR(5),
  @CurrentCulture CHAR(5)
)
AS
SET NOCOUNT ON

  SELECT R.[Name], RL.[Value]
   FROM Resources R 
      INNER JOIN ResourcesLocale RL ON R.Id = RL.ResourceId
      INNER JOIN Culture C ON RL.CultureId = C.CultureId
   WHERE C.Culture = @CurrentCulture
   
   UNION ALL

  SELECT R.[Name], RL.[Value]
   FROM Resources R 
      INNER JOIN ResourcesLocale RL ON R.Id = RL.ResourceId
      INNER JOIN Culture C ON RL.CultureId = C.CultureId
   WHERE C.Culture = @DefaultCulture
    AND R.[Name] NOT IN (
        SELECT [Name] FROM Resources R2 
          INNER JOIN ResourcesLocale RL2 ON R2.Id = RL2.ResourceId
          INNER JOIN Culture C2 ON RL2.CultureId = C2.CultureId
       WHERE C2.Culture = @CurrentCulture
    )
SET NOCOUNT OFF

The stored procedure is made a little more complex than might seem necessary. However, our data model is well normalized, meaning we need multiple JOINs and we decided to push the fallback logic to the stored procedure.

Final Considerations

There's some shared code between the XML and SQL implementations. This functionality could be pushed into the abstract ResourceManager. For example, the RetrieveString method (which is identical in both cases) could be placed in ResourceManager and instead the GetResource method could be abstract. Similarly, caching could be implemented in the ResourceManager rather than in each implementation. However, you can never tell how a specific provider will be implemented, and I'd hate to make an assumption that would make a provider difficult to develop. For example, just because the SQL and XML providers make use of a NameValueCollection, doesn't mean an Oracle one would.

Something else to keep in mind is that the interface of ResourceManager is well defined, but the internal implementation is totally private. This means you can make changes and tweaks as you see fit without having to worry about breaking existing code. The provider model itself promotes good programming practices that you should try to emulate, when appropriate, in your own code.

Image Support

The next feature we'll add is support for localizing images. This is a useful demonstration of how to localize a group of content. An image has three typical values that need to be localized, the height, width and alt tags. Height and width might not make any sense, but images of words will often be of different length depending on the culture. The solution I've seen for this in the past is to reuse the GetString method and a custom naming convention. For example, I've often seen:

img.Alt = ResourceManager.GetString("Image_Welcome_Alt");
img.Width = Convert.ToInt32(ResourceManager.GetString("Image_welcome_Width));
img.Height = Convert.ToInt32(ResourceManager.GetString("Image_Welcome_Height"));

This solution is both inelegant and error prone. Instead we'll build a GetImage method which returns a LocalizedImageData. LocalizedImageData is a simple class that contains all our localized properties:

public class LocalizedImageData
{
   private int width;
   private int height;
   private string alt;

   public int Width
   {
     get { return width; }
     set { width = value; }
   }
   public int Height
   {
     get { return height; }
     set { height = value; }
   }
   public string Alt
   {
     get { return alt; }
     set { alt = value; }
   }

   public LocalizedImageData()
   {
   }
   public LocalizedImageData(int width, 
                int height, string alt)
   {
      this.width = width;
      this.height = height;
      this.alt = alt;
   }
}

Now our GetImage function has something to return - a class that groups all the localized content. We implement the GetImage function in the ResourceManager. Like the newest GetString we covered in the first section of this article, it'll rely on an abstract RetrieveImage function which each provider will have to implement. We'll only cover the XML implementation in this article, but the downloadable code also has it implemented in the SQL class.

First we create the GetImage function in ResourceManager:

public static LocalizedImageData GetImage(string key)
{
   return Instance.RetrieveImage(key);
}

Next we create the abstract RetrieveImage function:

protected abstract LocalizedImageData RetrieveImage(string key);

Finally, we implement RetrieveImage in the ResourceManagerXml class:

protected override LocalizedImageData RetrieveImage(string key)
{
   Hashtable imageData = GetImages();
   if (imageData[key] == null)
   {
      imageData[key] = new LocalizedImageData(0,0,string.Empty);
#if DEBUG
      throw new ApplicationException("Resource value not found for key: " + key);
#endif
   }
   return (LocalizedImageData) imageData[key];
}

We go over this code quickly because it's almost identical to the GetString method. Instead of calling LoadResources however, GetImages is called. This is where the code starts to get a little different. We could have used the same XML file to store a new type of data, but decided a different file might help keep things clean. The main difference happens in the parsing of the XML file:

private void LoadImage(Hashtable resource, string culture, string cacheKey)
{
   string file = string.Format("{0}\\{1}\\Images.xml", fileName, culture);
   XmlDocument xml = new XmlDocument();
   xml.Load(file);
   foreach (XmlNode n in xml.SelectSingleNode("Images"))
   {
      if (n.NodeType != XmlNodeType.Comment)
      {
         LocalizedImageData data = new LocalizedImageData();
         data.Alt = n.InnerText;
         data.Height = Convert.ToInt32(n.Attributes["height"].Value);
         data.Width = Convert.ToInt32(n.Attributes["width"].Value);
         resource[n.Attributes["name"].Value] = data;
      }
   }
   HttpRuntime.Cache.Insert(cacheKey, resource, 
               new CacheDependency(file), 
               DateTime.MaxValue, TimeSpan.Zero);
}

Since the XML structure is a little more complex, there's more work to be done in the LoadImages function, but in reality, it's all pretty straightforward. The XML file we use looks something like:

<Images>
   <item name="Canada" width="10" height="10">Canada!</item>
   ...
   ...
</Images>

Finally, the last step is to create our localized controls:

public class LocalizedHtmlImage : HtmlImage, ILocalized
{
   private const string imageUrlFormat = "{0}/{1}/{2}";
   private string key;
   private bool colon = false;
   public bool Colon
   {
      get { return colon; }
      set { colon = value; }
   }
   public string Key
   {
      get { return key; }
      set { key = value; }
   }

   protected override void Render(HtmlTextWriter writer)
   {
      LocalizedImageData data = ResourceManager.GetImage(key);
      if (data != null)
      {
         base.Src = string.Format(imageUrlFormat, 
                    LocalizationConfiguration.GetConfig().ImagePath, 
                    ResourceManager.CurrentCultureName, base.Src);
         base.Width = data.Width;
         base.Height = data.Height;
         base.Alt = data.Alt;
      }
      if (colon)
      {
         base.Alt += ResourceManager.Colon;
      }
      base.Render(writer);
   }
}

Like all localized controls, our class implements the ILocalized interface which defines the two properties Key and Colon. Rather than calling GetString in the Render method however, we call GetImage and set the appropriate image values based on the returned LocalizedImageData class. Finally, note that the src of the image is also localized. Basically, if you specify src="Welcome.gif", it'll be turned into src="/images/en-CA/welcome.gif" assuming your web.config specified "/images/" as the ImagePath and en-Ca as the current culture.

We went through the image localization exercise rather quickly. This is in large part due to the similarity with the existing code. Almost all other groups of localized data can be done the same way. For example, you could localize emails (subject and body) using the same approach.

JavaScript Localization

We've done a good job of providing all the necessary tools one would need to provide a rich multilingual experience to our users. One aspect of our UI still needs to have some localization capabilities: JavaScript. With the growing popularity of AJAX, JavaScript's role will only grow. Even the most common JavaScript validation needs to be localized. We need to build some functionality directly into the client.

Our design will be to try and emulate in JavaScript what we've already built server-side. Ideally, we want to be able to do ResourceManager.GetString(XYZ); in JavaScript and get the localized value. One solution would be to use AJAX, but that might be too intensive for many applications. Instead, we'll create a couple utility functions which dump localized content into a JavaScript array. We'll wrap the array in a JavaScript object which exposes a GetString method. Thanks to some unique features of JavaScript, the code is surprisingly compact. The downside is that the entire localized content won't be available, rather we'll have to specify which values we want available during Page_Load. First we'll look at the JavaScript methods:

var ResourceManager = new RM();
function RM()
{
  this.list = new Array();
};
RM.prototype.AddString = function(key, value)
{
  this.list[key] = value;
};
RM.prototype.GetString = function(key)
{
  var result = this.list[key];  
  for (var i = 1; i < arguments.length; ++i)
  {
    result = result.replace("{" + (i-1) + "}", arguments[i]);
  }
  return result;
};

If you aren't familiar with JavaScript objects, the above code might seem a little odd. Basically we create a new instance of the RM class (client-side), which just contains an array. Next we create two members, AddString and GetString. You may not know this, but JavaScript arrays don't need to be indexed by integers. Rather, they can be associative (like Hashtable). This is obviously fundamental to how our client-side ResourceManager works. If we add a value to an array based on a key, we can easily retrieve the value via that same key. The above GetString function also supports placeholders (something we worked hard at to achieve in Part 2). JavaScript allows a dynamic amount of parameters to be passed into a function. GetString() assumes the first parameter is the key of the resource to get, and all subsequent parameters to be placeholder values. For example, to use the "InvalidEmail" resource of "{0} is not a valid email", we'd do:

ResourceManager.GetString("InvalidEmail", email.value);

We'll use a little server-side utility function to dump localized content into the client-side array:

public static void RegisterLocaleResource(string[] keys)
{
   if (keys == null || keys.Length == 0)
   {
      return;
   }
   Page page = HttpContext.Current.Handler as Page;
   if (page == null)
   {
      throw new InvalidOperationException("RegisterResourceManager" + 
                               " must be called from within a page");
   }
   StringBuilder sb = new StringBuilder("<script language="\""JavaScript\">");
   sb.Append(Environment.NewLine);
   foreach (string key in keys)
   {
      sb.Append("ResourceManager.AddString('");
      sb.Append(PrepareStringForJavaScript(key));
      sb.Append("', '");
      sb.Append(PrepareStringForJavaScript(ResourceManager.GetString(key)));
      sb.Append("');");
      sb.Append(Environment.NewLine);
   }
   sb.Append("</script>");
   page.RegisterStartupScript("RM:" + string.Join(":", keys), sb.ToString());
}

The function is called with one or more keys. But instead of returning a localized value, the value is added to the client-side ResourceManager. In other words, before we can use the InvalidEmail resource, we need to call ResourceManager.RegisterLocaleResource("InvalidEmail");. Again, we can pass multiple values if we want, such as ResourceManager.RegisterLocaleResource("InvalidEmail", "InvalidUsername", "Success");. You can call RegisterLocaleResource multiple times as well. This is ideal if your user controls require specific localized content.

LocalizedNoParametersLiteral Haunts Me!

Nothing we've done so far should break existing code. However, in Part 2 we created a LocalizedNoParametersLiteral server control. This was a mistake - to be honest I don't know what I was thinking. The downloadable sample renames LocalizedNoParametersLiteral to LocalizedLiteral and LocalizedLiteral is now LocalizedLabel. If necessary, you can rename them to their old names to avoid broken code, but it's something that I just had to fix this time around.

Conclusion

In this part we covered three major enhancements:

  • The Provider Model and SQL capabilities,
  • The built-in image support, and
  • The JavaScript functionality.

In addition to changing the names of the the LocalizedNoParametersLiteral and LocalizdLiteral around, the downloadable sample has a few other minor code changes. These changes should have no impact on your existing code as they are merely minor improvements.

Aside from being an actual useful library, it's my hope that the Creating Multilingual Websites series made use of strong design practices that you'll be able to make use of in your own code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Karl Seguin
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionMedium TrustmemberMember 834764227 Apr '12 - 10:13 
Is there a way that I can use the code under medium trust?
It works fine locally, but as soon as I access it from my webhost, I get an error message.
When I add Medium Trust to the web.config, it also fails locally.
QuestionUrgently Help NeededmemberMember 137496219 Nov '11 - 5:41 
Firstly I want to thank Karl For all this 3 very nice post by him and others who has posted & concerns & comments . I am new to ASP .NET and assigned a project to build a website using ASP .NET MVC 3 / 4. Website has be multilingual. I want to design a database and perform all coding using asp .net mvc 3 or 4 versions. Please let me know how do I start with database design with simple examples such as posting the POSTS in multilingual and storing in database, LIST ITEMS in many langs, Label, Images etc.
Coutries, States/Regions etc; Please can you give me data if you have?
 
Karl Sir, I see you have Images table, ImageLocale, Resoruces table. Can you bit explain how it works ImageLocale is not having any relation with Images table?
 
CREATE TABLE Resources
(
  [Id] INT NOT NULL PRIMARY KEY IDENTITY (1,1),
  [Name] VARCHAR(64) NOT NULL
)
 
CREATE TABLE ResourcesLocale
(
   [LocaleId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
   [ResourceId] INT NOT NULL,
   [CultureId] INT NOT NULL,
   [Value] VARCHAR(1024) NOT NULL
)
CREATE TABLE ImagesLocale
(
   [LocaleId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
   [ResourceId] INT NOT NULL,
   [CultureId] INT NOT NULL,
   [Width] INT NOT NULL,
   [Height] INT NOT NULL,
   [Alt] VARCHAR(1024) NOT NULL
)
CREATE TABLE Images
(
  [Id] INT NOT NULL PRIMARY KEY IDENTITY (1,1),
  [Name] VARCHAR(64) NOT NULL
)
CREATE TABLE Culture
(
  [CultureId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
  [Culture] CHAR(5) NOT NULL,
  [DisplayName] VARCHAR(64) NOT NULL
)
 

 
Waiting urgent response please.
 
Thank you in advance.
 
Regards
VKA
AnswerRe: Urgently Help NeededmemberKarl Seguin19 Nov '11 - 6:01 
Rename the ResourceId column in ImagesLocal to ImageId, and that's your link.
 
Karl
QuestionUrgently Help NeededmemberMember 137496219 Nov '11 - 5:39 
Firstly I want to thank Karl For all this 3 very nice post by him and others who has posted & concerns & comments . I am new to ASP .NET and assigned a project to build a website using ASP .NET MVC 3 / 4. Website has be multilingual. I want to design a database and perform all coding using asp .net mvc 3 or 4 versions. Please let me know how do I start with database design with simple examples such as posting the POSTS in multilingual and storing in database, LIST ITEMS in many langs, Label, Images etc.
 
Karl Sir, I see you have Images table, ImageLocale, Resoruces table. Can you bit explain how it works ImageLocale is not having any relation with Images table?
 
CREATE TABLE Resources
(
  [Id] INT NOT NULL PRIMARY KEY IDENTITY (1,1),
  [Name] VARCHAR(64) NOT NULL
)
 
CREATE TABLE ResourcesLocale
(
   [LocaleId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
   [ResourceId] INT NOT NULL,
   [CultureId] INT NOT NULL,
   [Value] VARCHAR(1024) NOT NULL
)
CREATE TABLE ImagesLocale
(
   [LocaleId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
   [ResourceId] INT NOT NULL,
   [CultureId] INT NOT NULL,
   [Width] INT NOT NULL,
   [Height] INT NOT NULL,
   [Alt] VARCHAR(1024) NOT NULL
)
CREATE TABLE Images
(
  [Id] INT NOT NULL PRIMARY KEY IDENTITY (1,1),
  [Name] VARCHAR(64) NOT NULL
)
CREATE TABLE Culture
(
  [CultureId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
  [Culture] CHAR(5) NOT NULL,
  [DisplayName] VARCHAR(64) NOT NULL
)
 

 
Waiting urgent response please.
 
Thank you in advance.
 
Regards
VKA
Generalpopulate a grid at runtime with culturememberabhinav113 Jun '07 - 2:04 
hi,
 
I am working on localization for a website.
 
I wnt to know that how can we set CurrentCulture and CurrentUICulture at runtime.
 
I want to populate a grid with local resource file after selecting desired culture from dropdownlist using which i get the value and current culturename.
 
I have tried but no results.
 
My code is:
 
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
------
-----
ComboBox1.DataSource = myDS
ComboBox1.DataBind()
ComboBox1.SelectedIndex = 0
End If
 
userCulture = ComboBox1.SelectedItem.Text.Trim()
Dim c As CultureInfo = New CultureInfo(userCulture)
 
If userCulture <> "" Then
Dim ci As New System.Globalization.CultureInfo(userCulture)
If (Not ci.IsNeutralCulture) Then
' only set the CurrentCulture if this is a 'specific culture'
System.Threading.Thread.CurrentThread.CurrentCulture = ci
End If
 
System.Threading.Thread.CurrentThread.CurrentUICulture = ci
 
End If
 
End Sub
 
----------------------------
 

Thanks for any help

 
kamal
GeneralRe: populate a grid at runtime with culturememberKarl Seguin13 Jun '07 - 14:18 
Are you getting an error or it just doesn't work?
 
It looks straightforward enough. I did notice that you are using userCulture before checking if it's empty...you could certainly reorganize and clean up the code a bit (to prevent possible null references)...but aside from that, have you tried stepping through the code and making sure everyhting is as it should be?
 

GeneralRe: populate a grid at runtime with culture [modified]memberabhinav113 Jun '07 - 20:03 
Hi Karl,
 
Thanks for reply.
 
I have you tried stepping through the code and made sure that everyhting is as it should be.
 
'Userculture' is not null because I set it from dropdown.selectedItem.text.
 
Selected culture is also in userculture.
CultureInfo is also ok(with respect to whatever culture selected.)
 
I dont know why desired resx file is not selected by resourcemanager OR I m missing somthing in my code? OR It might be wrong placed in code,where I set selected culture.
 
One more thing is that when Culture is set from Browser(IE), Then it is OK but then dropdown losts its usability because every time user has to set culture from IE (In this condition Data comes according to Culture from database in grid but GridUI is always IE Language setting dependent.

 

kamal
 

-- modified at 4:15 Thursday 14th June, 2007
QuestionResource files or databasememberTeachesOfPeaches28 May '07 - 8:27 
Helo,
 
I would like to localize my existing website.So I try to find out what is the best approach to do this. When I've read your article I was a little bit confused when you sayed that one of the drawbacks using the resource files is that 'Resource files are embedded into [satellite] assemblies'
Is it always the case or is it also possible to work with recource files without creating the satellite assemblies?
And the second question is:
are the reasons, you've mentioned, for not using the resource files still valid even when I use the framework 2.0?
 
Regards, Andreas
AnswerRe: Resource files or databasememberKarl Seguin28 May '07 - 14:09 
Yes, using the default resources files of .NET, they are always embedded into the assembly. This might actually be a useful if you don't want others to change the resources...but it isn't flexible. With your own custom XML files, you can either embedded them or not.
 
And yes,most of the drawbacks in 1.x are still there in 2.x They made it slightly easier to use, but nothing really worth mentioning.
GeneralRe: Resource files or databasememberTeachesOfPeaches10 Jun '07 - 9:57 
hi, I have found this article saying that in the Web Application Project model the resource files are published as editable raw .resx files. Is that correct?
 
http://geekswithblogs.net/vivek/archive/2006/12/14/101119.aspx
 
Because I want to keep my localized data in a database I'm going to implement your solution anyway but I would like to know if I'm missing a point in this article.Confused | :confused:
 
Thanks, Andreas

GeneralRe: Resource files or databasememberKarl Seguin10 Jun '07 - 10:58 
well, you have full control over what's embedded into the the dlls and what isn't. I wasn't aware that with the web application project that resx files, by default, weren't embedded. I'm quite surprised actually.
QuestionUrl Rewrite Path ProblemmemberOzLand1 Mar '07 - 23:06 
How About if you want to rewrite the url into something nicer?
im looking at diferent articles and yours which is very nice seems doesnt allow me to rewrite the path into something localized depending on the culture. If so, could you enlight me.
Need to finish this and its the last in the list!
Appreciatte so much your article and the time taken to write it!
Thanks!
QuestionRe: Url Rewrite Path ProblemmemberKarl Seguin2 Mar '07 - 1:58 
To be honest, I'm not sure what you mean. Are you saying you actually want to change the URL in the address bar? That isn't exactly what URL Rewriting does, it just rewrites the path internally. The only way, that I know of anyways, to change the actual address that the user sees is via a Response.Redirect...
QuestionRe: Url Rewrite Path Problem [modified]memberOzLand2 Mar '07 - 3:58 
Let me explain, then.
i have a categories - subcategories system, im loading some   Localized:LocalizedDropdowns with some values, loaded from the xmlresourcemanager.
when i change the language them dropdowns change too. So nice you got in my life!!.
 
the problem comes when i decided to use querystrings and i need to "www.App.com/es-ES/Default.aspx?cat=1&scat=12" to be something like "www.App.com/es-ES/Default/Cars/Second_Hand/".
the dropdowns will change the url so i thought i could fetch them keys from the xml file too.
i need the app to rewrite the cats and scats in the diferent languages i support.
i saw some articles like Lenny Bacon's on Localized Breadcrumbs and Url-Rewriting on this site which i thought it was just the thing i need.
I was going to do it in your HttpModule but i though to ask first in case it was a better way.
I hope this time works!D'Oh! | :doh:
Sorry about my english i've been away for too long, both coding and talkin....
Thanks and again Good Work!!
 

-- modified at 10:17 Friday 2nd March, 2007
AnswerRe: Url Rewrite Path ProblemmemberKarl Seguin2 Mar '07 - 14:32 
I think it'll work and I think it's a fine way to solve it. With my localization stuff it always goes from key-->localized value, you are simply gonna have to walk your way backwards. You'll know the culture is fr-CA and that the category is "auto" and will be able to walk back to category Id 4 for Cars.
AnswerRe: Url Rewrite Path ProblemmemberOzLand5 Mar '07 - 4:07 
Sorry i didnt answer before, some fine dificulties.hehehe!
Thanks, i'll address the issue when i finished the app, since is not a requirement.
I will post the code here, no doubt when the time comes.
Thanks for answering and again for your work is helping to get back to work alot!

QuestionLocalizedImages Or HtmlControlsmemberArkonXX1 Mar '07 - 22:26 
Has anybody used the localizedImage Control?
 
i can't find it in the toolbox.
In the ddl there are only the normal server controls to add.
How can i add it to the toolbox and make in work.
 
I want to create a normal htmlinputbutton with localized options.
So if the normal htmlImage works, i can make my htmlinputbutton to work.
 
Thanks..

 
ArkonXX
Just a programmer
AnswerRe: LocalizedImages Or HtmlControlsmemberOzLand1 Mar '07 - 23:09 
Have you check you are not using an old .dll?
It happen to me before......D'oh!, Download this last one...U'll be ok!
GeneralRe: LocalizedImages Or HtmlControlsmemberArkonXX2 Mar '07 - 4:47 
No. I'm not using an old .dll. I'm working with the latest project.
Even if i write a new Control which inherits from a HtmlInputButton it doesn't come in the toolbox.
 

using System.Web.UI;
using System.Web.UI.HtmlControls;
 
namespace Localization
{
public class LocalizedHtmlInputButton : HtmlInputButton
{
}
}

Does anyone know what the problem is? I just want to make a htmlbutton localized.
 
ArkonXX
Just a Programmer
AnswerRe: LocalizedImages Or HtmlControls [modified]memberOzLand2 Mar '07 - 5:03 
the localization occurs on the server, so your html button would have to be runat=server, which makes it an asp:button,so so!.
for me the controls dont appear in the toolbox, you should write them down in your html code i believe. also u must implement ILocalized
 

 
-- modified at 13:06 Friday 2nd March, 2007
AnswerRe: LocalizedImages Or HtmlControlsmemberJeff_Is_Just_A_Programmer13 Mar '07 - 4:56 
For this problem i have a solution. I can now use the normal LocalizedButtons. I have made a property Postback. If this property is set false, i add a Attricute "onclick" with value "return false;"
Meaning that the page is never valid, so there will be no postback.
There is also a check when i added in code a onclick attribute.
The solution there for is "onclick" : <#usercode#> + "return false".
 
Just a Programmer

GeneralSwitch to VS 2005memberolivier demers11 Jan '07 - 4:23 
Hi,
 
Well i don't know if you can help me but here my problem.
 
We recently purchase Visual Studio 2005. I switch my web site from 2003 to 2005. The web site work fine. But in design mode on my .aspx page I get an error on the localized control : Error Rendering Control - MyControl, Object reference not set to an instance of object. Also when I compile the WebSite I get a Warning in the Web.config file : Could not find schema information for the element 'Localization'. but my section group 'Localization' still there. I don't know what I have to chance in the file.
 
Hope you can help me.
 
thank you
 
Olivier
GeneralRe: Switch to VS 2005memberKarl Seguin11 Jan '07 - 14:41 
Your second problem is fixed by modifying VS.NET's XSD file to add the schema information for the new configuration section. Since you can safely ignore those warnings, I personally don't think it's worth it, but if you really want to have them be gone, take a look at: http://www.ovationmarketing.com/XSD.asp[^]
 
As for rendering in design time, I don't use the designer much, but I believe that VS.NET will actually execute the Render method. My guess is that it's crashing (probably because ResourceManager is throwing an exception 'cuz it isn't setup to work in a designer) and you get the error. You can actually debug VS.NET while it's loading the control to see exactly what the problem is: http://forums.asp.net/thread/1445676.aspx[^]
 
If you want, you can assign a Designer to each control and explitly describe how it should appear at design time. There's a simple example at:
http://www.codersource.net/published/view/288/creating_composite_control_in_asp_net_3.aspx[^]
 
Sorry that I can't be of any more help.
QuestionJavascript Localizationmemberdjsputnik8 Oct '06 - 17:50 
Hi,
Great article, just tried your javascript suggestion and realized something about our design that's preventing this from working well - a lot of our javascript executes "right away" or inline, while RegisterStartupScript writes the javascript at the end of the page. Can you suggest a nice way to output the registered values at the top of the page, before our inline code?
Thanks,
Gennady
AnswerRe: Javascript LocalizationmemberKarl Seguin11 Oct '06 - 1:34 
Gennady:
 
One solution that comes to mind is to rely on an HttpHandler to take care of it for you. You could do something like:
 
<script language="Javascript" src="localization.ashx?page=aboutus"></script>
 
in your localization.ashx httphandler, you can output the javascript as you would any other .js file for the "aboutus" page.   One of the reasons I like this approach is that it moves the burden of registering javascript values from each page, onto a centralized handler.
 
If you aren't familiar with HttpHandlers, check out:
http://codebetter.com/blogs/karlseguin/archive/2006/05/24/145397.aspx
AnswerRe: Javascript Localizationmemberdjsputnik11 Oct '06 - 17:05 
Just found "RegisterClientScriptBlock" and it's same as "RegisterStartupScript". I couldn't tell the difference from looking at the Microsoft documentation, but after more digging, I read that RegisterClientScriptBlock is rendered on top of the page for cases exactly like mine. So there is a very simple solution, I change the the function that's called.
Thanks for the feedback.
QuestionHow to localize XMLDocument ?memberhari19777 Oct '06 - 1:55 
Hi carl,
 
I really liked this article. However, I have a query, if anyone can help me out?
How to localize XMLDocument ?
 
Iam using component tree view control, in that data retriving from XML document.Now i want to change NavBarItem   Text name in spanish language.
 

<b>my xml file:</b>
 
<NavItems>
<NavItem Text="My Subscription">
-----
----
<NavItem Text="Request New Subscription">
-------
<NavItem Text="My Preference">
------
</NavItem>
<NavItem Text="My Contact Details">
-------
</NavItem>
</NavItems>
 

 
I used GETSTRING method also but am not getting.
 

 

krish
 
krish
 

 

 
-- modified at 2:57 Monday 9th October, 2006
AnswerRe: How to localize XMLDocument ?memberKarl Seguin11 Oct '06 - 1:37 
Krish:
Why not just build a GetXmlDocument() function in the ResourceManager that returns a whole XmlDocument, like:
 
public XmlDocument GetXmlDocument()
{
   //do stuff a lot like getString
}
 

Then you can have multiple xml files,
 
en-CA/nav.xml
fr-Ca/nav.xml
....
 
And based on the current culture, the right "nav.xml" will be returned.
 
(I'd suggest that you cache the XmlDocument once you've read it the first time).
Questionurl images/files/ referencememberDiegoSB4 Oct '06 - 14:28 
Hi, first mi inglish is to bad. i hope you understand me.
Second, your framework is great...i made some improvements. I change the LoadCulture to detect user culture and if exist i used. And some support for generic culture for this...example i make a language resource "es" for al spanish cultures ( "es-Es".."es-AR" ...etc ).
I you send this improvements if you want.
Now...the problem i'm have with url rewriting is maping to css, js...images...flash...i have to work with relative paths so i cant use "/images/hello.jpg" and if i use "image/hello.jpg" when i rewrite with de culture now i don't find de images. Yes..i cant write all resources with <% request.apli... but is to much coding... i was playing tring to pass the language by querystring but i can't preserve when i redirect...i can save in session but i don't like.
What do you use? suggest?
Thanks.

 
Diego
AnswerRe: url images/files/ referencememberKarl Seguin4 Oct '06 - 15:20 
Diego:
It can be a real annoyance - i know. I don't have any magic answers, but I do have some potential solutions.
 
1 - Use cookies to maintain the culture. The added advantage here is that the user's culture will automatically be preserved across visits
 
2 - Look at the html base tag (http://www.wdvl.com/Authoring/HTML/Head/base.html). I'm not 100% sure it'll fix the problem (and it might introduce new problems), but I think it might be the best solution.
 
3 - Use ../images/hello.jpg for your paths. In order to work, you'll always have to url rewrite.
 
Finally, if you are using usercontrols for the footer/header (or a master page), using Request.ApplicationPath isn't too bad - but I understand why you want to find another solution.
 
Hope that helps a bit.
GeneralRe: url images/files/ referencememberDiegoSB4 Oct '06 - 16:02 
Thanks for your fast answer.
Yes i can use cookies but it can be disable.
I'm test your point 3 and something like this
<link href="<%= request.ApplicationPath %>/css/Home.css" rel="stylesheet" type="text/css">
but i don't sure if this aproach have to much overhead for something so simple. I'm building a CMS for this page thats why i'm thinking to much for this. i think the cms admin will work in the rewrited url too...i don't now yet.
Now i make a index page to redirect to and "start" page with the culture added to the path url and the user will navigate from there. i think i will die in your point 3 at last.
Thanks for this framework it's save me too much time of research and coding.
soo bad you don't do the magic!! Big Grin | :-D
 
Diego
GeneralRe: url images/files/ referencememberDiegoSB16 Oct '06 - 15:52 
I have another question...what do you do when someone want's to see the root of the language. In other words http://localhost/somesite/ takes you to the default.aspx ( or index.aspx ) but when we localize this is http://localhost/somesite/us-EN/ dosen't wrings you the default.aspx or index.aspx and raise a file not found error.
Thanks.
Diego
QuestionThe page must be refeshed to take effect?memberhapytran13 Sep '06 - 15:57 
Hi,
 
I added Creating Multilingual Websites - part 3 into my exist web app and cookie to remember the latest language changing. I added some codes into LoadCulture (in LocalizationHttpModule.cs) function:
 

bool bLan = false;
if(ln!=null)
{
HttpCookie lCookie = new HttpCookie("l");
switch (ln.ToLower())
{
case "vi":
pathParts[0] = "vi-VN";
lCookie["lVal"] = pathParts[0];
lCookie.Expires = DateTime.Now.AddDays(1d);
response.Cookies.Add(lCookie);
bLan = true;
break;
default:
pathParts[0] = "en-CA";
lCookie["lVal"] = pathParts[0];
lCookie.Expires = DateTime.Now.AddDays(1d);
response.Cookies.Add(lCookie);
bLan = true;
break;
}
}
if (request.Cookies["l"] != null && !bLan)
{
if (request.Cookies["l"]["lVal"] != null)
{
pathParts[0] = request.Cookies["l"]["lVal"];
}
}

 
But i do not know why when i click on a new language, i must click this link again or refesh the page to take effect? Is it cache? I seem that when i click the first time, LoadResources function is not working?
 
Thanks
 
P.S: i changed URL from "xxx/en-CA/xxx.aspx" to "xxx/xxx.aspx?ln=e"

AnswerRe: The page must be refeshed to take effect?memberKarl Seguin14 Sep '06 - 4:11 
Hapy:
I took a quick look and was able to get things workng fine.   Here's what the relevant part of my LocalizationHttpModule looks like:
 
private void context_BeginRequest(object sender, EventArgs e) {
   HttpRequest request = ((HttpApplication) sender).Request;
   HttpContext context = ((HttpApplication)sender).Context;
   string applicationPath = request.ApplicationPath;
   if(applicationPath == "/")
   {
         applicationPath = string.Empty;
   }           
   LoadCulture(request.QueryString["ln"], context.Response, request);
}
 
private void LoadCulture(string ln, HttpResponse response, HttpRequest request)
{           
   bool bLan = false;
   string[] pathParts = new string[1];
   if(ln!=null)
   {
         HttpCookie lCookie = new HttpCookie("l");
         switch (ln.ToLower())
         {
            case "fr":
                  pathParts[0] = "fr-CA";
                  lCookie["lVal"] = pathParts[0];
                  lCookie.Expires = DateTime.Now.AddDays(1d);
                  response.Cookies.Add(lCookie);
                  bLan = true;
                  break;
            default:
                  pathParts[0] = "en-CA";
                  lCookie["lVal"] = pathParts[0];
                  lCookie.Expires = DateTime.Now.AddDays(1d);
                  response.Cookies.Add(lCookie);
                  bLan = true;
                  break;
         }
   }
   if (request.Cookies["l"] != null && !bLan)
   {
         if (request.Cookies["l"]["lVal"] != null)
         {
            pathParts[0] = request.Cookies["l"]["lVal"];
         }
   }           
   string defaultCulture = LocalizationConfiguration.GetConfig().DefaultCultureName;
   if(pathParts[0] != null) {
         try {
            Thread.CurrentThread.CurrentCulture = new CultureInfo(pathParts[0]);                    
         }catch (Exception ex) {
            if(!(ex is ArgumentNullException) && !(ex is ArgumentException)) {
                  throw;
            }                    
            Thread.CurrentThread.CurrentCulture = new CultureInfo(defaultCulture);
         }
   }else {
         Thread.CurrentThread.CurrentCulture = new CultureInfo(defaultCulture);
   }
   Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}
 
It switched the culture right away..and correctly remembered my culture between page navigation and browser restarts.
QuestionResourceManagerXml.RetrieveString Error [modified]memberVeselina Radeva30 Aug '06 - 6:42 
Hi,
 
I use this module for the first time and I like it very much. Unfortunately I experienced a strange problem with it. If two or more users browse the site, an error occurs and it breaks down from time to time. After you refresh the site once or twice it works fine again. Below is the exception that occurs when the site breaks down. I've noticed that WilLScott posted this kind of error here: http://www.codeproject.com/aspnet/LocalizedSamplePart2.asp?df=100&forumid=96123&fr=51[^] but there is no solution to it.
 
Thanks
 
Error Message: Object reference not set to an instance of an object.
Error Source: Localization
Error InnerException:
Error TargetSite : System.String RetrieveString(System.String)
Stack Trace: at Localization.ResourceManagerXml.RetrieveString(String key)
at Localization.LocalizedLiteral.Render(HtmlTextWriter writer)
at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
at System.Web.UI.HtmlControls.HtmlTitle.Render(HtmlTextWriter writer)
at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
at System.Web.UI.HtmlControls.HtmlHead.RenderChildren(HtmlTextWriter writer)
at System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer)
at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
at System.Web.UI.Control.Render(HtmlTextWriter writer)
at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
at System.Web.UI.Page.Render(HtmlTextWriter writer)
at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
 

 

 
-- modified at 8:08 Thursday 31st August, 2006
AnswerRe: ResourceManagerXml.RetrieveString ErrormemberVeselina Radeva13 Dec '06 - 10:41 
I found where the problem is - the cache. For some reason the cache is lost from time to time. I replaced the cache functionality with static variables and one additional check if the currently loaded language is changed and now everything works fine.
GeneralLocalized ListItemsmemberQuintonSmith17 Jul '06 - 23:04 
Hi,
 
I have a RadioButtonList which has several ListItems, can someone point me in the right direction to build what I think is a customised LocalizedRadioButtonList and LocalizedListItem (as per the article code localized controls). I need to know which methods to override in each class, and whether I should use the ListItemControlBuilder class. I'm having a go at it myself, but am not that experienced in developing custom controls.
 
Any help would be appreciated.
 
Regards,
 
Quinton
GeneralRe: Localized ListItemsmemberkeo_ua14 Sep '06 - 1:25 
Maybe...
 
using System.Web.UI;
using System.Web.UI.WebControls;
using System;
 
namespace Localization
{
public class LocalizedListBox: ListBox
{
protected override void OnPreRender(EventArgs arg)
{
base.OnPreRender(arg);
for (int i=0; i<base.Items.Count; i++) {
base.Items[i].Text =
ResourceManager.GetString( base.Items[i].Value );
}
}
}
}
GeneralhttpHandlers filesmemberQuintonSmith17 Jul '06 - 4:22 
Hi,
 
Fantastic article, I'm using the code straight away in a fairly large ASP.Net 2.0 project with lots of user controls.
All is going well, but I did have one problem with the context_BeginRequest method. Since I have another control registering in the config file under httpHandlers the BeginRequest runs several times to call this other resource (I think), which wiped out my URL rewrite variable the second/third time through this method, and so the default language was always set. I've done a quick work-around, just see what it looking for and ignore it if it exists.
 

Thanks a lot.
 
Quinton
General.NET 2 referencememberJonyO25 May '06 - 13:41 
Hello
 
Please excuse my ignorance but when you reference .Net 2 in Part 3 does that mean your code samples and methodology only work in 2?
Thanks
GeneralRe: .NET 2 referencememberKarl Seguin25 May '06 - 14:00 
No. Everything works in both 1.1 (where it was originally developed) and 2.0 Smile | :)
 
Cheers,
Karl
QuestionLocalizedCustomValidatormemberArkonXX2 May '06 - 21:16 
I have put a new control to the localized components, a localizedCustomValidator.
This control checks server-side some rule. If this rule isn't oke, than it appears in the summary.
No problem! But the localized function doesn't work. I think it is because the postback.
If i have the debugger on, i can see that the control gets his new ErrorMessage from DB, but it shows his standard Errormessage. What can i do to this problem?
 
Here is the code of the control:

public class LocalizedCustomValidator : CustomValidator, ILocalizedControls
{
#region fields and properties
private string key;
private bool newLine = false;
private bool colon = false;
 
public bool NewLine
{
get { return newLine; }
set { newLine = value; }
}
 
public string Key
{
get { return key; }
set { key = value; }
}
 
public bool Colon
{
get { return colon; }
set { colon = value; }
}
#endregion
 
protected override void Render(HtmlTextWriter writer)
{
if (!DesignMode)
{
string value = ResourceManager.GetString(key);
value = LocalizedUtility.ReplaceParameters(Controls, value);
value += ResourceManager.Colon;
base.ErrorMessage = value;
}
base.Render(writer);
}
}

 
Here the code of my page (front - aspx file):

<asp:TextBox ID="TB1" runat="server"></asp:TextBox>
<LocalizedCustomValidator ID="lcv1" runat="server" OnServerValidate="LocalizedCustomValidator1_ServerValidate" ErrorMessage="LocalizedCustomValidator" Text="*" ControlToValidate="TB1" Key="uqResource"></LocalizedCustomValidator>
<asp:Button ID="Button1" runat="server" Text="Button" />

 
Here the code of my page (back - cs file):

protected void LocalizedCustomValidator1_ServerValidate(object source, ServerValidateEventArgs args)
{
args.IsValid = false;
}

 
Grtz,
ArkonXX
GeneralRe: LocalizedCustomValidatormemberArkonXX2 May '06 - 21:33 
Client-side the control works fine.
 
Added code in front aspx:

<SCRIPT LANGUAGE="JavaScript">
function validateTest(oSrc, args){
args.IsValid = false; }
</SCRIPT>
 
<LocalizedCustomValidator ID="lcv1" runat="server" ErrorMessage="LocalizedCustomValidator" Text="*" ControlToValidate="TB1" Key="uqResource" ClientValidationFunction="validateTest"></LocalizedCustomValidator>

 
Grtz,
ArkonXX
 
-- modified at 4:25 Wednesday 3rd May, 2006
 
But i still want to know how i can change my code so it will work serverside.
QuestionRe: LocalizedCustomValidatormemberArkonXX3 May '06 - 2:23 
A Solution could be instead of override Render Methode a override of an other method like:
 

protected override void OnInit(System.EventArgs e){ base.OnInit(e); }
protected override void OnLoad(System.EventArgs e){ base.OnLoad(e); }
protected override void OnPreRender(System.EventArgs e){ base.OnPreRender(e); }

 
Is this a good solution for my problem? Should i use 1 of these methods or is there something else i should know?
 
Grtz,
ArkonXX
AnswerRe: LocalizedCustomValidatormemberKarl Seguin3 May '06 - 14:11 
Try doing it in Init, which occurs before the validation stuff fires..
GeneralRe: LocalizedCustomValidatormemberArkonXX3 May '06 - 23:06 
The custumvalidator works fine now!!!
 
I have also build a own usercontrol, which uses the localized controls, such as LocalizedLabel. But because the LocalizedLabel is inside the usercontrol (dynamic build in pageload) the value isn't set. I can also solve this problem to change the localized label method Render to init Method. But this will take effect on all the labels. Is it a wrong conclusion that all localized labels should be set inside the init method? What are the pro and cons?
 

QuestionSeperate resource files for each web pagememberLisa Chakraborty24 Apr '06 - 1:00 
I really liked this article. However, I have a query, if anyone can help me out? In the sample code only one resource file for a single language have been used to support all the web pages within the application. That means, it would be quite time consuming to generate the XML document for all the web pages? Isn't it better if we keep seperate resource files (to support a single language) to handle different web pages?
Pls, suggest.
AnswerRe: Seperate resource files for each web pagememberKarl Seguin24 Apr '06 - 13:16 
I'm of mixed opinion about this. The builtin model in 2.0 supports this via Local or Global resources. It could be achieved with this custom resource manager, with a bit of reworking.
 
The only thing I dislike about per-page resources (and I really do hate it), is I find it makes managing the resources even more challenging. It makes it much harder to reuse existing resources and, if it's badly implemented it makes its hard to promote/demote a resource from global <--> local.
 
Of course, you seem to need to have them separated out for some reason - perhaps your translators need extra context and that's an easy way for you to do it. So maybe in your case it makes sense. But do think about how you'll handle two pages with the same resource, and what happens if one should change. Also, when a new page is added, how will you see if the resource already exists locally on the page? or will you simply duplicate it?
QuestionRe: Seperate resource files for each web pagememberLisa Chakraborty24 Apr '06 - 19:58 
Thanks Karl for such a prompt reply. I understand that if we have seperate resources for each web page it would be difficult to manage them. However, I have just one apprehension- If I have all the content for the entire site in one resource file, then the resource file will be too heavy. So, in that case won't it be a little slow when the runtime generates in-memory structure for the XML resource file?
AnswerRe: Seperate resource files for each web pagememberKarl Seguin25 Apr '06 - 1:01 
It might be a problem for large files, yes. I haven't run into the issue myself, but I'm sure there's a threshold. Especially true if you have long content.
 
Again, the solution might need to be customized for your needs. If you have very large paragraphs, you might want to put those in a separate file that isn't cached. Instead, you could create a simple user control that expects a "key" property, retrieves the content from the XML/Database and uses more efficient Outputcaching to help out with the load.
 
Karl

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 1 Nov 2005
Article Copyright 2005 by Karl Seguin
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid