Importing resx files and documentation updates.

This commit is contained in:
Kay Unkroth 2021-03-26 14:17:03 -07:00
Родитель f2c74441fb
Коммит 6ce7537260
11 изменённых файлов: 468 добавлений и 246 удалений

Просмотреть файл

@ -65,7 +65,7 @@ namespace MTCmd
}
catch (Exception ex)
{
Console.WriteLine($"{ex}");
Console.Error.WriteLine($"{ex}");
}
});
@ -75,29 +75,66 @@ namespace MTCmd
static void Import(DataModel model, FileInfo importFile, bool overwriteMode)
{
Func<string, bool> import = (lcid) => {
if (Path.GetExtension(importFile.Name).Equals(".csv", StringComparison.InvariantCultureIgnoreCase))
{
model.ImportFromCsv(importFile.FullName, lcid, overwriteMode);
return true;
}
else if (Path.GetExtension(importFile.Name).Equals(".resx", StringComparison.InvariantCultureIgnoreCase))
{
string referenceResx = $"{importFile.DirectoryName}\\{model.DefaultCulture}.resx";
if(File.Exists(referenceResx))
{
try
{
model.ImportFromResx(importFile.FullName, referenceResx, lcid, overwriteMode);
return true;
}
catch (NoResxMatchesException noMatch)
{
Console.Error.WriteLine(string.Format(Strings.NoResxMatches, noMatch.TranslationResx, noMatch.ReferenceResx));
return false;
}
}
else
{
Console.Error.WriteLine(string.Format(Strings.noResxReferenceFile, importFile.FullName, referenceResx));
return false;
}
}
else
{
Console.Error.WriteLine(Strings.invalidFileType);
return false;
}
};
if (importFile != null)
{
string lcid = Path.GetFileNameWithoutExtension(importFile.Name);
if (model.SetLanguageFlags(lcid, true))
{
model.ImportFromCsv(importFile.FullName, lcid, overwriteMode);
model.Update();
Console.WriteLine(Strings.importSuccess, importFile);
if (import(lcid))
{
model.Update();
Console.WriteLine(Strings.importSuccess, importFile);
}
}
else
{
Console.WriteLine(Strings.invalidLocale);
Console.Error.WriteLine(Strings.invalidLocale);
}
}
else
{
Console.WriteLine(Strings.noImportFileSpecified);
Console.Error.WriteLine(Strings.noImportFileSpecified);
}
}
static void Export(Mode mode, DataModel model, DirectoryInfo exportFolder, string lcid)
{
Action<string> export = (path) => { if (mode == Mode.ExportResx) model.ExportToResx(path); else model.ExportToCsv(path); };
Action export = () => { if (mode == Mode.ExportResx) model.ExportToResx(exportFolder.FullName); else model.ExportToCsv(exportFolder.FullName); };
if (exportFolder != null)
{
@ -106,22 +143,22 @@ namespace MTCmd
model.DeselectAllLanguages();
model.SetLanguageFlags(lcid, true, false);
export(exportFolder.FullName);
export();
Console.WriteLine(Strings.singleLocalExportSuccess, lcid, exportFolder);
}
else if (model.HasTargetLanguages)
else if (model.HasTargetLanguages || mode == Mode.ExportResx)
{
export(exportFolder.FullName);
export();
Console.WriteLine(Strings.exportSuccess, exportFolder);
}
else
{
Console.WriteLine(Strings.noExportableTranslations);
Console.Error.WriteLine(Strings.noExportableTranslations);
}
}
else
{
Console.WriteLine(Strings.noExportFolderSpecified);
Console.Error.WriteLine(Strings.noExportFolderSpecified);
}
}
}

31
MetadataTranslator/MTCmd/Strings.Designer.cs сгенерированный
Просмотреть файл

@ -88,7 +88,7 @@ namespace MTCmd {
}
/// <summary>
/// Looks up a localized string similar to The full path and name of the comma-separated values (csv) translation file to import..
/// Looks up a localized string similar to The full path and name of the comma-separated values (csv) or resource (resx) translation file to import..
/// </summary>
internal static string ifDescription {
get {
@ -106,7 +106,16 @@ namespace MTCmd {
}
/// <summary>
/// Looks up a localized string similar to Either the imported language matches the default locale, or the file name does not correspond to a supported locale identifier. The file name convention is &lt;lcid&gt;.csv..
/// Looks up a localized string similar to Invalid translation file type. The file must be either a comma-separated values (csv) file or a resource (resx) file..
/// </summary>
internal static string invalidFileType {
get {
return ResourceManager.GetString("invalidFileType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Either the imported language matches the default locale, or the file name does not correspond to a supported locale identifier. The file name convention is &lt;lcid&gt;.csv or &lt;lcid&gt;.resx..
/// </summary>
internal static string invalidLocale {
get {
@ -159,6 +168,24 @@ namespace MTCmd {
}
}
/// <summary>
/// Looks up a localized string similar to No translated strings found! The keys of the reference resx &apos;{0}&apos; and the translation resx &apos;{1}&apos; don&apos;t seem to match..
/// </summary>
internal static string NoResxMatches {
get {
return ResourceManager.GetString("NoResxMatches", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unable to import &apos;{0}&apos; because the reference resx file with the default language strings was not found. In addition to the import resx, make sure the following reference resx file also exists: {1}.
/// </summary>
internal static string noResxReferenceFile {
get {
return ResourceManager.GetString("noResxReferenceFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Metadata Translator command-line app.
/// </summary>

Просмотреть файл

@ -127,13 +127,16 @@
<value>Existing translations exported to: {0}</value>
</data>
<data name="ifDescription" xml:space="preserve">
<value>The full path and name of the comma-separated values (csv) translation file to import.</value>
<value>The full path and name of the comma-separated values (csv) or resource (resx) translation file to import.</value>
</data>
<data name="importSuccess" xml:space="preserve">
<value>Translation file {0} imported successfully.</value>
</data>
<data name="invalidFileType" xml:space="preserve">
<value>Invalid translation file type. The file must be either a comma-separated values (csv) file or a resource (resx) file.</value>
</data>
<data name="invalidLocale" xml:space="preserve">
<value>Either the imported language matches the default locale, or the file name does not correspond to a supported locale identifier. The file name convention is &lt;lcid&gt;.csv.</value>
<value>Either the imported language matches the default locale, or the file name does not correspond to a supported locale identifier. The file name convention is &lt;lcid&gt;.csv or &lt;lcid&gt;.resx.</value>
</data>
<data name="lcidDescription" xml:space="preserve">
<value>A valid Windows Language Code Identifier (LCID), aka language tag, such as en-US, af-NA, and zh-CN.</value>
@ -150,6 +153,12 @@
<data name="noImportFileSpecified" xml:space="preserve">
<value>Use the --import-file option to specify an import file for this operation.</value>
</data>
<data name="NoResxMatches" xml:space="preserve">
<value>No translated strings found! The keys of the reference resx '{0}' and the translation resx '{1}' don't seem to match.</value>
</data>
<data name="noResxReferenceFile" xml:space="preserve">
<value>Unable to import '{0}' because the reference resx file with the default language strings was not found. In addition to the import resx, make sure the following reference resx file also exists: {1}</value>
</data>
<data name="rootCmdDescription" xml:space="preserve">
<value>Metadata Translator command-line app</value>
</data>

Просмотреть файл

@ -15,30 +15,30 @@
{
"Entry"
{
"MsmKey" = "8:_0635D5A6F23DB1379A0FD91FF1B01507"
"OwnerKey" = "8:_7C7487CE661C4E1E5239FB4306E0749B"
"MsmKey" = "8:_15129D0347D11843939442B84751A2BC"
"OwnerKey" = "8:_1EF590E9755E072BDC7697262C8261FD"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_0635D5A6F23DB1379A0FD91FF1B01507"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_0635D5A6F23DB1379A0FD91FF1B01507"
"MsmKey" = "8:_15129D0347D11843939442B84751A2BC"
"OwnerKey" = "8:_5EE4AF833E364CBAB977AFCB8CD5E99D"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_0635D5A6F23DB1379A0FD91FF1B01507"
"MsmKey" = "8:_15129D0347D11843939442B84751A2BC"
"OwnerKey" = "8:_C48ECEE77D85A17550522737B8522D2B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_15129D0347D11843939442B84751A2BC"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_19CCDCF64DCD42A423A21A106271862F"
"OwnerKey" = "8:_4A5BE4840FB638200664527C5EDB50B1"
"MsmSig" = "8:_UNDEFINED"
@ -70,6 +70,12 @@
"Entry"
{
"MsmKey" = "8:_441D4DFF8EBA539070D14AA52B77A767"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_441D4DFF8EBA539070D14AA52B77A767"
"OwnerKey" = "8:_5EE4AF833E364CBAB977AFCB8CD5E99D"
"MsmSig" = "8:_UNDEFINED"
}
@ -81,12 +87,6 @@
}
"Entry"
{
"MsmKey" = "8:_441D4DFF8EBA539070D14AA52B77A767"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_4A5BE4840FB638200664527C5EDB50B1"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
@ -135,60 +135,6 @@
}
"Entry"
{
"MsmKey" = "8:_7C7487CE661C4E1E5239FB4306E0749B"
"OwnerKey" = "8:_C48ECEE77D85A17550522737B8522D2B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_7C7487CE661C4E1E5239FB4306E0749B"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_7C7487CE661C4E1E5239FB4306E0749B"
"OwnerKey" = "8:_1EF590E9755E072BDC7697262C8261FD"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_7C7487CE661C4E1E5239FB4306E0749B"
"OwnerKey" = "8:_5EE4AF833E364CBAB977AFCB8CD5E99D"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_905A4AA564BAC6CC36A45AEE36EFE2FD"
"OwnerKey" = "8:_5EE4AF833E364CBAB977AFCB8CD5E99D"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_905A4AA564BAC6CC36A45AEE36EFE2FD"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_905A4AA564BAC6CC36A45AEE36EFE2FD"
"OwnerKey" = "8:_1EF590E9755E072BDC7697262C8261FD"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_905A4AA564BAC6CC36A45AEE36EFE2FD"
"OwnerKey" = "8:_7C7487CE661C4E1E5239FB4306E0749B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_905A4AA564BAC6CC36A45AEE36EFE2FD"
"OwnerKey" = "8:_C48ECEE77D85A17550522737B8522D2B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_9069E279B1FD50CD11B425DE3C9211AC"
"OwnerKey" = "8:_B92FE44943D4DEC27E1C3FA927231844"
"MsmSig" = "8:_UNDEFINED"
@ -201,26 +147,26 @@
}
"Entry"
{
"MsmKey" = "8:_A1F0AFE5FCA14283B33A21CAC42178E4"
"OwnerKey" = "8:_C0C5DE7473DD8B2395E58F2E339A1AF1"
"MsmKey" = "8:_A915B6D2A5B737C989D5A9820C90541B"
"OwnerKey" = "8:_15129D0347D11843939442B84751A2BC"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_A1F0AFE5FCA14283B33A21CAC42178E4"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_A1F0AFE5FCA14283B33A21CAC42178E4"
"MsmKey" = "8:_A915B6D2A5B737C989D5A9820C90541B"
"OwnerKey" = "8:_5EE4AF833E364CBAB977AFCB8CD5E99D"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_A1F0AFE5FCA14283B33A21CAC42178E4"
"OwnerKey" = "8:_905A4AA564BAC6CC36A45AEE36EFE2FD"
"MsmKey" = "8:_A915B6D2A5B737C989D5A9820C90541B"
"OwnerKey" = "8:_C48ECEE77D85A17550522737B8522D2B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_A915B6D2A5B737C989D5A9820C90541B"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
@ -267,6 +213,30 @@
}
"Entry"
{
"MsmKey" = "8:_CF08BC6F349ADA91DCE542635C8FFE0B"
"OwnerKey" = "8:_EAFD1746D4EDBAE590F6668ACEC315D5"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_CF08BC6F349ADA91DCE542635C8FFE0B"
"OwnerKey" = "8:_5EE4AF833E364CBAB977AFCB8CD5E99D"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_CF08BC6F349ADA91DCE542635C8FFE0B"
"OwnerKey" = "8:_C0C5DE7473DD8B2395E58F2E339A1AF1"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_CF08BC6F349ADA91DCE542635C8FFE0B"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_DE34EA7F69E9468ED2359C26A753AE38"
"OwnerKey" = "8:_C4ACAD76FA650ADD691588B6D2AA3BB2"
"MsmSig" = "8:_UNDEFINED"
@ -279,6 +249,36 @@
}
"Entry"
{
"MsmKey" = "8:_EAFD1746D4EDBAE590F6668ACEC315D5"
"OwnerKey" = "8:_1EF590E9755E072BDC7697262C8261FD"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_EAFD1746D4EDBAE590F6668ACEC315D5"
"OwnerKey" = "8:_C48ECEE77D85A17550522737B8522D2B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_EAFD1746D4EDBAE590F6668ACEC315D5"
"OwnerKey" = "8:_5EE4AF833E364CBAB977AFCB8CD5E99D"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_EAFD1746D4EDBAE590F6668ACEC315D5"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_EAFD1746D4EDBAE590F6668ACEC315D5"
"OwnerKey" = "8:_15129D0347D11843939442B84751A2BC"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_5EE4AF833E364CBAB977AFCB8CD5E99D"
"MsmSig" = "8:_UNDEFINED"
@ -286,6 +286,18 @@
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_C48ECEE77D85A17550522737B8522D2B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_C0C5DE7473DD8B2395E58F2E339A1AF1"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_2CE91AB829584CF99B8C35A6E138B5FF"
"MsmSig" = "8:_UNDEFINED"
}
@ -328,6 +340,36 @@
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_15129D0347D11843939442B84751A2BC"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_A915B6D2A5B737C989D5A9820C90541B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_EAFD1746D4EDBAE590F6668ACEC315D5"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_CF08BC6F349ADA91DCE542635C8FFE0B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_441D4DFF8EBA539070D14AA52B77A767"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_B92FE44943D4DEC27E1C3FA927231844"
"MsmSig" = "8:_UNDEFINED"
}
@ -355,48 +397,6 @@
"OwnerKey" = "8:_9069E279B1FD50CD11B425DE3C9211AC"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_C48ECEE77D85A17550522737B8522D2B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_7C7487CE661C4E1E5239FB4306E0749B"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_0635D5A6F23DB1379A0FD91FF1B01507"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_905A4AA564BAC6CC36A45AEE36EFE2FD"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_C0C5DE7473DD8B2395E58F2E339A1AF1"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_A1F0AFE5FCA14283B33A21CAC42178E4"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_441D4DFF8EBA539070D14AA52B77A767"
"MsmSig" = "8:_UNDEFINED"
}
}
"Configurations"
{
@ -454,6 +454,14 @@
"PrerequisitesLocation" = "2:1"
"Url" = "8:"
"ComponentsUrl" = "8:"
"Items"
{
"{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2"
{
"Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)"
"ProductCode" = "8:.NETFramework,Version=v4.7.2"
}
}
}
}
}
@ -512,20 +520,20 @@
}
"File"
{
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_0635D5A6F23DB1379A0FD91FF1B01507"
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_15129D0347D11843939442B84751A2BC"
{
"AssemblyRegister" = "3:1"
"AssemblyIsInGAC" = "11:FALSE"
"AssemblyAsmDisplayName" = "8:Microsoft.AnalysisServices.Tabular.Json, Version=19.16.3.4, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"
"AssemblyAsmDisplayName" = "8:Microsoft.AnalysisServices.Tabular, Version=19.16.3.4, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"
"ScatterAssemblies"
{
"_0635D5A6F23DB1379A0FD91FF1B01507"
"_15129D0347D11843939442B84751A2BC"
{
"Name" = "8:Microsoft.AnalysisServices.Tabular.Json.dll"
"Name" = "8:Microsoft.AnalysisServices.Tabular.dll"
"Attributes" = "3:512"
}
}
"SourcePath" = "8:Microsoft.AnalysisServices.Tabular.Json.dll"
"SourcePath" = "8:Microsoft.AnalysisServices.Tabular.dll"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_E31DB11D2BA64930BBAB95E69D1A099C"
@ -800,68 +808,6 @@
"IsDependency" = "11:TRUE"
"IsolateTo" = "8:"
}
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_7C7487CE661C4E1E5239FB4306E0749B"
{
"AssemblyRegister" = "3:1"
"AssemblyIsInGAC" = "11:FALSE"
"AssemblyAsmDisplayName" = "8:Microsoft.AnalysisServices.Tabular, Version=19.16.3.4, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"
"ScatterAssemblies"
{
"_7C7487CE661C4E1E5239FB4306E0749B"
{
"Name" = "8:Microsoft.AnalysisServices.Tabular.dll"
"Attributes" = "3:512"
}
}
"SourcePath" = "8:Microsoft.AnalysisServices.Tabular.dll"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_E31DB11D2BA64930BBAB95E69D1A099C"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Vital" = "11:TRUE"
"ReadOnly" = "11:FALSE"
"Hidden" = "11:FALSE"
"System" = "11:FALSE"
"Permanent" = "11:FALSE"
"SharedLegacy" = "11:FALSE"
"PackageAs" = "3:1"
"Register" = "3:1"
"Exclude" = "11:FALSE"
"IsDependency" = "11:TRUE"
"IsolateTo" = "8:"
}
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_905A4AA564BAC6CC36A45AEE36EFE2FD"
{
"AssemblyRegister" = "3:1"
"AssemblyIsInGAC" = "11:FALSE"
"AssemblyAsmDisplayName" = "8:Microsoft.AnalysisServices.Core, Version=19.16.3.4, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"
"ScatterAssemblies"
{
"_905A4AA564BAC6CC36A45AEE36EFE2FD"
{
"Name" = "8:Microsoft.AnalysisServices.Core.dll"
"Attributes" = "3:512"
}
}
"SourcePath" = "8:Microsoft.AnalysisServices.Core.dll"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_E31DB11D2BA64930BBAB95E69D1A099C"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Vital" = "11:TRUE"
"ReadOnly" = "11:FALSE"
"Hidden" = "11:FALSE"
"System" = "11:FALSE"
"Permanent" = "11:FALSE"
"SharedLegacy" = "11:FALSE"
"PackageAs" = "3:1"
"Register" = "3:1"
"Exclude" = "11:FALSE"
"IsDependency" = "11:TRUE"
"IsolateTo" = "8:"
}
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_9069E279B1FD50CD11B425DE3C9211AC"
{
"AssemblyRegister" = "3:1"
@ -913,20 +859,20 @@
"IsDependency" = "11:FALSE"
"IsolateTo" = "8:"
}
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_A1F0AFE5FCA14283B33A21CAC42178E4"
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_A915B6D2A5B737C989D5A9820C90541B"
{
"AssemblyRegister" = "3:1"
"AssemblyIsInGAC" = "11:FALSE"
"AssemblyAsmDisplayName" = "8:Microsoft.AnalysisServices.SPClient.Interfaces, Version=19.16.3.4, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"
"AssemblyAsmDisplayName" = "8:Microsoft.AnalysisServices.Tabular.Json, Version=19.16.3.4, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"
"ScatterAssemblies"
{
"_A1F0AFE5FCA14283B33A21CAC42178E4"
"_A915B6D2A5B737C989D5A9820C90541B"
{
"Name" = "8:Microsoft.AnalysisServices.SPClient.Interfaces.dll"
"Name" = "8:Microsoft.AnalysisServices.Tabular.Json.dll"
"Attributes" = "3:512"
}
}
"SourcePath" = "8:Microsoft.AnalysisServices.SPClient.Interfaces.dll"
"SourcePath" = "8:Microsoft.AnalysisServices.Tabular.Json.dll"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_E31DB11D2BA64930BBAB95E69D1A099C"
@ -1099,6 +1045,37 @@
"IsDependency" = "11:TRUE"
"IsolateTo" = "8:"
}
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_CF08BC6F349ADA91DCE542635C8FFE0B"
{
"AssemblyRegister" = "3:1"
"AssemblyIsInGAC" = "11:FALSE"
"AssemblyAsmDisplayName" = "8:Microsoft.AnalysisServices.SPClient.Interfaces, Version=19.16.3.4, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"
"ScatterAssemblies"
{
"_CF08BC6F349ADA91DCE542635C8FFE0B"
{
"Name" = "8:Microsoft.AnalysisServices.SPClient.Interfaces.dll"
"Attributes" = "3:512"
}
}
"SourcePath" = "8:Microsoft.AnalysisServices.SPClient.Interfaces.dll"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_E31DB11D2BA64930BBAB95E69D1A099C"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Vital" = "11:TRUE"
"ReadOnly" = "11:FALSE"
"Hidden" = "11:FALSE"
"System" = "11:FALSE"
"Permanent" = "11:FALSE"
"SharedLegacy" = "11:FALSE"
"PackageAs" = "3:1"
"Register" = "3:1"
"Exclude" = "11:FALSE"
"IsDependency" = "11:TRUE"
"IsolateTo" = "8:"
}
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_DE34EA7F69E9468ED2359C26A753AE38"
{
"AssemblyRegister" = "3:1"
@ -1130,6 +1107,37 @@
"IsDependency" = "11:TRUE"
"IsolateTo" = "8:"
}
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_EAFD1746D4EDBAE590F6668ACEC315D5"
{
"AssemblyRegister" = "3:1"
"AssemblyIsInGAC" = "11:FALSE"
"AssemblyAsmDisplayName" = "8:Microsoft.AnalysisServices.Core, Version=19.16.3.4, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"
"ScatterAssemblies"
{
"_EAFD1746D4EDBAE590F6668ACEC315D5"
{
"Name" = "8:Microsoft.AnalysisServices.Core.dll"
"Attributes" = "3:512"
}
}
"SourcePath" = "8:Microsoft.AnalysisServices.Core.dll"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_E31DB11D2BA64930BBAB95E69D1A099C"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Vital" = "11:TRUE"
"ReadOnly" = "11:FALSE"
"Hidden" = "11:FALSE"
"System" = "11:FALSE"
"Permanent" = "11:FALSE"
"SharedLegacy" = "11:FALSE"
"PackageAs" = "3:1"
"Register" = "3:1"
"Exclude" = "11:FALSE"
"IsDependency" = "11:TRUE"
"IsolateTo" = "8:"
}
}
"FileType"
{
@ -1197,15 +1205,15 @@
{
"Name" = "8:Microsoft Visual Studio"
"ProductName" = "8:Metadata Translator"
"ProductCode" = "8:{B6113405-F39B-4074-ACFE-79AB32CC2CFA}"
"PackageCode" = "8:{FB893543-9B0F-4DD8-8470-D4FEF396FAE2}"
"ProductCode" = "8:{6E78ABEB-1417-473B-BA84-60B10C49BA4A}"
"PackageCode" = "8:{84CEE7B5-BB29-4386-B26C-360A47BB3ECF}"
"UpgradeCode" = "8:{67E5697C-787F-4F4D-A7B8-C0C2CB4392DD}"
"AspNetVersion" = "8:4.0.30319.0"
"RestartWWWService" = "11:FALSE"
"RemovePreviousVersions" = "11:FALSE"
"DetectNewerInstalledVersion" = "11:TRUE"
"InstallAllUsers" = "11:TRUE"
"ProductVersion" = "8:1.2.0"
"ProductVersion" = "8:1.3.0"
"Manufacturer" = "8:Analysis Services Samples"
"ARPHELPTELEPHONE" = "8:"
"ARPHELPLINK" = "8:"

Просмотреть файл

@ -17,6 +17,7 @@ using Adomd = Microsoft.AnalysisServices.AdomdClient;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Resources;
using System.Collections;
namespace Metadata_Translator
{
@ -504,40 +505,66 @@ namespace Metadata_Translator
/// <param name="replaceExistingTranslations"></param>
public void ImportFromCsv(string filePath, string lcid, bool replaceExistingTranslations)
{
try
string csvData = File.ReadAllText(filePath);
if (string.IsNullOrEmpty(csvData)) return;
List<CsvRow> parsedRows = new List<CsvRow>();
using (TextFieldParser parser = new TextFieldParser(new StringReader(csvData)))
{
string csvData = File.ReadAllText(filePath);
if (string.IsNullOrEmpty(csvData)) return;
parser.CommentTokens = new string[] { "#" };
parser.SetDelimiters(new string[] { "," });
parser.HasFieldsEnclosedInQuotes = true;
List<CsvRow> parsedRows = new List<CsvRow>();
using (TextFieldParser parser = new TextFieldParser(new StringReader(csvData)))
/// Skip the header row.
///
parser.ReadFields();
while (!parser.EndOfData)
{
parser.CommentTokens = new string[] { "#" };
parser.SetDelimiters(new string[] { "," });
parser.HasFieldsEnclosedInQuotes = true;
/// Skip the header row.
///
parser.ReadFields();
while (!parser.EndOfData)
var textFields = parser.ReadFields();
if (textFields != null && textFields.Count() == 3)
{
var textFields = parser.ReadFields();
if (textFields != null && textFields.Count() == 3)
parsedRows.Add(new CsvRow
{
parsedRows.Add(new CsvRow
{
Type = textFields[0],
Original = textFields[1],
Translation = textFields[2]
});
}
Type = textFields[0],
Original = textFields[1],
Translation = textFields[2]
});
}
}
ApplyTranslation(lcid, parsedRows, replaceExistingTranslations);
}
catch { }
ApplyTranslation(lcid, parsedRows, replaceExistingTranslations);
}
public void ImportFromResx(string filePath, string referencePath, string lcid, bool replaceExistingTranslations)
{
List<CsvRow> parsedRows = new List<CsvRow>();
using (ResXResourceReader defaultLocaleStrings = new ResXResourceReader(referencePath))
using (ResXResourceSet translatedStrings = new ResXResourceSet(filePath))
{
foreach (DictionaryEntry kvp in defaultLocaleStrings)
{
string key = kvp.Key.ToString();
string value = kvp.Value?.ToString();
string translation = translatedStrings.GetString(key);
if (!string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(translation))
{
parsedRows.Add(new CsvRow
{
Type = string.Empty,
Original = value,
Translation = translation
});
}
}
}
if (parsedRows.Count == 0)
throw new NoResxMatchesException(filePath, referencePath);
ApplyTranslation(lcid, parsedRows, replaceExistingTranslations);
}
/// <summary>
@ -548,6 +575,11 @@ namespace Metadata_Translator
/// <param name="replaceExistingTranslations"></param>
private void ApplyTranslation(string lcid, List<CsvRow> translatedRows, bool replaceExistingTranslations)
{
if (translatedRows == null || translatedRows.Count == 0)
return;
bool hasTypeInfo = !string.IsNullOrEmpty(translatedRows[0].Type);
var allDataRows = GetAllDataRows();
if(!MatchAllRows(allDataRows, lcid, translatedRows, replaceExistingTranslations))
{
@ -558,7 +590,8 @@ namespace Metadata_Translator
{
var metaContainer = (MetadataObjectContainer)row.GetObject(ContainerColumnHeader);
var original = row.GetValue(DefaultCulture);
var csvRow = translatedRows.Where(x => x.Type == metaContainer.TranslatedProperty.ToString() && x.Original.Equals(original)).FirstOrDefault();
var csvRow = (hasTypeInfo)? translatedRows.Where(x => x.Type == metaContainer.TranslatedProperty.ToString() && x.Original.Equals(original)).FirstOrDefault() :
translatedRows.Where(x => x.Original.Equals(original)).FirstOrDefault();
if(csvRow != null)
{
row.SetValue(lcid, csvRow.Translation, replaceExistingTranslations);

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 177 KiB

Просмотреть файл

@ -142,8 +142,90 @@ The following command imports German translations from a .csv file called de-DE.
>
> MTCmd.exe only imports one translation file at a time. To import multiple languages, run MTCmd.exe in a loop.
### Working with resx files
While the graphical user interface of Metadata Translator only works with csv files, the command-line tool supports an additional option to work with XML resource (.resx) files as well. Resx files rely on a well-defined XML schema, including a header followed by data in name/value pairs. These files are often used for software localization. Among other things, Visual Studio provides a convenient interface for creating and maintaining resx files. And with command-line support for resx files in Metadata Translator, you can reuse these software translations by applying them to your Power BI datasets.
#### Csv versus Resx
Conceptually, resx files are slightly more difficult to work with than csv files because software localization and dataset localization use different approaches. In software projects structured for localization, UI elements are typically assigned an identifier, which then maps to an actual value in a resx file. For each supported language, the software project includes a separate resx file (or set of resx files). In this sense, there is no default locale. The identifiers establish the relationship or string mapping, and all supported languages are equal.
In Power BI datasets, on the other hand, named objects don't have identifiers. They hold the strings of the default locale so that measures, relationships, and other metadata elements can use meaningful references and clients connecting without specifying a locale on the connection string get meaningful information, such as human-readable table and column names. Moreover, there is no reliable way to associate the metadata objects in a dataset with a persisted unique identifier. For this reason, Metadata Translator strongly favors csv files because csv permits a direct relationship between the strings of the default locale and the translated strings of an additional locale without the help of a relationship base on an identifier.
The following figure illustrates the differences between csv and resx files for a dataset with a default locale of *en-US* and an additional locale of *it-IT*. Exporting the strings into a csv file produces a single it-IT.csv file containing both the original (default locale) strings and the translated strings. On the other hand, exporting the strings into resx produces two files, one for each locale with a globally unique identifier establishing the relationship between them. If you lost the en-US.resx file, for example, you practically also lost the Italian translations because you no longer have a mapping to the strings of the default locale.
![csv versus resx](https://github.com/microsoft/Analysis-Services/blob/master/MetadataTranslator/Metadata%20Translator/Documentation/Images/csv%20versus%20resx.png)
#### Exporting into resx files
Exporting translations from a dataset into resx files is very similar to exporting into csv files. Just specify the ExportResx as the mode of operation. For example, the following command exports all translations into resx files from an AdventureWorks dataset hosted in Power BI into a folder called ExportedTranslations:
`MTCmd -cs "powerbi://api.powerbi.com/v1.0/myorg/AdventureWorksSource;initial catalog=AdventureWorks" -ef C:\ExportedTranslations -m ExportResx`
> Note
>
> The specified export folder must exist prior to running the command. MTCmd.exe does not create the export folder. However, if any files exist in the export folder, MTCmd.exe might overwrite them without warning. MTCmd.exe exports each locale into a separate .csv file based on the locale identifier (LCID), including the default locale.
#### Repeatedly exporting into resx files
It is important to note that Metadata Translator generates new globally unique identifiers (GUIDs) every time you export translations from a dataset. There is no reliable way for Metadata Translator to persist previously generated GUIDs with their corresponding metadata objects in the model. It is therefore not recommended to export translations into an existing set of resx files.
For illustration, imagine the following scenario:
1. You export the translations from a dataset that supports the locales en-US, it-IT, and es-ES. The default locale is en-US.
2. Metadata Translator creates to following files in the export folder: en-US.resx, it-IT.resx, and es-ES.resx. All three files use the same GUIDs to establish the string relationships. In other words, these three files from a translation set of resx files.
3. You export the Italian translations again by using the -lcid parameter (-lcid it-IT). You use the same export folder. Metadata Translator overwrites the en-US.resx and it-IT.resx files.
4. The existing es-ES.resx file is now orphaned because the GUIDs in the new en-US.resx file, representing the default locale of this dataset, no longer match.
5. To bring this translation set of resx files back into a consistent state, you must export the full set of locales again.
> Note
>
> To avoid orphaned resx files, it is a good idea to always export all locales together. If you must export individual languages, make sure you export them into a separate (empty) folder to avoid possibly overwriting an existing resx file of the default locale.
#### Importing translations from resx files
Importing translations from resx files is practically identical to importing from csv files. Simply specify the resx import file by using the --import-file (-if) parameter. Metadata Translator detects the import mode based in the .resx file name extension. For more details, refer to the section "Importing translations" earlier in this document.
The following command imports German translations from a resx file called de-DE.resx into an AdventureWorks dataset hosted in Power BI, overwriting any existing German strings in the dataset:
`MTCmd -cs "powerbi://api.powerbi.com/v1.0/myorg/AdventureWorksSource;initial catalog=AdventureWorks" -if C:\Translations\de-DE.resx -m Overwrite`
> Note
>
> For a resx import operation to succeed, Metadata Translator requires the resx file of the dataset's default locale to be located in the same folder as the translation resx file. For example, if the above command attempts to import German translations into a dataset with the default locale of en-US, you must have a matching en-US.resx file in the same folder as the de-DE.resx file.
#### Importing translations from external resx files
There is no real difference between resx files generated by Metadata Translator vs. other tools. The GUIDs that Metadata Translator generates merely provide a convenient way to establish unique relationships between strings, but the Name values don't have to be GUIDs necessarily. Metadata Translator can use any kind of name to find a matching translation as long as the name is unique. The resx XML schema enforces uniqueness for the Name property.
Metadata Translator performs the following steps during a resx import operation:
1. Iterate over all the name/value pairs from the resx file of the default locale.
2. Use the name to lookup the corresponding value string in the translation resx file.
3. Use the value strings from the default and translated locales to construct a set of string pairs dynamically.
4. Use the same logic as if the string pairs were from a csv file to apply the translations to the dataset.
## CSV Format for translation files
Metadata Translator translation files use a straightforward schema with only three columns: Type,Original,Translation. Do not delete or add columns or change the column ordering because Metadata Translator will not process translation files that don't follow this schema. Refer to the following table for more details.
| Column | Description |
| ----------- | ------------------------------------------------------------ |
| Type | An optional value to specify the metadata property type to which the translation should apply when Metadata Translator switches to case-sensitive string matching. <br />Supported values are: <br /> Caption<br /> Description<br /> DisplayFolder<br />If you specify an invalid property type, the translation might be ignored. Leave this column blank if you want to apply a translation to all property types across the board. |
| Original | A string in the default locale of the dataset. Metadata Translator compares the caption, description, or display folder name of a metadata object with this string using case-sensitive string matching to determine if the translation applies to this object. Make sure the captions, descriptions, and display folder names of your metadata objects match the strings in the Original column exactly. |
| Translation | A translated string in the locale of the csv file, as identified by the csv file name, such as de-DE.csv. |
> Note
>
> Ideally, strings in the Original column of a translation file fully match of the captions, descriptions, and display folder names of the metadata objects, including their ordering. However, you can also maintain a global set of translation files that apply to multiple different datasets. In this case, Metadata Translators uses case-sensitive string matching to determine which translation to apply. If you compile translation files by using a separate tool and don't know the property type, leave the Type column empty, but make sure the column exists.
## Additional features
Metadata Translator v1.2 does not support editing the default language strings because the external tools integration feature of Power BI Desktop does not support these operations yet.
Metadata Translator v1.3 does not support editing the default language strings because the external tools integration feature of Power BI Desktop does not support these operations yet.
Metadata Translator v1.3 does not yet support translating synonyms for Power BI Q&A, mainly because Power BI Q&A currently only supports answering natural language queries asked in English. Although there is a preview available for Spanish, the current support policy is English only (see [Supported languages and countries/regions for Power BI - Power BI | Microsoft Docs](https://docs.microsoft.com/power-bi/fundamentals/supported-languages-countries-regions#whats-translated)).
For additional feature requests, create a new item under [Issues](https://github.com/microsoft/Analysis-Services/issues).

Просмотреть файл

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public class NoResxMatchesException : Exception
{
public string ReferenceResx { get; set; }
public string TranslationResx { get; set; }
public NoResxMatchesException(string referenceResx, string translationResx)
{
ReferenceResx = referenceResx;
TranslationResx = translationResx;
}
}
}

Просмотреть файл

@ -104,6 +104,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Helpers\NoResxMatchesException.cs" />
<Compile Include="UI\ConnectionStringInput.xaml.cs">
<DependentUpon>ConnectionStringInput.xaml</DependentUpon>
</Compile>

Просмотреть файл

@ -51,5 +51,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyVersion("1.3.0.0")]
[assembly: AssemblyFileVersion("1.3.0.0")]

Просмотреть файл

@ -48,14 +48,19 @@ namespace Metadata_Translator
if (openFileDialog1.ShowDialog() == true)
{
using (new Hourglass())
using (new Hourglass())
{
foreach (string filePath in openFileDialog1.FileNames)
{
string lcid = Path.GetFileNameWithoutExtension(filePath);
mainWnd.AddColumn(lcid);
mainWnd.DataModel?.ImportFromCsv(filePath, lcid, mainWnd.OverwriteTranslation);
try
{
mainWnd.DataModel?.ImportFromCsv(filePath, lcid, mainWnd.OverwriteTranslation);
}
catch { }
}
}
}