by

Going Bilingual in Tridion 2011 with Razor (or: When Text isn’t content)

In  a current project, we have a website that’s in both French and English which we’re transferring into Tridion 2011,  using Razor Mediator. This is not my first bilingual website, and it definitely isn’t my first Razor project, either. However, it’s the first time I’ve had both at the same time. So what I’d like to share is how to solve a specific problem where you have content that could be hard-coded in your Template Building Block (TBB), but can’t, because it needs to be translated.

How about a Real World Example to Start?

Nutrition data
Nutrition data

We have a product detail page for a specific product. On that page is nutritional information. Every product will show the same sets  of information.So from the image, you’ll see that every product will need to show Nutrient Facts VitaminsMinerals, and Ingredients. Now, each of those items falls under the banner of Nutrient Information.

Let’s make sure the requirements are really clear, too:

  • The ingredient section will be titled the same
  • Every product will have the same four sections
  • Every product section will have the same title
  • Every product will have different items in those sections, and different amounts
So what this boils down to is that, despite all the text  that you see, only one thing here is really content manageable, and that’s the list of ingredients and their amounts.

Making the Schema

According to requirements, every product must show the same four sections for that product in the same order — this is a job for an embeddable schema or two.

At the most basic level, this is about a single ingredient name and an ingredient amount, so I’ll first make a schema like so:

A nutrition field embeddable schema
XML Name Description Type Other Info
IngredientName Ingredient Name Text List Comes from a category
IngredientAmount Ingredient Amount PlainText

So, what I’m going to do next is create another embeddable schema. After all, I want this information in distinct groups, so let’s make it as easy as possible for the content author:

Product Nutrition Embeddable Schema
XML Name Description Type Other Info
NutritionFacts Nutrition Facts Embedded Schema Nutrition Ingredient Schema
NutritionVitamins Nutrition Vitamins Embedded Schema Nutrition Ingredient Schema
NutritionMinerals Nutrition Minerals Embedded Schema Nutrition Ingredient
NutritionIngredients Nutrition Ingredients Embedded Schema Nutrition Ingredient Schema

 

So we’ve created an embeddable schema and put that in another embeddable schema. At this point, I now have only one entry to make into my product detail schema:

The product detail schema
XML Name Description Type Other Info
ProductNutrition Nutrition Information Embedded Schema Product Nutrition Schema

 

We certainly haven’t covered all the fields in the product detail schema. Let’s safely assume we actually grab information about the product, a few images, and even get some information about the flavors later.

So how about the Template Building Block

How would you do it if you were monolingual?

If I were in a situation where this product information were only going to ever appear in one language, this would be relatively easy. Below you’ll see an example of how I’d render the “Nutrition Information” section. I’d basically do a copy and paste of that table and h3, change the text in the h3, and call it a day.

<article class="nutritionInformation">
	<h2>Nutritional Information</h2>
	@* THIS SECTION REPEATS*@	
	<h3>Nutrient Facts</h3>
	<table summary="Nutrient Facts">
		<colgroup>
		<col>
		<col style="text-align:right;">
		</colgroup>
		<tbody>
	@foreach(var fact in Fields.ProductNutrition.NutritionFacts){
			<tr>
				<td>@fact.IngredientName</td>
				<td>@fact.NutritionAmount</td>
			</tr>
	}
		</tbody>
	</table><!--/NUTRIENT TABLE -->
@* /END REPEATING SECTION *@
</article>

But isn’t it a bad idea to write text into the code?

Good question. It’s generally an awful idea to write content into your code. But, consider this scenario and the requirements.

Solution Problem with it
Give the content author a plain text field in the schema to write the title Risk of content-authoring mistakes from one product detail to the next
Add a text field, make it a drop-down list, where the content author chooses the section title Risk of content author showing the product info sections in the wrong order
Add a text field, make it a list that comes from categories, showing the different sections There’s still an issue that, from product-to-product the order or the titles could be wrong

What it boils down to, is that, when those sections are always there, always in that order, and always have those titles, they aren’t really content. And in an English-only site, there’s nothing too wrong with writing those titles into the code.

But what about going bilingual?

Couldn’t you localize the TBB?

One option you have is to localize your Component Template and Template Building Block. That’s a generally horrible idea because you’re duplicating identical functionality. I’ve actually seen that — and (sadly) had to do that, too. Every time you update code, you do it in two places. What’s worse, a TBB isn’t exposed to a translation manager like good ol’ components would be; the client has to communicate translations to a developer. It just so happens that the client who localized their TBBs lucked out because I’m fluent in Spanish and French.

So how do you manage those titles without making them content?

The simple solution in this case is that you create a “language labels” schema and component. By doing so, you give the content author full control over the titles.

In this case, I created an embeddable schema

Language Label Embeddable Schema
XML Name Description Type Other Info
LabelKey Language Label Key plain text
LabelValue Language Label Value plain text

With that out of the way, I went ahead and created my content schema, too:

XML Name Description Type Other Info
LabelKey Language Label Key plain text
LabelValue Language Label Value plain text

So now what we have is a component where I can make a ‘key’ and a language title. I’ll use Razor to find the “key” and then return the value. So the question becomes, “how do I do it?”

One option is that I could have the content author stick it in a folder and then, using the webdav address, I have the Razor go and find the component in that designated folder. The problem with that approach is that the component could be moved.

I’m a fan of just creating a component link field in the schema where my content author can attach a Language Labels component of his or her choosing. This means that they can organize content however they like, and they can also update those labels as-needed for a product — if they have to change a label for one specific product, they can create a custom labels component.

The catch in this case is that I, as the developer, have to fill in the “Key” field. There’s also the risk that the content author could change that key field. Now, to at least help myself, I put a nice message in the “description” field that said, “don’t change this”. It doesn’t matter if that “key” field is a drop-down list or comes from a Category, there’s still the risk that the content author could change it – so we just rely on training and good messaging for the moment.

In the end, we have a component loaded with repeatable sets of “Language Keys” and “Language Labels” or values. So now, the content author only has to localize one single component to make sure these titles are changed for all these product pages.

So how does the Razor look for a Language Labels Component?

I found that the simplest solution in this case was a helper function that runs through each LanguageLabel searching for a LabelKey and returning a LabelValue.

@helper renderLabel(dynamic thisLabel){
	@foreach(var label in Fields.SchemaLabels.LanguageLabel){
		@if(label.LabelKey == thisLabel){
			@label.LabelValue
		}
	}	
}

Now that helper function is in place, we just need to render a given label like so:

<h3>@renderLabel("NutrientFacts")</h3>

So now code depends on the content author?

The code doesn’t depend on the content author. I am responsible for writing in those keys, and then I write my Razor template against it. The presentation could be broken by the content author, though, if he chooses to change that key.

There’s another way to mitigate, or maybe mediate, that risk. You could give control of the language keys to the content author.

Letting the content author set the language label key

If you don’t like the fact that the content author that could change something which breaks your code, let’s find a way to remove ourselves from the loop. We could create a third field in our Product Nutrition Embeddable Schema called LanguageKey. It could be a single, plain text field that would be made mandatory. This means that the content author sets the key in the content component and in the language component. So our development now has no dependency on content.

So if we do that, then we can use our Razor helper function like so:

@renderLabel(Fields.ProductNutrition.NutritionFactKey)

We’re just taking a piece of content and sending that to our label renderer to find the matching content.

The catch with setting language label keys

If you go to the approach where you allow even those language label keys to be content-managed, then you have a piece of data that can vary on a per-component basis. We go back to the original risk that a content author could screw that up and break the user experience. In essence, if you take this approach, you might as well just skip everything and give them a plain-text field that they can screw up on a per-component basis.

But, I actually used this ‘set-your-language-key’ approach. In another scenario, I had a modal window with buttons. There could be varying numbers of buttons with different purposes, but probably not more than three or four modal components. The risk of inconsistent user experience is low since so few modals will be created — and the need for flexibility was high since I had a varying amount of buttons where a set amount of keys would not work.

The big idea

There are cases where there’s nothing too wrong with hard-coding text right into a Template Building Block. When it’s a single language, it never changes, and doesn’t need to be content-manageable, it’s ok.

In cases where you have more than one language, you absolutely cannot let a single piece of visually consumed text get written into a TBB. It’s just too ugly and painful to try localizing those TBBs.

Making the non-content text a basic field in the content’s schema opens up the risk of content-authoring mistakes, and therefore a disrupted end-user experience. Non-content text is best off-loaded from the managed content.

In the case where we want to offload the non-content text, an option is a component link to a language labels component. Though there is a small amount of risk that a content author could change the language key in such a way that the label isn’t rendered, you can minimize the risk with a good description and good training.

There are cases where the risk of getting a wrong language key are low, but the need for flexibility is high. In those cases, you can always go the route of letting the content author set that language key.

You can make non-content content content-manageable. It’s easier than re-reading that last sentence.