Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 30 additions & 12 deletions src/Microsoft.DotNet.XliffTasks.Tests/ResxDocumentTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using XliffTasks.Model;
Expand Down Expand Up @@ -49,34 +50,51 @@ public void BasicLoadAndTranslate()
}

[Fact]
public void RewriteFileReferenceToAbsoluteInDestinyFolder()
public void RewriteFileReferenceToRelativePathFromOutputFolder()
{
string sourceFolder = Directory.GetCurrentDirectory();
string expectedAbsoluteLocation = Path.Combine(
Directory.GetCurrentDirectory(),
@"Resources\Package.ico".Replace('\\', Path.DirectorySeparatorChar));
// Simulates: source .resx is in src/MyProject/, translated output .resx is in artifacts/obj/MyProject.xlf/
// The resource file at src/MyProject/Resources/Package.ico should be referenced as
// a relative path from the output directory.
string tempBase = Path.Combine(Path.GetTempPath(), "xliff-test-" + Path.GetRandomFileName());
string sourceFolder = Path.Combine(tempBase, "src", "MyProject");
string outputFolder = Path.Combine(tempBase, "artifacts", "obj", "MyProject.xlf");
string sourceFullPath = Path.Combine(sourceFolder, "Resources.resx");
string outputFullPath = Path.Combine(outputFolder, "MyProject.resx");

string resourceRelativePath = Path.Combine("Resources", "Package.ico");
string resourceAbsolutePath = Path.GetFullPath(Path.Combine(sourceFolder, resourceRelativePath));

// Compute expected relative path from output directory to resource file
Uri fromUri = new Uri(outputFolder.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar);
Uri toUri = new Uri(resourceAbsolutePath);
string expectedRelativePath = Uri.UnescapeDataString(fromUri.MakeRelativeUri(toUri).ToString())
.Replace('/', Path.DirectorySeparatorChar);

string source =
@"<root>
$@"<root>
<data name=""400"" type=""System.Resources.ResXFileRef, System.Windows.Forms"">
<value>Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<value>{resourceRelativePath};System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>";

string expectedTranslation =
@"<root>
$@"<root>
<data name=""400"" type=""System.Resources.ResXFileRef, System.Windows.Forms"">
<value>ABSOLUTEPATH;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<value>{expectedRelativePath};System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>".Replace("ABSOLUTEPATH", expectedAbsoluteLocation);
</root>";

ResxDocument document = new();
StringWriter writer = new();
document.Load(new StringReader(source));
document.RewriteRelativePathsToAbsolute(
Path.Combine(sourceFolder, "Resources.resx"));
document.RewriteRelativePathsForOutputPath(sourceFullPath, outputFullPath);
document.Save(writer);

AssertEx.EqualIgnoringLineEndings(expectedTranslation, writer.ToString());

// The path in the output must NOT be absolute — it must be relative,
// so the translated .resx remains valid after the repo is renamed or moved.
Assert.DoesNotContain(tempBase, writer.ToString());
}


Expand Down
42 changes: 28 additions & 14 deletions src/Microsoft.DotNet.XliffTasks.Tests/VsctDocumentTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using XliffTasks.Model;
Expand Down Expand Up @@ -71,35 +72,47 @@ public void BasicLoadAndTranslate()
}

[Fact]
public void RewriteHrefOfImageToAbsoluteInDestinyFolder()
public void RewriteHrefOfImageToRelativePathFromOutputFolder()
{
string sourceFolder = Directory.GetCurrentDirectory();
string expectedAbsoluteLocation = Path.Combine(
Directory.GetCurrentDirectory(),
@"Resources\Images.png".Replace('\\', Path.DirectorySeparatorChar));
// Simulates: source .vsct is in src/MyProject/, translated output is in artifacts/obj/MyProject.xlf/
string tempBase = Path.Combine(Path.GetTempPath(), "xliff-vsct-test-" + Path.GetRandomFileName());
string sourceFolder = Path.Combine(tempBase, "src", "MyProject");
string outputFolder = Path.Combine(tempBase, "artifacts", "obj", "MyProject.xlf");
string sourceFullPath = Path.Combine(sourceFolder, "MyPackage.vsct");
string outputFullPath = Path.Combine(outputFolder, "MyPackage.vsct");

string resourceRelativePath = Path.Combine("Resources", "Images.png");
string resourceAbsolutePath = Path.GetFullPath(Path.Combine(sourceFolder, resourceRelativePath));

Uri fromUri = new Uri(outputFolder.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar);
Uri toUri = new Uri(resourceAbsolutePath);
string expectedRelativePath = Uri.UnescapeDataString(fromUri.MakeRelativeUri(toUri).ToString())
.Replace('/', Path.DirectorySeparatorChar);

string source =
@"<CommandTable xmlns=""http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
$@"<CommandTable xmlns=""http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<Bitmaps>
<Bitmap guid=""guidImages"" href=""Resources\Images.png"" usedList=""bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"" />
<Bitmap guid=""guidImages"" href=""{resourceRelativePath}"" usedList=""bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"" />
</Bitmaps>
</CommandTable>";

string expectedTranslation =
@"<CommandTable xmlns=""http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
$@"<CommandTable xmlns=""http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<Bitmaps>
<Bitmap guid=""guidImages"" href=""ABSOLUTEPATH"" usedList=""bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"" />
<Bitmap guid=""guidImages"" href=""{expectedRelativePath}"" usedList=""bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"" />
</Bitmaps>
</CommandTable>".Replace("ABSOLUTEPATH", expectedAbsoluteLocation);
</CommandTable>";

VsctDocument document = new();
StringWriter writer = new();
document.Load(new StringReader(source));
document.RewriteRelativePathsToAbsolute(
Path.Combine(sourceFolder, "Resources.resx"));
document.RewriteRelativePathsForOutputPath(sourceFullPath, outputFullPath);
document.Save(writer);

AssertEx.EqualIgnoringLineEndings(expectedTranslation, writer.ToString());

// The path in the output must NOT be absolute
Assert.DoesNotContain(tempBase, writer.ToString());
}

[Fact]
Expand All @@ -112,11 +125,12 @@ public void DoesNotNullReferenceWhenNoHRef()
</Bitmaps>
</CommandTable>";

string sourceFullPath = Path.Combine(Directory.GetCurrentDirectory(), "Resources.vsct");

VsctDocument document = new();
StringWriter writer = new();
document.Load(new StringReader(source));
document.RewriteRelativePathsToAbsolute(
Path.Combine(Directory.GetCurrentDirectory(), "Resources.resx"));
document.RewriteRelativePathsForOutputPath(sourceFullPath, sourceFullPath);
document.Save(writer);

AssertEx.EqualIgnoringLineEndings(source, writer.ToString());
Expand Down
9 changes: 6 additions & 3 deletions src/Microsoft.DotNet.XliffTasks/Model/ResxDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,11 @@ protected override IEnumerable<TranslatableNode> GetTranslatableNodes()
}
}

public override void RewriteRelativePathsToAbsolute(string sourceFullPath)
public override void RewriteRelativePathsForOutputPath(string sourceFullPath, string outputFullPath)
{
string sourceDir = Path.GetDirectoryName(sourceFullPath);
string outputDir = Path.GetDirectoryName(outputFullPath);

foreach (XElement node in Document.Descendants("data"))
{
if (node.Attribute("type")?.Value == "System.Resources.ResXFileRef, System.Windows.Forms")
Expand All @@ -64,8 +67,8 @@ public override void RewriteRelativePathsToAbsolute(string sourceFullPath)
string[] splitRelativePathAndSerializedType = valueNodeOfFileRef.Value.Split(';');
string resourceRelativePath = splitRelativePathAndSerializedType[0].Replace('\\', Path.DirectorySeparatorChar);

string absolutePath = Path.Combine(Path.GetDirectoryName(sourceFullPath), resourceRelativePath);
splitRelativePathAndSerializedType[0] = absolutePath;
string absoluteResourcePath = Path.GetFullPath(Path.Combine(sourceDir, resourceRelativePath));
splitRelativePathAndSerializedType[0] = MakeRelativePath(outputDir, absoluteResourcePath);

valueNodeOfFileRef.Value = string.Join(";", splitRelativePathAndSerializedType);
}
Expand Down
32 changes: 30 additions & 2 deletions src/Microsoft.DotNet.XliffTasks/Model/TranslatableDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,39 @@ public void Translate(IReadOnlyDictionary<string, string> translations)
}

// rewrite nodes that point to external files (used often for icons, etc.)
// these will have relative paths adjusted to absolute path.
public virtual void RewriteRelativePathsToAbsolute(string sourceFullPath)
// these will have relative paths adjusted to a path relative to the output file.
public virtual void RewriteRelativePathsForOutputPath(string sourceFullPath, string outputFullPath)
{
}

/// <summary>
/// Computes a relative path from <paramref name="fromDirectory"/> to <paramref name="toPath"/>.
/// Falls back to an absolute path if the paths are on different drives.
/// </summary>
protected static string MakeRelativePath(string fromDirectory, string toPath)
{
// Ensure fromDirectory ends with a separator so Uri treats it as a directory
if (!fromDirectory.EndsWith(Path.DirectorySeparatorChar) &&
!fromDirectory.EndsWith(Path.AltDirectorySeparatorChar))
{
fromDirectory += Path.DirectorySeparatorChar;
}

Uri fromUri = new Uri(fromDirectory);
Uri toUri = new Uri(toPath);

// If on different drives (Windows), fall back to absolute path
if (fromUri.Scheme != toUri.Scheme || fromUri.Host != toUri.Host)
{
return toPath;
}

Uri relativeUri = fromUri.MakeRelativeUri(toUri);
string relativePath = Uri.UnescapeDataString(relativeUri.ToString());

return relativePath.Replace('/', Path.DirectorySeparatorChar);
}

protected abstract void LoadCore(TextReader reader);

protected abstract void SaveCore(TextWriter writer);
Expand Down
10 changes: 6 additions & 4 deletions src/Microsoft.DotNet.XliffTasks/Model/VsctDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ private HashSet<string> FindNonUniqueIds()
return conflictingIds;
}

public override void RewriteRelativePathsToAbsolute(string sourceFullPath)
public override void RewriteRelativePathsForOutputPath(string sourceFullPath, string outputFullPath)
{
string sourceDir = Path.GetDirectoryName(sourceFullPath);
string outputDir = Path.GetDirectoryName(outputFullPath);

foreach (XElement imageTag in Document.Descendants(Document.Root.Name.Namespace + "Bitmap"))
{
XAttribute hrefAttribute = imageTag.Attribute("href");
Expand All @@ -84,9 +87,8 @@ public override void RewriteRelativePathsToAbsolute(string sourceFullPath)
{
string resourceRelativePath = hrefAttribute.Value.Replace('\\', Path.DirectorySeparatorChar);

string absolutePath = Path.Combine(Path.GetDirectoryName(sourceFullPath), resourceRelativePath);

imageTag.Attribute("href").Value = absolutePath;
string absoluteResourcePath = Path.GetFullPath(Path.Combine(sourceDir, resourceRelativePath));
imageTag.Attribute("href").Value = MakeRelativePath(outputDir, absoluteResourcePath);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.DotNet.XliffTasks/Tasks/TranslateSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ protected override void ExecuteCore()

Directory.CreateDirectory(Path.GetDirectoryName(translatedFullPath));

sourceDocument.RewriteRelativePathsToAbsolute(Path.GetFullPath(sourcePath));
sourceDocument.RewriteRelativePathsForOutputPath(Path.GetFullPath(sourcePath), Path.GetFullPath(translatedFullPath));
sourceDocument.Save(translatedFullPath);
}
}
Expand Down
Loading