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.
Issue with Multiple languages resource files
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?
3 additional answers
Sort by: Most helpful
-
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:
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.
-
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.
-
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
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; } <h2>@ViewBag.Title.</h2> @foreach (var lang in Baker.Models.LanguageManager.AvailableLanguages) { @Html.ActionLink(lang.LanguageName,"ChangeLanguage","Home", new { lang = lang.LanguageCultureName}, null) <span>&nbsp;&nbsp;</span> } @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { @Html.AntiForgeryToken() <div class="row"> <div class="col-xs-12 col-md-6"> <h4>@Baker.Resource.Create</h4> </div> <div class="col-xs-12 col-md-6"> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">@Baker.Resource.PasswordRequirements;</h3> </div> <ul class="list-group"> <li class="list-group-item">@Baker.Resource.PasswordRequirement1</li> <li class="list-group-item">@Baker.Resource.PasswordRequirement2</li> </ul> </div> </div> </div> <hr /> @Html.ValidationSummary("", new { @class = "text-danger" }) <div class="form-group"> <div class="row"> @Html.LabelFor(m => m.FirstName, new { @class = "col-md-3 control-label" }) <div class="col-md-9"> @Html.TextBoxFor(m => m.FirstName, new { @class = "form-control-50", required = "required" }) </div> </div> </div> <div class="form-group"> <div class="row"> @Html.LabelFor(m => m.LastName, new { @class = "col-md-3 control-label" }) <div class="col-md-9"> @Html.TextBoxFor(m => m.LastName, new { @class = "form-control-50", required = "required" }) </div> </div> </div> <div class="form-group"> <div class="row"> @Html.LabelFor(m => m.Email, new { @class = "col-md-3 control-label" }) <div class="col-md-9"> @Html.TextBoxFor(m => m.Email, new { @class = "form-control-50" }) </div> </div> </div> <div class="form-group"> <div class="row"> @Html.LabelFor(m => m.Password, new { @class = "col-md-3 control-label" }) <div class="col-md-9"> @Html.PasswordFor(m => m.Password, new { @class = "form-control-50", autocomplete="new-password" }) </div> </div> </div> <div class="form-group"> <div class="row"> @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-3 control-label" }) <div class="col-md-9"> @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control-50" }) </div> </div> </div> <div class="form-group"> <div class="row"> @Html.LabelFor(m => m.PhoneNumber, new { @class = "col-md-3 control-label" }) <div class="col-md-9"> @Html.TextBoxFor(m => m.PhoneNumber, new { @class = "form-control-50", required = "required", placeholder="___-___-____" }) </div> </div> </div> @Html.SpamPreventionFields() <div class="form-group"> <div class="col-md-offset-3 col-md-9"> <input type="submit" class="btn btn-primary" value="@Baker.Resource.Register" /> </div> </div> } @section Scripts { @Scripts.Render("~/bundles/jqueryval") @Html.SpamPreventionScript() <script src="/Scripts/jquery.mask.min.js" type="text/javascript"></script> <script src="/Scripts/additional-methods.min.js" type="text/javascript"></script> <script> jQuery(function($){ $('#PhoneNumber').mask('000-000-0000'); }); $("#theForm").validate({ rules: { PhoneNumber: { pattern: /^[0-9]{3}-[0-9]{3}-[0-9]{4}$/ } }, messages: { Phonenumber: { pattern: "Must include area code in Phone Number" } } }); </script> }
The foreach loop at the top of the page creates this HTML
<a href="/Home/ChangeLanguage?lang=en">English</a> <span> </span> <a href="/Home/ChangeLanguage?lang=sp">Español</a> <span> </span>Here is my controller code
public ActionResult ChangeLanguage(string lang) { LanguageManager lm = new LanguageManager(); lm.SetLanguage(lang); return View("Register"); }
Here is the model for LanguageManager class
public class LanguageManager { public static List<Language> AvailableLanguages = new List<Language> { new Language { LanguageName = "English", LanguageCultureName = "en" }, new Language { LanguageName = "Español", 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); //Thread.CurrentThread.CurrentCulture = new CultureInfo(lang); //Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang); HttpCookie langCookie = new HttpCookie("Culture", 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 = "FirstNameRequired")] [Display(Name = "FirstName", ResourceType = typeof(Resource))] [StringLength(30, ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "FirstNameLength")] public string FirstName { get; set; } [Required(ErrorMessageResourceType = typeof(Resource), ErrorMessageResourceName = "LastNameRequired")] [Display(Name = "LastName", 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 = "PasswordConfirm", 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; } }
here is the Global.asax code
protected void Application_BeginRequest(object sender, EventArgs e) { HttpCookie cookie = HttpContext.Current.Request.Cookies["Culture"]; if (cookie != null && 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("en"); System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en"); } }
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?