I’ve recently done a lot of work with my current client to get SharePoint variations working and as a result I’ve learned that you need to be very patient and persistent and keep a full bottle of Excedrin nearby. Though this post is not directly related to stsadm extensions the results of my efforts on this project produced a couple of new commands which I’ll touch upon here and detail in a follow-up post. Before you begin working on a multilingual site using variations I highly suggest that you read the Building Multilingual Solutions Using 2007 SharePoint Products and Technologies white paper.

Variations in, a nutshell, allow you to present the same pages using different views of the page. Though variations are typically used for multilingual sites they can also be used for other things such as media specific views. For this post I’m going to create multilingual variation with the source variation being English and a single target variation using Korean. The first thing we need to do is make sure that the operating system is ready for our language packs to be installed. If you’re using an East Asian language, such as Korean, then you will need to prep the OS, otherwise the OS should already be capable of handling the language pack. To install the East Asian languages on the OS go to Start -> Control Panel -> Regional and Language Options. On the “Languages” tab select the appropriate options and click OK. This will install the necessary language files which will require a reboot when complete.

Now that the OS is prepared you are ready to install the language packs. The install is real straightforward so I won’t detail it here. If you don’t already have a web application created to host your variation sites you’ll want to do that now. Once you’ve identified your target web application you are ready to create your site collection. It’s important to note that the variations publishing feature is scoped at the site collection level – this means that you cannot have a source variation in one site collection and a target variation in another. Also, the variation features are installed with the publishing feature so you must pick a publishing template when creating your initial site collection or activate the publishing features. For your root site collection make sure you pick the language template that your administrators are going to be able to work with as your end-users will never see the root site collection stuff.

To create your variation hierarchy you must first enable variations. This is done by going to the root of the site collection and selecting “Site Actions -> Site Settings -> Modify All Settings -> Variations”. You first need to establish the variation root site – typically you’ll just put a “/” which will make the root of site collection the variation root but you can make the variation root be a sub-site of the site collection. The other options are fairly straightforward but the “Update Target Web Parts” and “Resources” options may need some clarification. If you choose to update web parts with changes from the source be aware that any translations or changes that you may have made in the target variations will be overwritten with that of the source. So if you’re using a content editor web part and translating that content on the target sites then those translations will be overwritten when the page is propagated. This may or may not be what you intend to happen.

One other thing that may require some additional clarification is the Resources option. If you leave the option set to reference existing resources then there’s essentially no change to the content – it will simply references images, for example, on the source site. The copy option, however, will copy images to the target variations. Note that if the image in question does not exist in the variations PublishingImages library then it won’t be copied. I’ve not had to deal with other resource types so I can’t say what the limitations are for anything other than images.

Once you’ve established that the site collection is to use variations you now need to create your variation labels. When you initially create your labels you are not actually creating any instances of a site – you are just telling SharePoint how you want your sites to be created. The sites are actually created later when you choose to create the variation hierarchy. The first label you want to create is your source label. It’s important to note that once you’ve established your source you cannot change it later. If you would a custom site template to be available here make sure that the FilterCategories property of your site template is set to PublishingSiteTemplate (you can find information about how to create your own publishing site templates here). The “Label Name” field is what will appear in your URL – so if you specify “en-us” as the label name your url for the variation will look like “http://demo/en-us/”. For the “Hierarchy Creation” option you need to specify what you want copied when the label hierarchy is created. The other options on this screen are very straightforward – you just need to specify the language template to use and the local and a name.

For this demo I’ve setup a source variation with a label name of “EN”, “English” as the language template, “English (United States)” as the locale, “US-English” as the name, and “Publishing Site with Workflow” as the site template. I’ve also chosen to copy the publishing site and all pages during hierarchy creation.

Once your source variation is setup you can set up all of your target variations. Note that you don’t have to set the target variations up right away – you can create your hierarchy now and have it just create your source variation, get it looking/working the way you want and then add target variations as
you are ready to handle them. I would recommend though that you at least create one target variation so that you can work out the various issues that you’re likely to encounter (see below) before you roll out your source variation.

For the purposes of this demonstration I’m going to create one target variation label which I’ll name “KO”. The language template and locale will be set to “Korean”.

Once you’ve established your labels you are ready to create your variation hierarchy. You do this by clicking the “Create Hierarchies” link on the “Variation Labels” page.

Now that you’ve got your variations created you can go to the site. Notice that if you go to the root of the site collection (in my case “http://demo/” you will be automatically redirected to the variation whose locale matches that of your browser. If SharePoint is unable to find a locale match then it will redirect you to the source variation. The redirect is happening because SharePoint has replaced the welcome page of the root site collection with a new page called “VariationRoot.aspx”. It’s important to note that the default.aspx page is still present and if a user navigates to that page they will see it so I recommend either deleting this page or adding a redirect web part or something similar to redirect users to the VariationRoot.aspx file so that it can do its thing.

Okay, so you’ve got your variations set up, now what? Now it’s time to create some content (realistically though you’re going to want to work on branding and custom page layouts first but as there are tons of posts on how to do this I’m going to assume you’ve got that covered and are ready for creating content). Editing the page in your source variation is no different than editing any publishing page. However, once you publish the page the changes that you have made will be automatically copied to the variations. Note that the act of copying the page is the result of an event receiver that is hooked up to the Pages library. The event receiver will inspect the page and determine what needs to occur and then trigger a timer job to handle the actual copying of the page from the source library to all target libraries. This timer job uses the Deployment API to copy the pages and if you’ve read any of my earlier posts you know that this deployment API has numerous limitations (I’ll touch upon a couple of problems below). Because a timer job is used you won’t see the pages propagated immediately – it could take several seconds to several minutes (I’ve even see it take several hours to show up). If you’d like the page to be copied immediately then you can trigger it by selecting the “Update Variations” option from the “Tools” menu on the page editing toolbar. If the page doesn’t already exist in a variation you can use the “Submit a Variation…” link to submit the page to all variations. If the page already exists in all variations then the “Submit a Variation…” link will effectively just work like the Create Page action. Note that you can also use content deployment jobs to handle the translation of content by external agencies. The XML that results from a content deployment job is a nightmare to work with but it wouldn’t take much to provide a custom interface to that could be provided to your translators to work with the content.

Now that you’ve got your changes made and you’ve created the variation, how do you get to that other pages? One option is to simply edit the URL but you’re users aren’t going to know how to do that. Fortunately there’s a built in user control that we can enable called the Variation Label Control. The control works via a SharePoint delegate control which is added to the master page of all publishing sites. To enable the control you simply have to un-comment the server control tag in the “12\template\controltemplates\VariationsLabelMenu.ascx” file. Once this has been enabled by un-commenting the server control tag then a drop down menu will appear at the top of the site allowing the user to jump to matching pages in other variations.

It’s important to understand how SharePoint relates the various pages to each other because in certain situations this relationship can get corrupted. At the root of the site collection you can find a hidden list called “Relationships List”. This list contains entries which map the relationship between the various pages. The behavior of the Variation Label Menu control is driven by this list so if the menu is not listing all pages then most likely it is because the Relationships List became corrupted (for example, an administrator on the Korean side decided to prevent changes from being propagated by deleting the page in the Korean Pages library and recreating it with new translated content).

Let’s take a look at this hidden list – in our demo site we’ll navigate to “http://demo/Relationships%20List/AllItems.aspx”. Once here we immediately see a few items with the title set to nothing (the first thing I usually do is create a new view showing at least the following columns: GroupID, ObjectID, and Deleted. There will always be at least one entry in this list corresponding to the variation home which we set up earlier and defined as “/”. This entry, which will be the first item in the list, will always have “F68A02C8-2DCC-4894-B67D-BBAED5A066F9” as it’s GroupID and whatever was set as the home path as it’s ObjectID. ParentAreaID has been empty in every example I’ve ever seen and does not appear to be used. The GroupID field is used to group related elements. So each variation label in the hierarchy will have the same GroupID and each page will have the same GroupID as its copied page in each variation. The ObjectID is always the server relative url of the element (so the path to the variation label or page).

It’s important to understand though that this is not the only place in which we find this information. SharePoint will also store the GroupID and a backward link to an item in this list within two hidden fields on the page: PublishingVariationGroupID and Publishing

VariationRelationshipLinkFieldID, respectively. This information is also replicated in the MetaInfo property bag. If we run my custom gl-exportlistitem2 command we can see what these values are for the default.aspx page:

C:\>stsadm -o gl-exportlistitem2 -url http://demo/en/pages/forms/allitems.aspx -path c:\exportdata\pages_en

The following is a snippet of the Manifest.xml file which results from running the above command (note that I’ve pulled some elements that were not relevant):

 1<Items SiteUrl="http://demo" WebUrl="/en">
 2  <Item IsPublishingPage="True" ContentTypeId="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF390064DEA0F50FC8C147B0B6EA0636C4A7D400040A230B8F679045ADE877C7A560A0E7" PageLayout="WelcomeLinks.aspx" File="c:\exportdata\pages_en\Data\default.aspx" CreatedDate="4/3/2008 4:27:48 PM" LeafName="default.aspx" FolderUrl="">
 3    <Fields>
 4      <Field Name="Title">Home</Field>
 5      <Field Name="PublishingVariationGroupID">41ad5bd3-a882-4d50-a2ac-db5de6eb872c</Field>
 6      <Field Name="PublishingVariationRelationshipLinkFieldID">http://demo/Relationships List/4_.000, /Relationships List/4_.000</Field>
 7      <Field Name="MetaInfo">PublishingPageContent:SW|Test
 8vti_parserversion:SR|12.0.0.6219
 9vti_charset:SR|utf-8
10vti_author:SR|
11vti_setuppath:SX|SiteTemplates\\BLANKINTERNET\\default.aspx
12vti_cachedneedsrewrite:BR|false
13PublishingPageImage:SW|
14SummaryLinks:SW|&lt;div title="_schemaversion" id="_3"&gt;\r\n  &lt;div title="_view"&gt;\r\n    &lt;span title="_columns"&gt;1&lt;/span&gt;\r\n    &lt;span title="_linkstyle"&gt;&lt;/span&gt;\r\n    &lt;span title="_groupstyle"&gt;&lt;/span&gt;\r\n  &lt;/div&gt;\r\n&lt;/div&gt;
15vti_modifiedby:SR|SPDEV\\spadmin
16vti_cachedhastheme:BR|false
17PublishingVariationGroupID:SW|41ad5bd3-a882-4d50-a2ac-db5de6eb872c
18SummaryLinks2:SW|&lt;div title="_schemaversion" id="_3"&gt;\r\n  &lt;div title="_view"&gt;\r\n    &lt;span title="_columns"&gt;1&lt;/span&gt;\r\n    &lt;span title="_linkstyle"&gt;&lt;/span&gt;\r\n    &lt;span title="_groupstyle"&gt;&lt;/span&gt;\r\n  &lt;/div&gt;\r\n&lt;/div&gt;
19vti_cachedcustomprops:VX|PublishingPageImage PublishingPageContent SummaryLinks PublishingVariationGroupID SummaryLinks2 ContentType PublishingVariationRelationshipLinkFieldID vti_title PublishingPageLayout
20ContentTypeId:SW|0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF390064DEA0F50FC8C147B0B6EA0636C4A7D400040A230B8F679045ADE877C7A560A0E7
21PublishingVariationRelationshipLinkFieldID:SW|http://demo/Relationships List/4_.000, /Relationships List/4_.000
22vti_cachedtitle:SR|Home
23vti_title:SR|Home
24ContentType:SW|Welcome Page
25PublishingPageLayout:SW|/_catalogs/masterpage/WelcomeLinks.aspx, /_catalogs/masterpage/WelcomeLinks.aspx
26</Field>
27    </Fields>
28  </Item>
29</Items>

Knowing that this information is stored in two places like this becomes extremely important if you are trying to troubleshoot issues with the variation propagation process. If the data in the Relationships List does not match up with the data in the page or if the related pages do not share the same GroupID then the variation propagation will fail.

Things that can go wrong (and they will)

Now that you hopefully have at least a basic understanding of what variations are and how they work we can begin to discuss where things fall apart (and if your deployment is anything more than demoware then you most likely will run into one or all of these issues). The following issues are what I’ve personally encountered:

  1. The Relationships List can get “confused” and have either missing or invalid entries
  2. The page fields linking the page to the Relationships List can become invalid
  3. Web parts must inherit from Microsoft.SharePoint.WebPartPages.WebPart or the variation propagation will fail causing the application pool to crash (there is a hotfix for this: http://support.microsoft.com/kb/948947)
  4. Your pages must have a valid contact or no contact or you will not be able to get into the page settings for the page (this is more of a content deployment problem and not a variation problem)
  5. Your pages must have a valid page layout URL or the variation propagation will fail causing the application pool to crash (among other things)
  6. Content types for pages must match
  7. The variation label menu will only work with variation pages and not system pages (such as a list view page)
  8. The variation label menu will not preserve querystring values
  9. If you’re working with large sites (several thousand pages and multiple variations) expect propagation times in terms of days (I’ve not personally dealt with this one but in my small environment with only a few pages it takes close to an hour for the pages to propagate so it’s easy to extrapolate out)
  10. Navigation structure is not preserved from one variation to another
  11. Root site pages and other content is still available if I know how to get to it
  12. Custom list definitions and site definitions will need to be localized (use resource files) – definitions reverse engineered using Solution Generator will not be localized by default
  13. Values in localized choice columns will lose localization settings on propagation
  14. Only pages in the “Pages” library will be propagated – list items will have to be handled using custom event receivers or workflows
  15. Page field values are overwritten when a page is propagated

In the following sections I will try to cover each of the above issues and what I did to work around the issue.

1,2 – Relationships List Gets “Confused” and/or Page Fields are Invalid or Missing

There are times when the items in the Relationships List can get out of sync with the pages they are supposed to correspond to or vice-a-versa (either entries could be missing or IDs can be incorrect or mismatched). When this happens you will run into issues with the propagation of page changes to variations. For example, you may receive errors stating that a page already exists when you go to propagate changes to a page. The most common way for this list to get corrupted is when pages are imported from other environments. To fix the data in the Relationships List and the page fields I created a new stsadm command called “gl-fixvariationrelationships”. The command essentially loops through all the pages in a variation and tries to find the page with the matching name in the other variations and then rebuilds the data in the Relationships List and the page fields based on its findings. Here’s an example of how to run this command:

c:\>stsadm -o gl-fixvariationrelationships -url http://portal -verbose

Update 4/14/2008: I’ve just finished documenting this command here.

Update 9/2/2008: Tim Dobrinski has a great tool that he’s put together for fixing many issues with the relationships list (including addressing sub-sites which I’m not currently dealing with). You can find details about the tool here: http://www.thesug.org/blogs/lsuslinky/Lists/Posts/Post.aspx?List=ee6ea231%2D5770%2D4c2d%2Da99c%2Dc7c6e5fec1a7&ID=21

3 – Web Parts Must Inherit From Microsoft.SharePoint.WebPartPages.WebPart

I consider this particular issue to be a bug with the product because it could have been easily solved. To demonstrate this I created a simple HelloWorld web part which just output some text and inherited from System.Web.UI.WebControls.WebPart. I added this web part to my default.aspx page and published the page. The result is that an InvalidCastException is thrown and the application pool crashes! You can see the error details below – note that you’ll see three errors in the event log all saying essentially the same thing.

If I change my web part code to inherit from Microsoft.SharePoint.WebPartPages.WebPart then the variation propagation works just fine. So for your custom web parts the solution is real easy – just inherit from the SharePoint WebPart base class. For third party web parts (the Dundas Chart web parts are a good example of this) you will need to wrap them in a custom web part. The interesting thing to note is that even though the propagation seemed to fail and the application pool crashed I found that in most cases the web part was in fact successfully propagated. The reason for this is because the failure happens in the PublishingPage object’s FixWebPartUrlsForVariation method. When this method is called the page and web parts have already been propagated and now the API is simply repairing some URLs within the web parts on the page – so in many cases repairs won’t be necessary so everything will work just fine. Microsoft could avoid this bug by simply doing a type check in the FixWebPartUrlsForVariation method before doing the cast.

Note that the only web parts out-of-the-box that are affected by the FixWebPartUrlsForVariation method are those that inherit from IWebPartVariationUpdate which at present only include the ContentByQueryWebPart and the TableOfContentsWebPart.

Update 4/21/2008: There is a hotfix for this issue – http://support.microsoft.com/kb/948947 – thanks to djeeg for adding the comment about this!

4 – Pages Must Have a Valid Contact

There are many ways in which the contact for a page can become invalid. The most common is when a contact is deleted from the site collection. Another way is when a page is imported from one environment to another. Having an invalid contact “shouldn’t” prevent the page variation propagation from occurring, so as far as variations are concerned this shouldn’t be an issue. However, many shops will choose to have a separate authoring environment and will import the pages into their public environment. In this scenario you are very likely to end up with invalid contacts on your pages. The issue comes when you choose to view the “Page Settings and Schedules” page. If your page has an invalid contact associated with it you will receive a user not found error. Of course the real problem here is that this is the page that allows you to fix the contact so your essentially stuck with no way of fixing the contact. This seems like an obvious bug – the settings page should be able to handle the case of a contact being deleted so that you can assign a valid contact.

To work around these issues I’ve created another new custom stsadm command which I called gl-fixpagecontact. This command will identify all pages with an invalid contact and will either attempt to locate a valid contact, assign the current user as the contact, or assign a passed in user as the contact. The contact field for the page is stored as a string in the format of “id;#name”. Often times when a page is imported from another environment the name will match but the ID will be different. In this case the command will attempt to locate a principle with a matching name.

Here’s an example of how to run this command:

C:\>stsadm -o gl-fixpagecontact -scope site -url http://demo -verbose

5 – Pages Must Have a Valid Page Layout URL

This particular issue can come up in a variety of situations and isn’t necessarily limited to variations. There are two ways in which this problem typically manifests itself – the first is when a page is migrated from another site (the entire site may have also been migrated) and the second is when you assign a page layout that exists in your source variation but not in your target variation. To demonstrate the problem I created a new page layout which was just a copy of the WelcomeLinks.aspx page and stored it in the master page gallery located at http://demo/en/_catalogs/masterpage/forms/allitems.aspx – I then changed the /en/default.aspx page to use this new page layout. After approving the page I clicked the “Update Variations” menu item to propagate my page. The result is that 3 error events were logged in the event log and the application pool crashed!

To resolve these issues make sure that you either use only site collection level page layouts or that for every page layout you have in your variation sites you make sure that there is a layout with the same name in every other variation site (the contents can be different – they just need to be named the same). In some cases (particularly when you import the content from another source) you won’t be able to reset the page layout using the browser (going to the “Page Settings and Schedules” page will throw a FileNotFoundException). To help fix this problem you can use a command I created a while ago called gl-fixpublishingpagespagelayouturl. This command has a variety of different options but here’s a command of how to explicitly set the layout for a specific page:

C:\>stsadm -o gl-fixpublishingpagespagelayouturl -url http://demo/en -pagename default.aspx -pagelayout "http://demo/\_catalogs/masterpage/WelcomeLinks.aspx, http://demo/\_catalogs/masterpage/WelcomeLinks.aspx" -verbose -scope page

6 – Page Content Types Must Match

I’ve not experienced this particular issue but I thought it was worth mentioning here for completeness. Jeremy Jameson gives a great explanation of the problem in his 3 part posts “Dumping MOSS 2007 Variations” so I won’t bother re-iterating it here (the gist of it though – use SP1 with custom site definitions if you need to use page content types).

7 – Variations Label Menu Only Works with Publishing Pages

This is rather minor limitation but its one you should be aware of before you decided to use it. If you don’t allow any access to system or list pages (/_layouts/viewlsts.aspx or /Documents/Forms/AllItems.aspx for example) then you don’t need to worry about this. If you do and you have matching pages/lists throughout all your variations then you’ll need to create a custom data source or your own menu if you want the ability to jump between variations.

8 – Variation Label Menu Does Not Preserve Querystring Values

I consider this to be a minor issue as well. If you have a page which, for example, uses a page field filter which takes in values from the querystring and you then use the variation label menu to jump between variations those querystring values will not be preserved. The reason I consider this minor is because I personally consider this menu to be nothing but a waste. If you’re asked to use it you really should ask yourself why? Is it for the end users so that they can read the same content in different languages (who does that?)? Or is it for your testers so that they can jump between the various variations easily? I think you’ll find that this is a feature that, under most circumstances, will never be used by your end users – so why deploy it?

9 – Long Running Propagation Jobs with Large Sites

This is another one that I don’t have personal experience with but Jeremy Jameson explains the problem real well in his previously mentioned post “Dumping MOSS 2007 Variations”.

10 – Navigation Structure Doesn’t Propagate Between Variations

This isn’t actually a bug – it’s by design and I completely understand the reasoning behind it. You may have very different navigation needs between different languages and there’s no easy way for the Microsoft to anticipate all needs. However, most will want the structure of their global and current navigation to match across all variations. This just another one of those things that you need to anticipate and plan for as you plan out your multilingual sites. One thing that I’ve used to help keep things in sync is my gl-enumnavigation command to export the navigation from the source variation and then let the translators translate the resultant XML and then use my gl-setnavigationnodes(/index.php/2007/09/more-site-navigation-settings-commands/) command to set the navigation for the target variations (at least for an initial site propagation – after that I’ve just made the changes manually).

11 – Root default.aspx and Other Root Pages Are Still Available

One thing I found that needs some clarification in the Building Multilingual Solutions Using 2007 SharePoint Products and Technologies white paper is its description of the VariationRoot.aspx file:

“After a variation hierarchy is created, the Variations feature replaces the default page of the variation’s home site with a special page called VariationRoot.aspx”

To be more precise – the feature changes the Welcome Page settings so that VariationRoot.aspx is now the welcome page of the site – so if I type in “http://demo/” I’ll be taken to the variation that’s appropriate based on my browser settings. However, if I type in “http://demo/pages/default.aspx” I’ll be taken to the root site collections default.aspx page, not the variation site. The same goes for any other content, lists, and sites that exist at the root site (assuming I have access). The solution here is pretty simple – if you don’t want them getting to it either delete it (if it’s not needed) or lock it down so they can’t get access to it. In the case of the default.aspx page you may want to keep it there and keep the access open and then just add a redirection web part or some simple javascript to handle redirecting to the VariationRoot.aspx page.

12 – Custom List Definitions Should be Localized

If you’re creating custom list definitions then you should plan on making sure those list definitions are localized – set display names, choice values, query parameters, etc., to resource strings. One thing to be aware of is that if you use Solution Generator (part of the Visual Studio Extensions for WSS) to reverse engineer your lists you’ll have to set all the fields to use localized strings which could add some time to your development/QA cycle.

13 – Localized Choice Columns Fail on Propagation

If you use any localized list definitions, as mentioned above, be aware that a side effect is that choice column values will be stored using the actual values, not the resource strings. Sjoert Ebben discusses this issue in his post “5 reasons why you should not use variations”. Sjoert recommends a couple of solutions, one of which is to create a custom field type which will store the resource string and not the actual value.

14 – Only Pages in the Pages Library will be Propagated to Variations

To some this may seem like a pretty obvious point but I found that many I spoke to about using variations initially were rather surprised that they couldn’t enable this functionality for any list. In order to get list values (such as announcements and such) propagated between variations I enabled moderation on the desired lists and created a custom event receiver which, upon approval, copies the list item to the lists with the same name in all variations. You could also use a custom workflow to do the same – it really just depends on how complex your needs are. You’ll find that handling modifications to data will require careful consideration. I’m hoping to provide a sample of my event receiver in a follow-up post so be on the lookout for that.

15 – Page Field Values Are Overwritten Upon Propagation

So here’s the dilemma – best practice with publishing pages is to use page fields for all content because web part changes are not stored with the history of the page – unfortunately though, when you are using a page field and you approve your page in the source variation the page will propagate to all target variations and all page field values will be overwritten. One solution to this is to add a custom page field where the editor can indicate whether the content change is a minor modification or a new version and then create a custom workflow which will delete the draft in the variation if the page is a minor modification. Some solutions will decide to simply put all content in web parts and just turn off the web part propagation – I still don’t recommend this as you will not be able to roll back to a previous version of the page (again, web part changes are stored separately from page data and cannot be rolled back!).

Conclusion

Variations can be a very cool feature that, for simple sites, could give you exactly what you need to get you up and running with a multilingual site. The key with variations, as with just about any SharePoint feature, is careful planning. Make sure you give yourself plenty of time to prototype your solution and, most importantly, get your translators involved early on as you will find that many things will work just fine when everything is in English but as soon as you start translating your content and resource files you will encounter issues. Sometimes these issues can be addressed with simple planning and change control policies but in most cases you’ll be looking at custom code and/or workflows and/or site and/or list definitions.

So, if you’re going to be working with variations make sure you’ve got lots of coffee, plenty of headache drugs, a good punching bag, and lots of time to switch to a backup plan 😉