Showing posts with label multilingual. Show all posts
Showing posts with label multilingual. Show all posts

Thursday, September 17, 2020

SXA language selector in a multisite/multilanguage environment

Sitecore SXA multi-language + multi-site

We have an environment with a full blown SXA site in place, available in 4 languages. Language fallback is setup on item level, including some search engine enhancements to avoid duplicate content issues.

Next to this company site, a bunch of (mini) sites is being created. These sites (should) have nothing in common with the main site (they are in another tenant) but they do share almost everything between them. As they do live in the same environment however, they are also available in all 4 languages - and with the item language fallback those versions will always exist.

But.. those sites do not need to exist in all those languages. They could be in any subset of the available (4) languages of the Sitecore environment.


SXA language selector

We are using the ootb language selector component from SXA to handle our language switch. This works fine on the sites, but of course it will show all the available languages and we would like to limit the number of languages per site. One option is creating our own language selector - that would be easy but I thought there would be a way to tell the ootb component which languages to use. And yes, there is.

First step: add site settings

On the site item in SXA (found in the "Settings/Site Grouping" folder in your site tree) you can add (custom) properties. Normally the formsRoot should be there already (set by the script that generated the site). We add a custom property to define the languages we want on the site: the name is "siteLanguages" and in the example here I've set the value (language list) to "nl|fr" to allow Dutch and French. 

Note that the name and the format for the value are custom - chosen in favor of the implementation.

Setting a default language is an ootb feature of SXA but note that if you do not have English in the list of allowed languages, you should (must) set a default language in the Language field in that same site definition item. 


Step two: a new LanguageSelectorRepository

The Language selector component gets its values from the Sitecore.XA.Feature.Context.Repositories.LanguageSelector.LanguageSelectorRepository which implements an interface ILanguageSelectorRepository. We need to create a new version of this repository and override the GetLangItems function:
public class LanguageSelectorRepository : Sitecore.XA.Feature.Context.Repositories.LanguageSelector.LanguageSelectorRepository, ILanguageSelectorRepository
{
  protected override IEnumerable<Item> GetLangItems(Item item)
  {
    var languageItems = base.GetLangItems(item);
    var languages = Sitecore.Context.Site.Properties["siteLanguages"];
    if (string.IsNullOrEmpty(languages))
    {
      return languageItems;
    }
    
    var siteLanguages = languages.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
    if (!siteLanguages.Any())
    {
      return languageItems;
    }

    return languageItems.Where(i => siteLanguages.Contains(i.Language.Name));
  }
}
As you can see we first use the existing (base) function to determine all the possible languages (the ootb implementation here is based on the existing language versions of the current item). We also fetch the siteLanguages property from the current Site. If we detect that this property is set we will filter the list of languages based on the set from the properties. 

It's a pretty simple example of extending this logic - you can do anything you want/need here of course.

Final step

As a final but easy step, use your dependency injection framework and register your LanguageSelectorRepository as the implementation of the ILanguageSelectorRepository interface.


Conclusion

Extending SXA is usually quite easy. That was not different in this case. We can still use the ootb component and only adapted the logic for fetching the data, which saved us time obviously.

However we did only adjust the data logic for the language selector component. We did not put anything in place to prevent viewing pages in the languages that we don't really want. That is out of scope for this blog post, but can be easily achieved with an extra resolver in the httpRequestBegin pipeline (an example can be seen in the EasyLingo module)
 

This is a first post in a series that will come from our multi-site project. Stay tuned for more SXA knowledge sharing...


Friday, February 28, 2020

SXA date variant and language fallback

Sitecore SXA DateVariantField

Note: this post assumes you know what a SXA variant is - if not, please read the documentation first.

Within a SXA variant, you can use a DateVariantField "Date" to display date and time from a date field in a chosen format. This definition is similar to the Field variant, but has an additional field "Date format" that allows you to choose a date and time format.





The date formats are stored in /sitecore/system/Settings/Foundation/Experience Accelerator/Rendering Variants/Enums/Date Formats as items (with display names).

On https://sitecore.stackexchange.com/a/8489/237 Alan explains how this works together with cultures and such. In short: SXA will render the date in the current culture with the given format.

We have a project with a few languages and language fallback is enabled with English as fallback for all other languages. Our date variants worked perfectly in Sitecore 9.0.2 with SXA 1.7.1 but after our upgrade to Sitecore 9.2 and SXA 1.9 we saw something weird...

Date variants and language fallback

So we have a site with language fallback enabled and it seems to work fine. But then we noticed weird date formats in our date variants when switching languages - although we had the format set to dd/MM/yyyy we did see a time value:


In English everything was still ok.

The workaround

First thing to check is language fallback is actually enabled on the site.

Note that since SXA 1.9 there is a checkbox to enable item language fallback. As this is an upgraded site, we also still have the fallback setting in the Other properties field, just to be very sure...



The workaround for the issue that I solved it for me was:
  1. Create language versions for /sitecore/system/Settings/Foundation/Experience Accelerator/Rendering Variants/Enums/Date Formats/date2 (which is the dd/MM/yyyy item) and give them a display name just like the English version. Repeat for every format you want to use.
  2. Remove the standard value from the variants format field. Save. Then select the dd/MM/yyyy again (this time actually selected and not as standard value). 
  3. Create language versions for the variant item.
Not all steps might be needed - Support and myself had some different conclusions on that.. and I think caching was also involved. Step 2 however is certainly needed - with a standard value it won't work (bug listed with reference number 119290)

But with this workaround all was fine again.



Tuesday, April 16, 2019

Sitecore 9 Forms : translating client-side error messages

Sitecore 9 Forms : translating error messages client-side

A while ago a wrote a post on translating error messages in Sitecore Forms. In that post I mentioned that the validation done by jquery on the client-side was out of scope (of that post).

When a question on the subject appeared on Sitecore Stack Exchange, I started investigating. With a little help of Support the outcome was adding some custom scripts (and files). A better solution might be added in a future release but for now (9.1.1 and earlier) we'll have to do it like this. Note that my answer on SSE is a general one - this post addresses fixing it in a SXA environment.

First a small recap:
The messages entered here will be used for client- and server-side error messages. Which is nice. Just be aware that somewhere also jquery validation gets used and that can lead to strange situations. I noticed that the email validation can give two different error messages depending on what the error is.. and one of them is not translated as it comes from jquery. 
This is what  I wrote on the translated error messages in my first post. So let's start translating that email validation!

The issue can be easily reproduced with a translated form (non-English). Start typing in an email field and you will get the English jquery message. Once you have something like "...@x"  the jquery message will disappear and the Sitecore one will appear (which can be translated).

Adding the localized scripts

  1. Download all the jquery-validation localized messages files you need from https://github.com/jquery-validation/jquery-validation/tree/master/src/localization
  2. Place those files in a \sitecore modules\Web\ExperienceForms\scripts\localization folder. 
  3. You can change the translation in the files as needed - their structure is very straight forward
  4. Open Sitecore Form Wrapper.cshtml in the \Views\Shared folder 
  5. Add a script just after the @Html.RenderFormScripts():  (adapt the script for the languages you have in your environment)
<script type="text/javascript">
var lang = '@Sitecore.Context.Language.Name';
var script = document.createElement("script");
if (lang == 'nl')
{
    script.src = "/sitecore%20modules/Web/ExperienceForms/scripts/localization/messages_nl.js";
    $("head").append(script);
}
else if (lang == 'fr')
{
    script.src = "/sitecore%20modules/Web/ExperienceForms/scripts/localization/messages_fr.js";
    $("head").append(script);
}
...
</script>

Don't forget to add the added/changed files to your solution. And be aware when upgrading that you changed a Sitecore file...

This should do it. The messages are not translatable by the editors, but at least they are not English everywhere...

Alternatives?

It will also work if you put the messages javascript files in the scripts folder of Forms (\sitecore modules\Web\ExperienceForms\scripts) and add the name of the messages file to the Scripts field in the form (where the other jquery js files already are). Problem with this solution is that the Scripts field is shared - meaning it is the same for all languages. So, you can add one language and that will be used for all languages (including English). Unless you have a single language site that is non-English, this is not what you want.

Wednesday, April 10, 2019

Sitecore SXA custom rendering variant field for translations

The requirement

We had to display a field (from the datasource item) and some fixed labels in a html structure.

So for a first version we just created the rendering variant and added Field and Text variants to the definition. This works..  but in our multilingual environment we have to translate the labels. This can be done as the values are not shared, but I started thinking.. 
do I want content editors that translate labels changing my rendering variants?
And the answer was no..

To fix that, I created my own variant definition to support translated labels.

 Creating a custom variant field

Template

First thing to do is create a template to define the fields of the variant. In our case that was very easy as I just wanted to create an enhanced version of "Text", so I used that template (/sitecore/templates/Foundation/Experience Accelerator/Rendering Variants/VariantText) as base template and added nothing else.

If you have another use case to create a custom variant field, check the existing ones to see what base templates you need. A good start will be: 
  • /sitecore/templates/Foundation/Experience Accelerator/Rendering Variants/Base/_Rendering Variants Base
  • /sitecore/templates/Foundation/Experience Accelerator/Rendering Variants/Base/_Data Attributes
  • /sitecore/templates/Foundation/Experience Accelerator/Rendering Variants/Base/_Link Attributes
  • /sitecore/templates/Foundation/Experience Accelerator/Variants/Base Variant Field
Remember not to place your template in the SXA folders! Use your own folder structure...

Insert Options

You want your field to be available in the insert options when people create a variant definition. The best practices tell us not to change the SXA templates, so we won't do that. We'll use the rules engine to add the insert options!

Go to /sitecore/system/Settings/Rules/Insert Options/Rules and add your insert rule:
You need to check the item template to detect the templates you want to add the insert option to (the variant definition itself and all rendering variant templates in my case), and add your template as insert option.

Corey Smith wrote an excellent blog post on organizing your insert options - a good idea, you should read this ;)

The code

First class we need is a model for your template. It should inherit from Sitecore.XA.Foundation.RenderingVariants.Fields.RenderingVariantFieldBase, but as we are building upon VariantText I inherited from that one (in the same namespace).
public class DictionaryText : VariantText
{
  public DictionaryText(Item variantItem) : base(variantItem)
  {
  }

  public static new string DisplayName => "Dictionary Text";
}

Creating instances of this DictionaryText class is done with a processor class that inherits from Sitecore.XA.Foundation.Variants.Abstractions.Pipelines.ParseVariantFields.ParseVariantFieldProcessor.  In that class you need to define the supported template (the guid of the template you created earlier) and the TranslateField function that translates the variant field arguments to the model class. I checked the VariantText implementation (as we have the same model):
public class ParseDictionaryText : ParseVariantFieldProcessor
{
  public override ID SupportedTemplateId => new ID("{AAD9B54E-C7B2-4193-975E-954C0AD5A922}");

  public override void TranslateField(ParseVariantFieldArgs args)
  {
    var variantFieldArgs = args;
    var variantText = new DictionaryText(args.VariantItem);
    variantText.ItemName = args.VariantItem.Name;
    variantText.Text = args.VariantItem[Sitecore.XA.Foundation.RenderingVariants.Templates.VariantText.Fields.Text];
    variantText.Tag = args.VariantItem.Fields[Sitecore.XA.Foundation.RenderingVariants.Templates.VariantText.Fields.Tag].GetEnumValue();
    variantText.IsLink = args.VariantItem[Sitecore.XA.Foundation.RenderingVariants.Templates.VariantText.Fields.IsLink] == "1";
    variantText.LinkField = args.VariantRootItem[Sitecore.XA.Foundation.Variants.Abstractions.Templates.IVariantDefinition.Fields.LinkField];
    variantText.CssClass = args.VariantItem[Sitecore.XA.Foundation.RenderingVariants.Templates.VariantText.Fields.CssClass];
    variantFieldArgs.TranslatedField = variantText;
  }
}
Note that the ID in this example is the ID of my template - you will probably have another one ;)

Next step is to create the processor that actually renders the variant. Create a class that inherits from Sitecore.XA.Foundation.Variants.Abstractions.Pipelines.RenderVariantField.RenderRenderingVariantFieldProcessor. You need to set a few properties to define the supported template and the render mode (html/json) but the main part is the RenderField function.
In this RenderField you can add any logic you want and render the output. In our case, we stay very close to the code from the ootb VariantText but just translate the value inside:
public class RenderDictionaryText : RenderRenderingVariantFieldProcessor
{
  public override Type SupportedType => typeof(DictionaryText);

  public override RendererMode RendererMode => RendererMode.Html;

  public override void RenderField(RenderVariantFieldArgs args)
  {
    if (!(args.VariantField is DictionaryText variantField))
    {
      return;
    }

    var dictionaryRepository = ServiceLocator.ServiceProvider.GetService<IDictionaryRepository>();
    var control = (Control)new LiteralControl(dictionaryRepository.GetValue(variantField.Text));
    if (variantField.IsLink)
    {
      control = InsertHyperLink(control, args.Item, variantField.LinkAttributes, variantField.LinkField, false, args.HrefOverrideFunc);
    }

    if (!string.IsNullOrWhiteSpace(variantField.Tag))
    {
      var tag = new HtmlGenericControl(variantField.Tag);
      AddClass(tag, variantField.CssClass);
      AddWrapperDataAttributes(variantField, args, tag);
      MoveControl(control, tag);
      control = tag;
    }

    args.ResultControl = control;
    args.Result = RenderControl(args.ResultControl);
  }
}
Note that I am using a custom DictionaryRepository here.. you'll need to change those two lines of code with your own code to translate the text.

Configuration
Last step is adding the processors to the correct pipelines in a config patch - e.g.:
<sitecore>
  <pipelines>
    <parseVariantFields>
      <processor type="Foundation.Dictionary.RenderingVariants.ParseDictionaryText, Foundation.Dictionary" resolve="true"/>
    </parseVariantFields>
    <renderVariantField>
      <processor type="Foundation.Dictionary.RenderingVariants.RenderDictionaryText, Foundation.Dictionary" resolve="true"/>
    </renderVariantField>
  </pipelines>
</sitecore>


Conclusion

My editors are happy. They can change and translate all labels as they are used to and don't need to think about the variants.

My developers are happy because the editors don't mess around in the variants just to translate a label.

And I did not have to spend so much time doing this...  but it seemed worth to share the idea (and the code).

Friday, May 11, 2018

Sitecore context language based on Geo IP location

Sitecore context language


Sitecore uses a context language (Sitecore.Context.Language) to fetch the correct version of an item to display. There is an out-of-the-box logic to determine the context language - it uses:
  1. The "sc_lang" query string parameter
  2. The language prefix in the url
  3. The language cookie for the site
  4. The default language in the site config.
  5. The DefaultLanguage setting in the Sitecore config
There are many other functional scenario's to determine the language of a (new) visitor and they can be developed in a custom processor that is placed in the httpRequestBegin pipeline. Create a class that overrides HttpRequestProcessor, implement an override for the Process method containing any logic you need and adapt the Sitecore Context properties (Language) as needed.

What if you want visitors to be directed to a language based on their location?

Geo IP detection

In Sitecore you can setup Geo IP location detection. This will add information to the Tracker about the location of your visitor. This information can be used for several purposes, and possibly also for language detection.

Geo IP to set the context language

We had to set the language of the new visitor to en-US when the visitor came from the USA.
As we had languages enabled in the urls, we only had to take care of the homepage - all other urls had a defined language already.

Seems so easy! Just create the language resolver as mentioned above and set the language based on the country in the tracker. Done.

Issue 1 : the tracker

If you place your language resolver where you normally would (just after the one from Sitecore) you'll notice that your tracker is not yet initialized. This is normal. If you want more information on the complete pipeline sequence in a request, you should read Martin Davies' blog
Solution: put your resolver elsewhere..  :
<startAnalytics>
   <processor patch:after="*[@type='Sitecore.Analytics.Pipelines.StartAnalytics.StartTracking, Sitecore.Analytics']" type="your-language-resolver, .." />
</startAnalytics>
As this is another pipeline, we'll also use another base class: my processor derives from RenderLayoutProcessor - the rest stays the same.

Issue 2: the Geo IP

On the first visit, you are most likely to find no geo ip data in the request pipeline. It's just too soon. The request has been made (async), but the results are not there yet. An article by Pavel Veller got us in the right direction. A new CreateVisitProcessor was made:
public class UpdateGeoIpData : CreateVisitProcessor
{
    public override void Process(CreateVisitArgs args)
    {
        var url = WebUtil.GetRawUrl();
        if (!url.Equals("/", StringComparison.OrdinalIgnoreCase))
        {
            return;
        }

        args.Interaction.UpdateGeoIpData(TimeSpan.FromMilliseconds(500));
    }
}

<pipelines>
    <createVisit>
        <processor type="Sitecore.Analytics.Pipelines.CreateVisits.UpdateGeoIpData, Sitecore.Analytics">
          <patch:delete/>
        </processor>
        <processor type="...UpdateGeoIpData, .." patch:after="processor[@type='Sitecore.Analytics.Pipelines.CreateVisits.XForwardedFor, Sitecore.Analytics']" />
    </createVisit>
</pipelines>

This will make sure that we wait a little longer, but only on the pages where needed.

Resolving

if (Tracker.Current.Interaction.HasGeoIpData)
{
    if (Tracker.Current.Interaction.GeoData.Country.Equals("US", StringComparison.OrdinalIgnoreCase))
    {
        return "en-US";
    }
}

Our final logic looked something as above. We test the country from the GeoData and handle accordingly.

For the moment we are running some performance tests, but so far it seems ok.

Wednesday, October 18, 2017

Sitecore 9 Forms : translating error messages

Sitecore 9 Experience Forms

Sitecore released a successor for Web Forms for Marketers (WFFM) with the new Sitecore 9 version, called Sitecore Forms.

In a multilingual environment you want to translate your forms, which is easy as the form editor itself has a language switch.  But what about the error messages - those things a visitor gets when (s)he fills in the form not as expected...

Translating error messages

Find the validators in the Sitecore Content Editor: /sitecore/system/Settings/Forms/Validations

Each validator has a Message field. Create versions of the validator item in all the languages needed and fill (or update) the message field as desired. Just check that the message is valid: the parameters in the text need to match the original value. As in the example (image) for email there is "{0}". A developer will recognize this syntax - all translations do need this token as it will be replaced by the field name when displaying the error.

Client-side messages

The messages entered here will be used for client- and server-side error messages. Which is nice. Just be aware that somewhere also jquery validation gets used and that can lead to strange situations. I noticed that the email validation can give two different error messages depending on what the error is.. and one of them is not translated as it comes from jquery. 

Translating the jquery validation is out of scope here as that is documented elsewhere and is not related to the Sitecore solution. Update: https://ggullentops.blogspot.com/2019/04/sitecore-9-forms-translating-client-error.html ;)

Required field

You might notice that the most used validation - required field - is not in the list. That is because this is a special case.. also on a Forms field, you would not select a required validator, but just tick the required checkbox. 

The code for the required field validation works in a different way and the message will come from the Sitecore dictionary. If you want to update or translate it, you need to add a key to that Dictionary. This can be done in the master database - just add an item in the /system/Dictionary - the name doesn't really matter. The key however needs to be exactly "{0} is required.". The phrase can be anything you want, just remember to add the {0} token. 

Create versions and translate in all languages needed - and don't forget to publish.

Update - password/email confirmation fields

As new fields got added in newer versions (9.3) - some new translation requirements were found. AS asked in the comments, the password & email confirmation fields seem to be special cases as well. How to translate them was mentioned on Sitecore StackExchange (https://sitecore.stackexchange.com/q/24129/237): just add "The {0} and {1} do not match." to the dictionary as well.

Monday, August 15, 2016

EasyLingo 2.1: multi-site, multi-lingual and now with XP editor support

EasyLingo 2.1


EasyLingo 2.1 was just released to the marketplace. After version 1 we promised to include support for the experience editor and so we did. Kris wrote a nice post about the how-to.

Once in the experience editor, in the view tab amongst the other "bars" you can select the "Language bar" and you will get the same EasyLingo experience as in the content editor.




We also added one more (optional) version section: in Sitecore you can create versions of an item in languages that are not registered in the systems settings. These versions are now listed as well -separately- by the module. Note that these versions will always use a general flag as we cannot detect it from the system items.

More information on this great module in my previous post. You can find the code and all documentation files on GitHub.  The module itself is available on the Sitecore marketplace.


Monday, August 1, 2016

EasyLingo

EasyLingo, our first module on the Sitecore marketplace


Why

This language expansion module was designed to simplify content editing when using a large amount of languages in your Sitecore environments. When you have multiple sites with different language sets (e.g. a Belgian site in Dutch/French, a Swiss site in fr/de/it and a Danish site in dk/en) or simply a large volume of available languages, managing these languages in Sitecore can become cumbersome.

Sitecore has no out of the box functionality that restricts or manages the languages used per node/site and only offers the languages list to navigate through the available languages. The language list presented by Sitecore comprises of both those with one or more versions as well as those that have no version available or are irrelevant for that context/site.

This module was created on a Sitecore 8.1 platform together with my colleague and Sitecore MVP Kris Verheire and is now available on the Sitecore marketplace.

What

This module consists of 2 parts, which can be activated separately.

Extra languages section in the content editor




The bar consists of 3 language lists:

  • On the first line, two of the lists are displayed. On the left hand side, the first list shows all the languages that have an actual version available. 
  • The right side shows the second list with all the languages for which a fallback version exists (working through Sitecore’s item fallback available as of version 8.1). 
  • The second line hold the third list that shows languages which are available on this node/site, but for which no version exists. This line will disappear if there are no such languages.
The versions with fallback on the right are rendered in italic. The current language on the Sitecore context is visualized in bold.


Languages in all the lists are clickable so the editor can easily switch languages without having to go through the whole list.

In order to get a nice view like in the screenshots it is necessary to manually set the icons of the languages in the system section of Sitecore (as Sitecore does not use these icons anymore in the list, they are not set by default. Flag-icons are still available though).



How are “available” languages detected?

We loop through the configured sites and check whether the current item is part of it (based on the
path in the content tree and the “rootPath” of the site). For each site we check if a custom property “allowedLanguages” is available in the SiteSettings specific site node. The distinct union of all these languages will be the list of allowed languages for the item. If the list is empty, all available languages in the Sitecore instance are allowed.

How to use the “allowedLanguages” setting?

The parameter will be part of a <site /> definition (just like hostname, database, rootPath and so on). It is not obligatory – the module works perfectly without it. It can be used for multi-site solution with different languages. For our example mentioned above we could have something like:
<site name=”Belgian” allowedLanguages="nl,fr-BE" … />
<site name=”Swiss” allowedLanguages="fr,de,it" … />
<site name=”Danish” allowedLanguages="dk,en" … />
The language should match the used language exactly (so “fr-BE” ≠ “fr”) and can be completely different or overlap. For the second part of the module it is important though to put the “default” language first.

Second part: request resolver

The second part of the module consists of a request resolver that is put in the httpRequestBegin pipeline after the LanguageResolver.
The current requested URL is checked to disable the functionality for Sitecore URL’s or when not in normal page mode. The current context site is checked for the ‘allowedLanguages’ parameter. If found, and the current language is not part of the ‘allowedLanguages’ set we will redirect the user to homepage in the default language (default is the first language in the set).

Future features

The EasyLingo bar is now only available in the Content Editor. The next step would be to introduce a language bar in Sitecore’s Experience Editor that allows users to quickly switch between the available and allowed languages for the content they are editing. (is already in progress)
We are also working on adding versions of items in languages that are not listed in the system languages.

More information


Wednesday, February 24, 2016

Translating WFFM 8.1 MVC forms error messages part II

Multilingual WFFM

The first post in this duo-series of posts handled the client-side validation of Sitecore's WebForms for Marketers. In this post I want to focus on the server-side error messages: changing them, translating them and the pitfalls you might encounter doing so.

Server-side validation and error messages

Server side validation is a must in forms - no need to tell you that. When using MVC forms you can alter the default messages here:
/sitecore/system/Modules/Web Forms for Marketers/Settings/Meta data/Mvc Validation Error Messages



Translating

As the "value" field of these items is not shared, one would assume that translating the error messages is nothing more than creating versions in other languages and filling in your translated message.
And yes, at first sight you might think that works as you will get to see error messages in a language that could be something else then English and could be even the current language. But it all depends on.. whether you were the first one to request the error messages. As far as we could detect, WFFM caches the error messages but forgot to take the language into account when caching...

So, how do we fix this? Luckily there is a way out. In the documentation of wffm 2.x the "Custom Errors" are mentioned and they are still present in 8.1...

Custom errors

Custom errors are items that can be used as error messages. A few ones exist and you can create more if needed. We used the available custom required and created a new custom email to test.
The items have a key: just look at the regular error message that you would like to use and copy the key (e.g. DynamicRequiredAttribute or DynamicEmailAttribute). In the "value" field you enter your custom message. 
Now create versions for all languages and translate the message as needed.

Using the custom errors

After creating custom error messages, we need to tell our forms to use them. Unfortunately this needs to be done for every field...


In the content editor go to your (required) field. Find the "Dictionary" section and select the desired entries from the list of all custom errors. In many cases that will just be the Custom required, but on an email field you can select for example Custom required and Custom email. Once you've done this for all your fields you're good to go.

Just don't forget to publish all your changes... 

Translating WFFM 8.1 MVC forms error messages part I

Multilingual WFFM

WebForms for Marketers has quite some issues challenges with multilingualism in version 8.1 but in this post I want to focus on the error messages. Error messages come in 2 flavors: client-side and server-side. This first post will handle the client-side validation - server side is for part II.

Client-side validation and error messages

For client-side validation we encountered several issues. First of all, we could not get the messages multi-lingual. Secondly, they didn't work for some fields (e.g. the checkbox list). So the idea we came up with is simple: we remove the client-side validation. 

Removing the client-side validation

First thing to do is disable "Is Ajax MVC Form". Normally this should be sufficient. And it will.. on the first request. But once your visitor gets a validation error (from the server-side validation) the form is reloaded and the client-side validation is back. 

So, let's go for some more serious enhancements then. We'll get rid of the javascript performing the validation!

In "sitecore modules\Web\Web Forms for Marketers\mvc" you will find (amongst others) 2 javascript files: main.js and wffm.js. 

Main.js

  • Remove "jquery_validate: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/jquery/jquery.validate.min",
  • Remove "jquery_validate_unobtrusive: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/jquery/jquery.validate.unobtrusive.min",
  • Edit the "shim" part by removing all references to jquery_validate and jquery_validate_unobtrusive
This should result in:

...
require.config(
{
  baseUrl: generateBaseUrl(),
  paths: {
   jquery: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/jquery/jquery-2.1.3.min",
   jquery_ui: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/jquery/jquery-ui-1.11.3.min",
   bootstrap: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/bootstrap/bootstrap.min",
   wffm: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/wffm.min"
  },
  waitSeconds: 200,
  shim: {
    "bootstrap": {
      deps: ["jquery"]
    },
    "jquery_ui": {
      deps: ["jquery"]
    },
    "wffm": {
      deps: ["jquery", "jquery_ui", "bootstrap"]
    }
  }
});
...

Wffm.js

In wffm.js there are several lines referring to the validation which will show errors in your browsers console when you do not remove them. So we started cleaning up.. posting the whole resulting javascript file would be a bit too long but if you take these steps you will get there:

  • remove the "if (ajaxForm)" part
  • remove the $scw.validator.setDefaults part
  • search for validator and remove the references
  • remove any now unused functions 

And last but not least: as you could see in main.js, the minified version of wff.js is used so you will need to minify your changed version and overwrite wffm.min.js.