We have a very big Sitecore solution with over 3000 editors, unfortunately they spend a great deal of time creating a content in the wrong language.
Easy enough to fix a single item, but when each page has a lot of renderings, which in turn have a lot of data source items (see image below), the task is not so easy and very time consuming.

Solution
Provide the ability to select a given item and move all the language versions from one language to another and then delete the source language version.
I decided to add 2 buttons, and I was lucky as there was already a Data Migration chunk I could add the buttons to.

See this blog for a detailed introduction to adding buttons to the sitecore client.
In order to know which language to move from and to, I added a source and target parameter to the move language command.

This is very easy todo, as on the “click” field for small button you can add parameters, then when sitecore calls the Execute function the CommandContext has the values in the Parameters list, see the code below, which can then identify the source and target language.
public override void Execute(CommandContext context)
{
var currentItem = context?.Items[0];
if (currentItem == null)
return;
string source = context?.Parameters["source"];
Assert.IsNotNullOrEmpty(source, $"Command parameter:source can not be empty or null");
string target = context?.Parameters["target"];
Assert.IsNotNullOrEmpty(target, $"Command parameter:source can not be empty or null");
context.Parameters.Add("item", currentItem.ID.ToString());
context.ClientPage.Start(this, nameof(Move), context.Parameters);
}
We need some configuration and in the solution there was already a data migration settings item that could store the following:
- Modules Folder Template id
- So I can identify which sub item contains the data source items and don’t iterate over the entire tree.
- List of fields to ignore
- i.e. revision, updated, updated by, owner, security, etc.

The code to move the language versions is quite simple.
- Get the current item, using the source language.
- Get the settings item.
- Get the items in the modules folder, by looking for any sub items that derive from the Modules Folder Template, then adding their descendants.
- For each item
- Get all the source versions (see code below)
- For each version create the new target language version
- For each field
- Skip all shared fields
- Skip fields that are in the Fields to Exclude list
- Copy the field
- For each field
- Remove all the source language version
private void Copy(
[NotNull] Database database,
[NotNull] Item source,
[NotNull] Language target,
[NotNull] IEnumerable<Field> languageMigrationExcludedFields)
{
Assert.ArgumentNotNull(source, nameof(source));
Assert.ArgumentNotNull(target, nameof(target));
Assert.ArgumentNotNull(languageMigrationExcludedFields, nameof(languageMigrationExcludedFields));
foreach (var sourceVersion in source.Versions.GetVersions())
{
if (sourceVersion == null)
continue;
var targetItem = database.GetItem(source.ID, target);
var newTargetVersion = targetItem.Versions.AddVersion();
Copy(sourceVersion, newTargetVersion, languageMigrationExcludedFields);
sourceVersion.Versions.RemoveVersion();
}
}
Hope this helps, cheers Alan