Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localization #2041

Merged
merged 9 commits into from
Feb 22, 2023
Merged

Localization #2041

merged 9 commits into from
Feb 22, 2023

Conversation

adamsitnik
Copy link
Member

@adamsitnik adamsitnik commented Feb 2, 2023

I've not configured OneLocBuild as we may move the code to dotnet/runtime where it's already configured for the whole repo.

To move the existing translations from SDK repo to here I've wrote a console app that took care of mapping the translations by ID, by original English text (some IDs has changed but text remained the same).

The app is ugly but it does what it's needed:

using System.Text;
using System.Transactions;
using System.Xml;
using System.Xml.Linq;

namespace CopyTranslations
{
    internal class Program
    {
        static void Main(string[] args)
        {
            DirectoryInfo sdkFolder = new DirectoryInfo(args[0]);
            DirectoryInfo sclFolder = new DirectoryInfo(args[1]);

            foreach (FileInfo sourceFile in sdkFolder.GetFiles("*.xlf"))
            {
                string extension = sourceFile.Name.Substring(sourceFile.Name.IndexOf('.'));
                string destinationFilePath = Path.Combine(sclFolder.FullName, $"Resources{extension}");

                using FileStream src = sourceFile.OpenRead();
                using XmlReader srcXml = XmlReader.Create(src);

                XmlDocument sourceDocument = new ();
                sourceDocument.Load(srcXml);

                XmlNode srcBodyNode = sourceDocument.ChildNodes[1]!.ChildNodes[0]!.ChildNodes[0]!;
                Dictionary<string, string> idToTarget = new ();
                Dictionary<string, string> sourceToTarget = new ();

                foreach (XmlNode node in srcBodyNode.ChildNodes)
                {
                    if (GetSource(node) != GetTarget(node)) // some has no translations
                    {
                        idToTarget.Add(GetId(node), GetTarget(node));
                        sourceToTarget.TryAdd(Normalize(GetSource(node)), GetTarget(node));
                    }
                }

                XmlDocument destinationDocument = new ();
                destinationDocument.Load(destinationFilePath);

                XmlNode dstBodyNode = destinationDocument.ChildNodes[1]!.ChildNodes[0]!.ChildNodes[0]!;

                foreach (XmlElement destinationElement in dstBodyNode.ChildNodes)
                {
                    string id = GetId(destinationElement);
                    string source = GetSource(destinationElement);

                    if (idToTarget.TryGetValue(id, out string? translation) // same IDs
                        || sourceToTarget.TryGetValue(Normalize(source), out translation)) // same English text
                    {
                        if (source.Contains("'{0}'") && translation.Contains("„{0}”"))
                        {
                            translation = translation.Replace("„{0}”", "'{0}'");
                        }
                        else if (source.Contains("'{0}'") && translation.Contains("\"{0}\""))
                        {
                            translation = translation.Replace("\"{0}\"", "'{0}'");
                        }
                        else if (source.Contains("'{0}'") && !translation.Contains("'{0}'"))
                        {
                            translation = translation.Replace("{0}", "'{0}'");
                        }

                        if (source.EndsWith('.') && !translation.EndsWith('.'))
                        {
                            translation += ".";
                        }

                        translation = translation.Replace(" :", ":");

                        destinationElement.ChildNodes[1]!.InnerText = translation;
                        destinationElement.ChildNodes[1]!.Attributes!["state"]!.InnerText = "translated";
                    }
                }

                destinationDocument.Save(destinationFilePath);
            }

            static string GetId(XmlNode node) => node.Attributes!["id"]!.InnerText;

            static string GetSource(XmlNode node) => node.ChildNodes[0]!.InnerText!;

            static string GetTarget(XmlNode node) => node.ChildNodes[1]!.InnerText!;

            static string Normalize(string formatString)
            {
                StringBuilder buffer = new(formatString);
                for (int i = 0; i < 4; i++)
                {
                    buffer.Replace("'{" + i + "}'", "{" + i + "}");
                }

                buffer.Replace(".", "");
                buffer.Replace(",", "");

                return buffer.ToString();
            }
        }
    }
}

# Conflicts:
#	src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt
#	src/System.CommandLine/Builder/CommandLineBuilder.cs
#	src/System.CommandLine/CommandLineConfiguration.cs
#	src/System.CommandLine/ParseResult.cs
@@ -32,6 +32,8 @@
<Compile Include="..\Common\ArgumentBuilder.cs" Link="Utility\ArgumentBuilder.cs" />
<Compile Include="..\Common\OptionBuilder.cs" Link="Utility\OptionBuilder.cs" />
<Compile Include="..\System.CommandLine.Suggest\DotnetMuxer.cs" Link="Utility\DotnetMuxer.cs" />
<Compile Include="..\System.CommandLine\LocalizationResources.cs" Link="LocalizationResources.cs" />
Copy link
Member Author

Choose a reason for hiding this comment

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

I am including these types and the resources here so the tests can call the internal APIs to verify error messages.

using System.Linq;
using Xunit;

namespace System.CommandLine.ApiCompatibility.Tests
Copy link
Member Author

Choose a reason for hiding this comment

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

I've added this type to a different project than System.CommandLine.Tests to ensure that it does not use the resources included in that project.

@adamsitnik adamsitnik marked this pull request as ready for review February 16, 2023 22:05
@adamsitnik
Copy link
Member Author

@KalleOlaviNiemitalo I've removed all the unused methods and resources, thank you for the feedback!

I was also curious if it's not a bug that we don't use them. It turned out that we are now just using different methods for the same purposes:

internal static string RequiredArgumentMissing(SymbolResult symbolResult) =>

internal static string ExpectsOneArgument(OptionResult optionResult)

@KalleOlaviNiemitalo
Copy link

Huh. I wonder if the unused resources were preserved for API compatibility only.

# Conflicts:
#	src/System.CommandLine/Builder/CommandLineBuilder.cs
#	src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs
#	src/System.CommandLine/CommandLineConfiguration.cs
#	src/System.CommandLine/Help/HelpOption.cs
#	src/System.CommandLine/Help/VersionOption.cs
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.

3 participants