Issue with Multiple languages resource files

M J 661 Reputation points
2021-11-08T19:50:08.447+00:00

I am developing a site that will have to also be available in Spanish.

I found this page how-to-create-multiple-languages-in-asp-net-mvc-4-5-framework

I tried to follow it as mush as possible only modifying to fit my needs. To that end, I created two resource files Resource.resx and Resource.Sp.resx. I set it so that the access modifier is Public in each of them, and then added the Name and Values for each field.

Resource.resx

Compare The password and confirmation password do not match.    
Create  Create a new account.   
Email   Email   
EmailRequired   Email is required   
FirstName   First Name  
FirstNameLength Your first name should be no more than 30 characters    
FirstNameRequired   First name is required  
LastName    Last Name   
LastNameLength  Your last name should be no more than 50 characters 
LastNameRequired    Last name is required   
Password    Password    
PasswordConfirm Confirm password    
PasswordConfirmRequired Confirmation of password is required    
PasswordMinimum The password must be at least 8 characters long 
PasswordRequired    Password is required    
Register    Register    
Telephone   Telephone   
TelephoneRequired   Telephone is required   


Resource.Sp.resx

Compare La contraseña y la contraseña de confirmación no coinciden. 
Create  Crea una cuenta nueva.  
Email   Correo electrónico  
EmailRequired   Correo electronico es requerido 
FirstName   Nombre  
FirstNameLength Su nombre no debe tener más de 30 caracteres    
FirstNameRequired   Se requiere el primer nombre    
LastName    Género  
LastNameLength  Tu apellido no debe tener más de 50 caracteres  
LastNameRequired    Se requiere apellido    
Password    Contraseña  
PasswordConfirm Confirmar Contraseña    
PasswordConfirmRequired Se requiere confirmación de contraseña  
PasswordMinimum La contraseña debe tener al menos 8 caracteres  
PasswordRequired    Se requiere contraseña  
Register    Inscribirse 
Telephone   Teléfono    
TelephoneRequired   Se requiere telefono    

Next I modified the code in the RegisterViewModel to be this

public class RegisterViewModel
{
    [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "FirstNameRequired")]
    [Display(Name = "First Name", ResourceType = typeof(Resource))]
    [StringLength(30, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "FirstNameLength")]
    public string FirstName { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "LastNameRequired")]
    [Display(Name = "Last Name", ResourceType = typeof(Resource))]
    [StringLength(50, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "LastNameLength")]
    public string LastName { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "EmailRequired")]
    [EmailAddress]
    [Display(Name = "Email", ResourceType = typeof(Resource))]
    public string Email { get; set; }

    [StringLength(100, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "PasswordMinimum", MinimumLength = 8)]
    [DataType(DataType.Password)]
    [Display(Name = "Password", ResourceType = typeof(Resource))]
    [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "PasswordRequired")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm Password", ResourceType = typeof(Resource))]
    [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "PasswordConfirmRequired")]
    [Compare("Password", ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "Compare")]
    public string ConfirmPassword { get; set; }

    [Display(Name = "Telephone", ResourceType = typeof(Resource)), Phone]
    [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "TelephoneRequired")]
    [DataType(DataType.PhoneNumber)]
    public string PhoneNumber { get; set; }
    [Required]
    public string CaptchaCode { get; set; }
}

And I added the following

public class LanguageManager
{
    public static List<Language> AvailableLanguages = new List<Language>
    {
        new Language {
            LanguageName = "English", LanguageCultureName = "en"
        },
        new Language {
            LanguageName = "Spanish", LanguageCultureName = "sp"
        },
    };

    public static bool IsLanguageAvailable(string lang)
    {
        return AvailableLanguages.Where(a => a.LanguageCultureName.Equals(lang)).FirstOrDefault() != null ? true : false;
    }
    public static string GetDefaultLanguage()
    {
        return AvailableLanguages[0].LanguageCultureName;
    }

    public void SetLanguage(string lang)
    {
        try
        {
            if (!IsLanguageAvailable(lang)) lang = GetDefaultLanguage();
            var cultureInfo = new CultureInfo(lang);
            Thread.CurrentThread.CurrentUICulture = cultureInfo;
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);
            HttpCookie langCookie = new HttpCookie("culture", lang)
            {
                Expires = DateTime.Now.AddYears(1)
            };
            HttpContext.Current.Response.Cookies.Add(langCookie);
        }
        catch (Exception ex) 
        {
            Errors.ErrorOccured(ex);
        }
    }
}


public class MyController: Controller
{
    protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
    {
        string lang = null;
        HttpCookie langCookie = Request.Cookies["culture"];
        if (langCookie != null)
        {
            lang = langCookie.Value;
        }
        else
        {
            var userLanguage = Request.UserLanguages;
            var userLang = userLanguage != null ? userLanguage[0] : "";
            if (userLang != "")
            {
                lang = userLang;
            }
            else
            {
                lang = LanguageManager.GetDefaultLanguage();
            }
        }
        new LanguageManager().SetLanguage(lang);
        return base.BeginExecuteCore(callback, state);
    }
}

I also changed the Account controller to be public class AccountController : MyController

Now it is time to build and launch to test it. Where I get the following error when I load the Register page.

Cannot retrieve property 'Name' because localization failed. Type 'Baker.Resource' is not public or does not contain a public static string property with the name 'First Name'.

What am I missing?

ASP.NET
ASP.NET
A set of technologies in the .NET Framework for building web applications and XML web services.
3,246 questions
0 comments No comments
{count} votes

Accepted answer
  1. M J 661 Reputation points
    2021-11-21T01:10:59.753+00:00

    OK I found what the issue was. Even though i was setting a cookie and trying to determine which resource file to use. You need to set the browser to use the language. I only had it set to use english. Once I added Spanish then it worked.

    0 comments No comments

3 additional answers

Sort by: Most helpful
  1. Yijing Sun-MSFT 7,061 Reputation points
    2021-11-09T06:15:27.043+00:00

    Hi @M J ,
    As far as I think,you need to change your resource class and properties to public to make it work.Just like this:
    147634-eq1dm.png
    Best regards,
    Yijing Sun


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  2. M J 661 Reputation points
    2021-11-09T16:47:55.897+00:00

    I found the issue.

    in the model for the Registration

    typically it looks like this

     [Required]
     [Display(Name = "First Name")]
     [StringLength(30, ErrorMessage = "Your first name should be no more than 30 characters")]
     public string FirstName { get; set; }
    

    and I had this

    [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "FirstNameRequired")]
    [Display(Name = "First Name", ResourceType = typeof(Resource))]
    [StringLength(30, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "FirstNameLength")]
    public string FirstName { get; set; }
    

    but it should have been this

    [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "FirstNameRequired")]
    [Display(Name = "FirstName", ResourceType = typeof(Resource))]
    [StringLength(30, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "FirstNameLength")]
    public string FirstName { get; set; }
    

    the Display(Name="String") points to the string name of the resource. It does not change the display name like will typically happen.

    0 comments No comments

  3. M J 661 Reputation points
    2021-11-19T22:47:45.077+00:00

    I still cannot get this to work. I apologize for my last post where I got snarky.

    Here is the spanish resource file for registration 151093-resource.png

    As you can see, it is set to Public. It still is not working though.

    Here is the view

    @using PoliteCaptcha 
    @using System.Globalization
    @model Baker.Models.RegisterViewModel
    @{
        ViewBag.Title = Baker.Resource.Register;
    }
    
    &lt;h2&gt;@ViewBag.Title.&lt;/h2&gt;
    
    @foreach (var lang in Baker.Models.LanguageManager.AvailableLanguages)
    {
        @Html.ActionLink(lang.LanguageName,&#34;ChangeLanguage&#34;,&#34;Home&#34;, new { lang = lang.LanguageCultureName}, null) &lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;
    }
    
    @using (Html.BeginForm(&#34;Register&#34;, &#34;Account&#34;, FormMethod.Post, new { @class = &#34;form-horizontal&#34;, role = &#34;form&#34; }))
    {
        @Html.AntiForgeryToken()
        &lt;div class=&#34;row&#34;&gt; 
    	    &lt;div class=&#34;col-xs-12 col-md-6&#34;&gt;
                 &lt;h4&gt;@Baker.Resource.Create&lt;/h4&gt;
            &lt;/div&gt;
            &lt;div class=&#34;col-xs-12 col-md-6&#34;&gt;
    			&lt;div class=&#34;panel panel-primary&#34;&gt;
    				&lt;div class=&#34;panel-heading&#34;&gt;
    				  &lt;h3 class=&#34;panel-title&#34;&gt;@Baker.Resource.PasswordRequirements;&lt;/h3&gt;
    				&lt;/div&gt;
    				&lt;ul class=&#34;list-group&#34;&gt;
    				  &lt;li class=&#34;list-group-item&#34;&gt;@Baker.Resource.PasswordRequirement1&lt;/li&gt;
    				  &lt;li class=&#34;list-group-item&#34;&gt;@Baker.Resource.PasswordRequirement2&lt;/li&gt;
    				&lt;/ul&gt;
    			&lt;/div&gt;
    		&lt;/div&gt;
    	&lt;/div&gt;
        &lt;hr /&gt;
        @Html.ValidationSummary(&#34;&#34;, new { @class = &#34;text-danger&#34; })
    
        &lt;div class=&#34;form-group&#34;&gt;
    		&lt;div class=&#34;row&#34;&gt;
    			@Html.LabelFor(m =&gt; m.FirstName, new { @class = &#34;col-md-3 control-label&#34; })
    			&lt;div class=&#34;col-md-9&#34;&gt;
    				@Html.TextBoxFor(m =&gt; m.FirstName, new { @class = &#34;form-control-50&#34;, required = &#34;required&#34; })
    			&lt;/div&gt;
    		&lt;/div&gt;
        &lt;/div&gt;
    
        &lt;div class=&#34;form-group&#34;&gt;
    		&lt;div class=&#34;row&#34;&gt;
    			@Html.LabelFor(m =&gt; m.LastName, new { @class = &#34;col-md-3 control-label&#34; })
    			&lt;div class=&#34;col-md-9&#34;&gt;
    				@Html.TextBoxFor(m =&gt; m.LastName, new { @class = &#34;form-control-50&#34;, required = &#34;required&#34; })
    			&lt;/div&gt;
    		&lt;/div&gt;
        &lt;/div&gt;
    
        &lt;div class=&#34;form-group&#34;&gt;
    		&lt;div class=&#34;row&#34;&gt;
    			@Html.LabelFor(m =&gt; m.Email, new { @class = &#34;col-md-3 control-label&#34; })
    			&lt;div class=&#34;col-md-9&#34;&gt;
    				@Html.TextBoxFor(m =&gt; m.Email, new { @class = &#34;form-control-50&#34; })
    			&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;form-group&#34;&gt;
    		&lt;div class=&#34;row&#34;&gt;
    			@Html.LabelFor(m =&gt; m.Password, new { @class = &#34;col-md-3 control-label&#34; })
    			&lt;div class=&#34;col-md-9&#34;&gt;
    				@Html.PasswordFor(m =&gt; m.Password, new { @class = &#34;form-control-50&#34;, autocomplete=&#34;new-password&#34; })
    			&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;form-group&#34;&gt;
    		&lt;div class=&#34;row&#34;&gt;
    			@Html.LabelFor(m =&gt; m.ConfirmPassword, new { @class = &#34;col-md-3 control-label&#34; })
    			&lt;div class=&#34;col-md-9&#34;&gt;
    				@Html.PasswordFor(m =&gt; m.ConfirmPassword, new { @class = &#34;form-control-50&#34; })
    			&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;form-group&#34;&gt;
    		&lt;div class=&#34;row&#34;&gt;
    			@Html.LabelFor(m =&gt; m.PhoneNumber, new { @class = &#34;col-md-3 control-label&#34; })
    			&lt;div class=&#34;col-md-9&#34;&gt;
    				@Html.TextBoxFor(m =&gt; m.PhoneNumber, new { @class = &#34;form-control-50&#34;, required = &#34;required&#34;, placeholder=&#34;___-___-____&#34; })
    			&lt;/div&gt;
    		&lt;/div&gt;
        &lt;/div&gt;
         @Html.SpamPreventionFields()
        &lt;div class=&#34;form-group&#34;&gt;
            &lt;div class=&#34;col-md-offset-3 col-md-9&#34;&gt;
                &lt;input type=&#34;submit&#34; class=&#34;btn btn-primary&#34; value=&#34;@Baker.Resource.Register&#34; /&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    }
    
    @section Scripts {
        @Scripts.Render(&#34;~/bundles/jqueryval&#34;)
        @Html.SpamPreventionScript()
        &lt;script src=&#34;/Scripts/jquery.mask.min.js&#34; type=&#34;text/javascript&#34;&gt;&lt;/script&gt; 
        &lt;script src=&#34;/Scripts/additional-methods.min.js&#34; type=&#34;text/javascript&#34;&gt;&lt;/script&gt;
        &lt;script&gt;
    		jQuery(function($){
    			$(&#39;#PhoneNumber&#39;).mask(&#39;000-000-0000&#39;);
    		});
    		$(&#34;#theForm&#34;).validate({
    			rules: {
    				PhoneNumber: {
    					pattern: /^[0-9]{3}-[0-9]{3}-[0-9]{4}$/
    				}
    			},
    			messages: {
    				Phonenumber: {
    					pattern: &#34;Must include area code in Phone Number&#34;
    				}
    			}
    		});
    	&lt;/script&gt;
    }
    

    The foreach loop at the top of the page creates this HTML
    <a href="/Home/ChangeLanguage?lang=en">English</a> <span>&nbsp;&nbsp;</span> <a href="/Home/ChangeLanguage?lang=sp">Espa&#241;ol</a> <span>&nbsp;&nbsp;</span>

    Here is my controller code

    public ActionResult ChangeLanguage(string lang)
    {
        LanguageManager lm = new LanguageManager();
        lm.SetLanguage(lang);
        return View(&#34;Register&#34;);
    }
    

    Here is the model for LanguageManager class

    public class LanguageManager
    {
        public static List&lt;Language&gt; AvailableLanguages = new List&lt;Language&gt;
        {
            new Language {
                LanguageName = &#34;English&#34;, LanguageCultureName = &#34;en&#34;
            },
            new Language {
                LanguageName = &#34;Español&#34;, LanguageCultureName = &#34;sp&#34;
            },
        };
    
        public static bool IsLanguageAvailable(string lang)
        {
            return AvailableLanguages.Where(a =&gt; a.LanguageCultureName.Equals(lang)).FirstOrDefault() != null ? true : false;
        }
        public static string GetDefaultLanguage()
        {
            return AvailableLanguages[0].LanguageCultureName;
        }
    
        public void SetLanguage(string lang)
        {
            try
            {
                if (!IsLanguageAvailable(lang)) lang = GetDefaultLanguage();
                var cultureInfo = new CultureInfo(lang);
                Thread.CurrentThread.CurrentUICulture = cultureInfo;
                Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);
                //Thread.CurrentThread.CurrentCulture = new CultureInfo(lang);
                //Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
                HttpCookie langCookie = new HttpCookie(&#34;Culture&#34;, lang);
                langCookie.Expires = DateTime.Now.AddYears(1);
               
                HttpContext.Current.Response.Cookies.Add(langCookie);
                }
            catch (Exception ex) 
            {
                Errors.ErrorOccured(ex);
            }
        }
    }
    

    And here is the Register Model

    public class RegisterViewModel
    {
        [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;FirstNameRequired&#34;)]
        [Display(Name = &#34;FirstName&#34;, ResourceType = typeof(Resource))]
        [StringLength(30, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;FirstNameLength&#34;)]
        public string FirstName { get; set; }
    
        [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;LastNameRequired&#34;)]
        [Display(Name = &#34;LastName&#34;, ResourceType = typeof(Resource))]
        [StringLength(50, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;LastNameLength&#34;)]
        public string LastName { get; set; }
    
        [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;EmailRequired&#34;)]
        [EmailAddress]
        [Display(Name = &#34;Email&#34;, ResourceType = typeof(Resource))]
        public string Email { get; set; }
    
        [StringLength(100, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;PasswordMinimum&#34;, MinimumLength = 8)]
        [DataType(DataType.Password)]
        [Display(Name = &#34;Password&#34;, ResourceType = typeof(Resource))]
        [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;PasswordRequired&#34;)]
        public string Password { get; set; }
    
        [DataType(DataType.Password)]
        [Display(Name = &#34;PasswordConfirm&#34;, ResourceType = typeof(Resource))]
        [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;PasswordConfirmRequired&#34;)]
        [Compare(&#34;Password&#34;, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;Compare&#34;)]
        public string ConfirmPassword { get; set; }
    
        [Display(Name = &#34;Telephone&#34;, ResourceType = typeof(Resource)), Phone]
        [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = &#34;TelephoneRequired&#34;)]
        [DataType(DataType.PhoneNumber)]
        public string PhoneNumber { get; set; }
        [Required]
        public string CaptchaCode { get; set; }
    }
    

    here is the Global.asax code

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        HttpCookie cookie = HttpContext.Current.Request.Cookies[&#34;Culture&#34;];
        if (cookie != null &amp;&amp; cookie.Value != null)
        {
            System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cookie.Value);
            System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cookie.Value);
        }
        else
        {
            System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(&#34;en&#34;);
            System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(&#34;en&#34;);
        }
    }
    

    I have tried manually setting it to show sp as the default yet it always shows English. The cookie shows a value of sp but it still shows English on the page when I run. I have to be missing something. Can someone please help me with this?

    0 comments No comments