A comprehensive C# library for working with XLIFF (XML Localization Interchange File Format) files, supporting both XLIFF 1.2 and XLIFF 2.0 specifications.
The idea to create this library was inspired by Microsoft's Multilingual App Toolkit. This project shares no code or other assets with the Multilingual App Toolkit. The Multilingual App Toolkit was deprecated by Microsoft and will no longer be supported after October 15, 2025.
Notmat is not a replacement for Multilingual App Toolkit. And that's OK. The initial code base was created with generous amounts of AI suggestions. And that's OK too.
- ☑️ Multi-Version Support: Works with XLIFF 1.2 and XLIFF 2.0 formats
- ☑️ File I/O Operations: Load and save XLIFF files with automatic version detection
- ☑️ Translation State Management: Full support for translation states and approval workflows
- ☑️ RESX Integration: Convert between XLIFF and .NET .resx resource files
- ☑️ Async Support: Asynchronous file operations for better performance
- ☑️ Type Safety: Strongly-typed models with XML serialization attributes
- ☑️ Comprehensive API: Easy-to-use classes for common localization workflows
- ◻️ AI Translation: Use AI to do translations
- ◻️ Other format: Import/export to Javascript, Android, Apple
- ◻️ Editor: Visual XLIFF editor for Windows & MacOs
- ◻️ Command line tools
- ◻️ Scanning code for etxt strings
- ◻️ Generate XLIFF from REX
- ◻️ Add new language to XLIFF
- ◻️ Generate REX from XLIFF
- ◻️ Nuget: Package availability via Nuget
Add the Notmat.Xliff NuGet package to your project:
dotnet add package Notmat.Xliffusing Notmat;
// Automatically detect version and load XLIFF file
var (version, document) = XliffLoader.LoadXliff("translations.xliff");
if (version == XliffVersion.Version12)
{
var xliff12 = (Xliff12.XliffDocument)document;
// Work with XLIFF 1.2 document
}
else if (version == XliffVersion.Version20)
{
var xliff20 = (Xliff20.XliffDocument)document;
// Work with XLIFF 2.0 document
}
// Load specific versions directly
var xliff12Doc = XliffLoader.LoadXliff12("translations.xliff");
var xliff20Doc = XliffLoader.LoadXliff20("translations.xliff");using Notmat.Xliff12;
// Create a new XLIFF 1.2 document
var xliffDoc = new XliffDocument();
var file = new XliffFile
{
Original = "Resources.resx",
SourceLanguage = "en-US",
TargetLanguage = "fr-FR",
DataType = "resx"
};
// Add translation units
var unit = new TranslationUnit
{
Id = "welcome_message",
ResourceName = "WelcomeMessage",
Source = "Welcome to our application!",
Target = "Bienvenue dans notre application!",
State = TranslationState.Translated
};
file.Body.TranslationUnits.Add(unit);
xliffDoc.Files.Add(file);
// Save the document
XliffSaver.SaveXliff12(xliffDoc, "output.xliff");using Notmat.Converters;
// Convert .resx to XLIFF 1.2
var xliff12 = ResxToXliffConverter.ConvertToXliff12(
"Resources.resx",
"en-US",
"fr-FR"
);
XliffSaver.SaveXliff12(xliff12, "Resources.fr-FR.xliff");
// Convert .resx to XLIFF 2.0
var xliff20 = ResxToXliffConverter.ConvertToXliff20(
"Resources.resx",
"en-US",
"es-ES"
);
XliffSaver.SaveXliff20(xliff20, "Resources.es-ES.xliff");using Notmat.Converters;
// Convert XLIFF to .resx (using target text as values)
XliffToResxConverter.ConvertFile(
"translations.xliff",
"Resources.fr-FR.resx",
useTargetAsValue: true,
includeUntranslated: false
);
// Convert XLIFF document to .resx
var (version, document) = XliffLoader.LoadXliff("translations.xliff");
XliffToResxConverter.ConvertFromXliff(
document,
"output.resx",
useTargetAsValue: true
);The library supports all standard XLIFF translation states:
| State | Description |
|---|---|
New |
Default state - no translation provided |
NeedsTranslation |
Translation is in progress |
NeedsReviewTranslation |
Translation completed but needs review |
Translated |
Translation reviewed and approved |
Final |
Translation is final and approved |
NeedsAdaptation |
Translation needs adaptation |
NeedsL10n |
Translation needs L10n review |
NeedsReviewAdaptation |
Translation needs review for adaptation |
NeedsReviewL10n |
Translation needs review for L10n |
SignedOff |
Translation is signed off |
// Set translation state
unit.State = TranslationState.Translated;
// Check if translation is complete
if (unit.State == TranslationState.Final || unit.State == TranslationState.SignedOff)
{
// Translation is complete
}
// Find units that need translation
var pendingUnits = file.Body.TranslationUnits
.Where(u => u.State == TranslationState.New || u.State == TranslationState.NeedsTranslation)
.ToList();using Notmat;
// Load the source XLIFF file
var (version, sourceDocument) = XliffLoader.LoadXliff("source.en-US.xliff");
if (version == XliffVersion.Version12 && sourceDocument is Xliff12.XliffDocument xliff12)
{
// Create a new document for the target language
var targetDoc = new Xliff12.XliffDocument();
foreach (var sourceFile in xliff12.Files)
{
var targetFile = new Xliff12.XliffFile
{
Original = sourceFile.Original,
SourceLanguage = sourceFile.SourceLanguage,
TargetLanguage = "de-DE", // New target language
DataType = sourceFile.DataType,
Header = sourceFile.Header
};
// Copy translation units and reset states
foreach (var sourceUnit in sourceFile.Body.TranslationUnits)
{
var targetUnit = new Xliff12.TranslationUnit
{
Id = sourceUnit.Id,
ResourceName = sourceUnit.ResourceName,
Source = sourceUnit.Source,
Target = null, // Clear target for new language
State = TranslationState.New // Reset state
};
targetFile.Body.TranslationUnits.Add(targetUnit);
}
targetDoc.Files.Add(targetFile);
}
// Save the new language file
XliffSaver.SaveXliff12(targetDoc, "target.de-DE.xliff");
}using Notmat;
public static class XliffLanguageHelper
{
/// <summary>
/// Creates a new XLIFF file for a target language based on an existing source file
/// </summary>
/// <param name="sourceFilePath">Path to the source XLIFF file</param>
/// <param name="targetLanguage">Target language code (e.g., "de-DE")</param>
/// <param name="outputPath">Output path for the new XLIFF file</param>
/// <param name="preserveTranslations">Whether to preserve existing translations</param>
public static void CreateLanguageVariant(string sourceFilePath, string targetLanguage,
string outputPath, bool preserveTranslations = false)
{
var (version, sourceDocument) = XliffLoader.LoadXliff(sourceFilePath);
switch (version)
{
case XliffVersion.Version12 when sourceDocument is Xliff12.XliffDocument xliff12:
CreateLanguageVariant12(xliff12, targetLanguage, outputPath, preserveTranslations);
break;
case XliffVersion.Version20 when sourceDocument is Xliff20.XliffDocument xliff20:
CreateLanguageVariant20(xliff20, targetLanguage, outputPath, preserveTranslations);
break;
default:
throw new NotSupportedException($"Unsupported XLIFF version: {version}");
}
}
private static void CreateLanguageVariant12(Xliff12.XliffDocument source, string targetLanguage,
string outputPath, bool preserveTranslations)
{
var targetDoc = new Xliff12.XliffDocument();
foreach (var sourceFile in source.Files)
{
var targetFile = new Xliff12.XliffFile
{
Original = sourceFile.Original,
SourceLanguage = sourceFile.SourceLanguage,
TargetLanguage = targetLanguage,
DataType = sourceFile.DataType,
Header = CloneHeader(sourceFile.Header)
};
foreach (var sourceUnit in sourceFile.Body.TranslationUnits)
{
var targetUnit = new Xliff12.TranslationUnit
{
Id = sourceUnit.Id,
ResourceName = sourceUnit.ResourceName,
Source = sourceUnit.Source,
Target = preserveTranslations ? sourceUnit.Target : null,
State = preserveTranslations && !string.IsNullOrEmpty(sourceUnit.Target)
? sourceUnit.State
: TranslationState.New
};
targetFile.Body.TranslationUnits.Add(targetUnit);
}
targetDoc.Files.Add(targetFile);
}
XliffSaver.SaveXliff12(targetDoc, outputPath);
}
private static void CreateLanguageVariant20(Xliff20.XliffDocument source, string targetLanguage,
string outputPath, bool preserveTranslations)
{
var targetDoc = new Xliff20.XliffDocument
{
SourceLanguage = source.SourceLanguage,
TargetLanguage = targetLanguage
};
foreach (var sourceFile in source.Files)
{
var targetFile = new Xliff20.XliffFile
{
Id = sourceFile.Id,
Original = sourceFile.Original,
Header = CloneHeader20(sourceFile.Header)
};
foreach (var sourceUnit in sourceFile.Units)
{
var targetUnit = new Xliff20.TranslationUnit
{
Id = sourceUnit.Id,
Name = sourceUnit.Name
};
foreach (var sourceSegment in sourceUnit.Segments)
{
var targetSegment = new Xliff20.Segment
{
Id = sourceSegment.Id,
Source = sourceSegment.Source,
Target = preserveTranslations ? sourceSegment.Target : null,
State = preserveTranslations && !string.IsNullOrEmpty(sourceSegment.Target)
? sourceSegment.State
: TranslationState.New
};
targetUnit.Segments.Add(targetSegment);
}
targetFile.Units.Add(targetUnit);
}
targetDoc.Files.Add(targetFile);
}
XliffSaver.SaveXliff20(targetDoc, outputPath);
}
private static Xliff12.XliffHeader? CloneHeader(Xliff12.XliffHeader? source)
{
if (source == null) return null;
return new Xliff12.XliffHeader
{
Tool = source.Tool != null ? new Xliff12.XliffTool
{
ToolId = source.Tool.ToolId,
ToolName = source.Tool.ToolName,
ToolVersion = source.Tool.ToolVersion
} : null,
Notes = source.Notes.ToList()
};
}
private static Xliff20.XliffHeader? CloneHeader20(Xliff20.XliffHeader? source)
{
if (source == null) return null;
return new Xliff20.XliffHeader
{
Tool = source.Tool != null ? new Xliff20.XliffTool
{
Id = source.Tool.Id,
Name = source.Tool.Name,
Version = source.Tool.Version
} : null,
Notes = source.Notes.ToList()
};
}
}
// Usage example:
XliffLanguageHelper.CreateLanguageVariant("source.en-US.xliff", "de-DE", "target.de-DE.xliff");-
Identify the Source File: Start with your source XLIFF file (e.g.,
Resources.en-US.xliff) -
Create the Target File: Use the
XliffLanguageHelper.CreateLanguageVariant()method or create manually -
Translate Content: Open the new XLIFF file in your preferred translation tool or edit programmatically:
var (version, document) = XliffLoader.LoadXliff("target.de-DE.xliff"); if (document is Xliff12.XliffDocument xliff12) { foreach (var file in xliff12.Files) { foreach (var unit in file.Body.TranslationUnits) { if (unit.Id == "welcome_message") { unit.Target = "Willkommen in unserer Anwendung!"; unit.State = TranslationState.Translated; } } } XliffSaver.SaveXliff12(xliff12, "target.de-DE.xliff"); }
-
Update Translation States: Mark translations as complete:
// Mark as translated unit.State = TranslationState.Translated; // Mark as final after review unit.State = TranslationState.Final;
-
Generate Resources: Convert back to .resx if needed:
XliffToResxConverter.ConvertFile( "target.de-DE.xliff", "Resources.de-DE.resx", useTargetAsValue: true, includeUntranslated: false );
XliffLoader: Load XLIFF files with automatic version detectionXliffSaver: Save XLIFF files in various formatsResxToXliffConverter: Convert .resx files to XLIFF formatXliffToResxConverter: Convert XLIFF files to .resx format
Xliff12.XliffDocument: Root documentXliff12.XliffFile: File containerXliff12.TranslationUnit: Individual translation entryXliff12.XliffNote: Notes and comments
Xliff20.XliffDocument: Root documentXliff20.XliffFile: File containerXliff20.TranslationUnit: Translation unit containerXliff20.Segment: Individual translation segmentXliff20.XliffNote: Notes and comments
XliffVersion: XLIFF version (1.2 or 2.0)TranslationState: Translation workflow statesApprovalState: Approval status (XLIFF 2.0)
# Build the solution
dotnet build
# Run tests
dotnet test
# Pack for NuGet
dotnet packTo create the NuGet package (.nupkg) for Notmat.Xliff using csproj-based packing:
# From the repository root
# Build and pack the library in Release
dotnet pack Notmat.Xliff/Notmat.Xliff.csproj -c Release -o distThe package will be placed in the dist/ folder. To publish to NuGet.org:
# Replace with your actual API key
dotnet nuget push dist/*.nupkg --api-key $env:NUGET_API_KEY --source https://api.nuget.org/v3/index.json- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.