Suppose your EPiServer CMS site contains a large number of pages and supports multiple languages, for example English (en), French (fr), and Italian (it).
You want to remove one language branch, such as French, from all pages across the site, but you do not know exactly how many pages currently use that language.
If you try to remove the language directly from the CMS admin UI, you will usually hit an error because the language is still being used by content or language settings.
Before You Start
- Take a database backup first.
- Test the process in a lower environment before running it in production.
- Disable the target language in Admin/Settings > Manage Website Languages so editors cannot keep creating new versions while the cleanup is running.
- This approach will not delete pages where the language you want to remove is the master language. Those must be handled separately.
Small Sites vs Large Sites
If only a handful of pages use the language, the built-in Versions gadget is usually enough.
But if the site is large, deleting language branches manually page by page quickly becomes painful. In that case, a scheduled job is the safer and more practical option.
Modern Approach
In newer EPiServer / Optimizely CMS solutions, the cleanest approach is to:
- Loop through the site start pages and their descendants.
- Check whether a page has the language branch you want to remove.
- Skip pages where that language is the master language.
- Delete the branch with
IContentRepository.DeleteLanguageBranch.
Scheduled Job Example
using System;
using System.Collections.Generic;
using System.Linq;
using EPiServer;
using EPiServer.Core;
using EPiServer.PlugIn;
using EPiServer.Scheduler;
using EPiServer.Security;
using EPiServer.Web;
[ScheduledPlugIn(
DisplayName = "Delete French Language Branch From All Pages",
Description = "Deletes the fr branch from all pages where fr is not the master language.",
SortIndex = 100)]
public class DeleteLanguageBranchFromAllPagesJob : ScheduledJobBase
{
private readonly IContentLoader _contentLoader;
private readonly IContentRepository _contentRepository;
private readonly ISiteDefinitionRepository _siteDefinitionRepository;
private bool _stopRequested;
private const string LanguageToDelete = "fr";
public DeleteLanguageBranchFromAllPagesJob(
IContentLoader contentLoader,
IContentRepository contentRepository,
ISiteDefinitionRepository siteDefinitionRepository)
{
_contentLoader = contentLoader;
_contentRepository = contentRepository;
_siteDefinitionRepository = siteDefinitionRepository;
IsStoppable = true;
}
public override string Execute()
{
var scanned = 0;
var deleted = 0;
var skippedMasterLanguage = 0;
var failed = 0;
var visited = new HashSet<int>();
foreach (var site in _siteDefinitionRepository.List().Where(x => !ContentReference.IsNullOrEmpty(x.StartPage)))
{
var references = new[] { site.StartPage }.Concat(_contentLoader.GetDescendents(site.StartPage));
foreach (var contentLink in references)
{
if (_stopRequested)
{
return $"Stopped. Scanned: {scanned}, Deleted: {deleted}, Skipped master language: {skippedMasterLanguage}, Failed: {failed}";
}
if (!visited.Add(contentLink.ID))
{
continue;
}
scanned++;
try
{
var languageBranches = _contentRepository
.GetLanguageBranches<IContent>(contentLink)
.OfType<PageData>()
.ToList();
if (!languageBranches.Any())
{
continue;
}
var branchToDelete = languageBranches.FirstOrDefault(x =>
string.Equals(x.Language.Name, LanguageToDelete, StringComparison.OrdinalIgnoreCase));
if (branchToDelete == null)
{
continue;
}
if (branchToDelete is ILocalizable localizable &&
localizable.MasterLanguage != null &&
string.Equals(localizable.MasterLanguage.Name, LanguageToDelete, StringComparison.OrdinalIgnoreCase))
{
skippedMasterLanguage++;
continue;
}
_contentRepository.DeleteLanguageBranch(contentLink, LanguageToDelete, AccessLevel.Delete);
deleted++;
}
catch (Exception ex)
{
failed++;
OnStatusChanged($"Failed for content ID {contentLink.ID}: {ex.Message}");
}
}
}
return $"Completed. Scanned: {scanned}, Deleted: {deleted}, Skipped master language: {skippedMasterLanguage}, Failed: {failed}";
}
public override void Stop()
{
_stopRequested = true;
}
}
How It Works
The job goes through each site start page and all descendant pages beneath it. For every page, it checks whether the target language exists.
If the page contains the target branch and that branch is not the master language, the job removes it. If the target language is the master language, the page is skipped.
After the Job
- Review the skipped pages. These are typically pages where the language you want to remove is the master language.
- Try removing the language from Manage Website Languages again.
- If the CMS still reports that the language is used in language settings, clear that language from the affected start pages or content language settings and retry.
Final Note
If you only need to remove language branches from a few pages, use the UI. If you need to clean up hundreds or thousands of pages, a scheduled job like the above is a much more realistic approach.
If you also want to remove the same language from blocks or media, the same idea can be extended beyond PageData to other localizable content types.