Translating Page URLs without Localizing Pages

A piece of advise that I learned from a wise Tridion sensei is that it’s not a good practice to localize pages in Tridion if you have a multi-lingual site (though there is an exception to this rule which I’ll discuss below).  The reason for this is simple: you lose the ability to centrally manage your site from the Master web publication.  Once a page is localized, you have to manage it individually, which is not ideal if you want to centrally update component presentations on a page across all the language sites.  So what do you do if you want to obey the rule of not localizing pages and your customer asks to have localized/translated URLs? 

Before I jump into discussing my solution, I want to state that localizing pages is not always a bad idea (it was in the old school days, but not any more).  If your implementation is going to use SDL Translation Management System or World Server, then we can localize pages all we want; and if we ever need to unlocalize to flip all the component presentations on the page, then re-localizing and getting a new translation will come for free from TMS’s translation memory.  Yeehaw!

Ok, and if a customer doesn’t plan to use TMS or World Server and has some other third party process to deal with translations?  Then we come back to the original idea that localizing pages is not so good.  Now that we’ve established this, let’s look at some options for translating a page URL without localizing the page…

A big portion of the URL is the path leading up to the page filename.  This is the path of the structure group hierarchy.  This part is easy – localize your SGs and translate the directory names.  There is no impact with regard to continue being able to manage pages from the central Master publication.  The issue comes with translating the page’s metadata (e.g. keywords, description, title, etc), but mainly the page filename.  You can’t really do it without localizing the page… or can you?  If we don’t account for the filename, then the answer is obvious: put all the translatable metadata into a component and in the page metadata schema simply link to that component.  But what do we do about the filename?

Well, if we didn’t obey our “don’t localize pages” rule, we’d just localize the page, translate the page filename, publish the page and voila!  So let’s build on that.  Once a page is published, it’s out there on the delivery side with the translated URL.  We can continue making changes to our page on the CM side without impacting what’s already published.  What I’m hinting at is, we can localize a page, translate the page filename, publish the page, then before anyone notices unlocalize it.

So let’s turn to one of my favourite parts of Tridion (2011 SP1 +): the TOM.NET Event System.  As I mentioned above regarding using a component to store the page’s translatable metadata, we can add a field to this component called “local page filename”.  Here is what the metadata component would look like:

During the event of publishing a page, we add some very basic logic to:

  1. localize the page
  2. grab the local page filename value from the metadata component
  3. update the page filename with the localized value
  4. save the page.

Then on the event of transaction state change, i.e. when the localized page has been sent off to the deployer, we simply unlocalize it in the CM.  No need for custom deployers.  No need to hack into the Broker database.  We stick to Tridion’s OOTB functionality as much as possible and add a few lines of Event System code.

Here is the code:

namespace ContentBloom.Tridion.Events
{
	[TcmExtension("Publish or Unpublish Events")]
	public class PublishOrUnpublishEvents : TcmExtension
	{
        private const string TEMP_LOCALIZED_FLAG = "[Localized for Translation by Event System]";

        public PublishOrUnpublishEvents()
		{
            EventSystem.Subscribe<Page, PublishEventArgs>(SetLocalizedPageFileName, EventPhases.Initiated);
            EventSystem.Subscribe<Page, SetPublishStateEventArgs>(UnlocalizePageOncePublished, EventPhases.Initiated);
		}

        /// <summary>
        /// In order to get a localized filename for a page, we grab the filename from the page metadata localized component.
        /// Then we localize the page on publish, update the filename to the localized one from the component and publish.
        /// Then a corresponding event on TransactionCommitted unlocalizes the page so that it can continue to be maintained 
        /// from a master web structure publication.
        /// </summary>
        /// <param name="subject"></param>
        /// <param name="e"></param>
        /// <param name="phase"></param>
        public void SetLocalizedPageFileName(Page page, PublishEventArgs args, EventPhases phase)
        {
            string localFilename = page.GetLocalilizedFileNameFromMetadataComponent();
            if (!string.IsNullOrEmpty(localFilename))
            {
                page.Localize();
                if (page.TryCheckOut())
                {
                    //append a flag temporarily to indicate that this page has been localized temporatily to get a translated filename.
                    //and not localized intentionally for another purpose.
                    page.Title = string.Format("{0}{1}", page.Title, TEMP_LOCALIZED_FLAG);
                    page.FileName = localFilename;
                    page.Save(true);
                }
            }

        }

        public void UnlocalizePageOncePublished(Page page, SetPublishStateEventArgs args, EventPhases phase)
        {
            if(page.IsLocalized && page.Title.Contains(TEMP_LOCALIZED_FLAG))
                page.UnLocalize();
        }
	}

    public static class ExtensionMethods
    {
		public static string GetLocalilizedFileNameFromMetadataComponent(this Page page)
		{
			string localFilename = string.Empty;
			if (page.Metadata != null)
			{
				ItemFields fields = new ItemFields(page.Metadata, page.MetadataSchema);
				if (fields.Contains("LocalizableMeta"))
				{
					ComponentLinkField localMetaField = fields["LocalizableMeta"] as ComponentLinkField;
					Component component = localMetaField.Value;
					if (component != null)
					{
						ItemFields compFields = new ItemFields(component.Content, component.Schema);
						if (compFields.Contains("LocalizedPageFilename"))
						{
							SingleLineTextField fileNameTextField = compFields["LocalizedPageFilename"] as SingleLineTextField;
							if (fileNameTextField != null)
								localFilename = fileNameTextField.Value;
						}
					}
				}
			}
			return localFilename;
		}
	}
}

2 thoughts on “Translating Page URLs without Localizing Pages

  1. Incredibly innovative solution to this age old problem. I like it! One thought – does Experience manager/SiteEdit freak out if the published version is later than the version in the CMS?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>