Skip to content

Use relative ResXFileRef paths in translated resx#17077

Draft
mwiemer-microsoft wants to merge 1 commit into
mainfrom
mwiemer-fix-xliff-resx-paths
Draft

Use relative ResXFileRef paths in translated resx#17077
mwiemer-microsoft wants to merge 1 commit into
mainfrom
mwiemer-fix-xliff-resx-paths

Conversation

@mwiemer-microsoft

Copy link
Copy Markdown
Member

Summary

Treats the absolute ResXFileRef rewrite as an intentional design choice, but adjusts the design for translated .resx outputs so cached intermediate files do not bake in the repo clone path.

TranslateSource writes translated files under the XLIFF intermediate output directory. The existing RewriteRelativePathsToAbsolute behavior appears intentional: the code comment says external-file references, often icons, have relative paths adjusted to absolute paths, and the existing unit test asserts the absolute rewrite. I did not find separate docs or issue/PR discussion explaining more than that; the behavior arrived in Arcade with the 2023 import from dotnet/xliff-tasks.

Changes

  • Adds RewriteRelativePathsForOutputPath(sourceFullPath, outputFullPath) as the output-aware hook, defaulting to the existing absolute rewrite for non-.resx document types.
  • Overrides that hook for .resx so ResXFileRef values are written relative to the translated .resx output directory.
  • Updates TranslateSource to call the output-aware hook.
  • Adds a regression test proving the generated .resx contains an output-relative path, not the absolute source clone path.

Fixes #17061.

Validation

  • ./eng/common/dotnet.cmd test ./src/Microsoft.DotNet.XliffTasks.Tests/Microsoft.DotNet.XliffTasks.Tests.csproj --configuration Debug

TranslateSource writes translated .resx files under the XLIFF intermediate output directory. ResXFileRef values were intentionally rewritten from source-relative paths to absolute paths so generated files could still resolve external resources after moving away from the source directory, but that bakes the repo location into cached intermediate files.

Add an output-path-aware rewrite for resx file references so the generated file points back to the resource relative to the translated .resx location. Keep the existing absolute rewrite API as the default for existing callers and tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 6, 2026 17:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts XliffTasks’ path-rewrite behavior so translated .resx files written under the XLIFF intermediate output directory do not embed absolute ResXFileRef paths tied to a specific repo clone location, improving incremental build reliability after moving/renaming a repo.

Changes:

  • Introduces an output-aware rewrite hook (RewriteRelativePathsForOutputPath(sourceFullPath, outputFullPath)) on TranslatableDocument.
  • Updates TranslateSource to call the output-aware hook when generating translated outputs.
  • Overrides the hook for .resx to write ResXFileRef values relative to the translated .resx output directory, and adds a regression test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/Microsoft.DotNet.XliffTasks/Tasks/TranslateSource.cs Calls the new output-aware rewrite hook prior to saving the translated document.
src/Microsoft.DotNet.XliffTasks/Model/TranslatableDocument.cs Adds the virtual output-aware path rewrite hook with a default implementation.
src/Microsoft.DotNet.XliffTasks/Model/ResxDocument.cs Implements .resx-specific output-relative rewriting of ResXFileRef values (and adds helper logic).
src/Microsoft.DotNet.XliffTasks.Tests/ResxDocumentTests.cs Adds a regression test asserting output-relative ResXFileRef paths and no baked-in absolute source paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +99 to +118
private static string MakeRelativePath(string fromDirectory, string toPath)
{
string fromFullPath = Path.GetFullPath(fromDirectory);
string toFullPath = Path.GetFullPath(toPath);
string fromRoot = Path.GetPathRoot(fromFullPath);
string toRoot = Path.GetPathRoot(toFullPath);
StringComparison comparison = Path.DirectorySeparatorChar == '\\'
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;

if (!string.Equals(fromRoot, toRoot, comparison))
{
return toFullPath;
}

Uri fromUri = new(AppendDirectorySeparator(fromFullPath));
Uri toUri = new(toFullPath);
string relativePath = Uri.UnescapeDataString(fromUri.MakeRelativeUri(toUri).ToString());
return relativePath.Replace('/', Path.DirectorySeparatorChar);
}
Comment on lines +87 to +95
XElement valueNodeOfFileRef = node.Element("value");
string[] splitRelativePathAndSerializedType = valueNodeOfFileRef.Value.Split(';');
string resourceRelativePath = splitRelativePathAndSerializedType[0].Replace('\\', Path.DirectorySeparatorChar);
string absoluteResourcePath = Path.GetFullPath(Path.Combine(sourceDirectory, resourceRelativePath));

splitRelativePathAndSerializedType[0] = MakeRelativePath(outputDirectory, absoluteResourcePath);

valueNodeOfFileRef.Value = string.Join(";", splitRelativePathAndSerializedType);
}

@mmitche mmitche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that that it's already outputting absolute paths makes me wonder whether this is a requirement in some way. I don't know enough to say.

My guess is that scenarios like this break in other ways, and that we generally don't support moving a repo around and getting a working incremental build.

Any thoughts @ViktorHofer?

@ViktorHofer

Copy link
Copy Markdown
Member

I want to defer to @tmat as he knows most about this code base.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TranslateSource bakes absolute ResXFileRef paths into cached translated .resx files, breaking incremental builds after repo rename/move (MSB3103)

4 participants