Adding the Metadata Translation project.

This commit is contained in:
Kay Unkroth 2021-02-24 17:34:03 -08:00
parent 0169a7a4c0
commit 8ec484fc17
43 changed files with 5638 additions and 0 deletions

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metadata Translator", "Metadata Translator\Metadata Translator.csproj", "{CB7D493C-B67E-4438-B304-EFE5D418ADDF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CB7D493C-B67E-4438-B304-EFE5D418ADDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB7D493C-B67E-4438-B304-EFE5D418ADDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB7D493C-B67E-4438-B304-EFE5D418ADDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB7D493C-B67E-4438-B304-EFE5D418ADDF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0733CEFA-6458-4D8F-B498-FC9827973880}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<appSettings>
<add key="SubscriptionKey" value="" />
<add key="TranslatorEndpoint" value="https://api.cognitive.microsofttranslator.com/" />
<add key="TranslatorLocation" value="" />
<add key="OverwriteTranslation" value="false" />
<add key="LastUsedExportFolder" value="" />
</appSettings>
</configuration>

View File

@ -0,0 +1,13 @@
<Application x:Class="Metadata_Translator.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Metadata_Translator"
Startup="Application_Startup">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources\StringDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace Metadata_Translator
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
MainWindow mainWindow = new MainWindow(e);
mainWindow.Show();
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace Metadata_Translator
{
public class CollectionEmptyTrueFalseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value is ObservableCollection<ExpandoObject> collection) ? collection.Count > 0 : false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace Metadata_Translator
{
public class PercentageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Double size)
{
return size * 0.5;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace Metadata_Translator
{
public class TranslationPropertyToolTipConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(parameter is string toolTipString)
{
string[] toolTip = toolTipString.Split('|');
if (value is ObservableCollection<ExpandoObject> collection && toolTip?.Length == 2)
{
return collection.Count > 0 ? toolTip[0] : toolTip[1];
}
else
return toolTipString;
}
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace Metadata_Translator
{
class TrueFalseVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (System.Convert.ToBoolean(value))? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
class FalseTrueVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (System.Convert.ToBoolean(value)) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public class CsvRow
{
public string Type { get; set; }
public string Original { get; set; }
public string Translation { get; set; }
}
}

View File

@ -0,0 +1,420 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AS = Microsoft.AnalysisServices;
using Microsoft.AnalysisServices.Tabular;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Dynamic;
using System.Windows.Controls;
using System.Windows.Data;
using System.IO;
using System.Web.Script.Serialization;
using Microsoft.VisualBasic.FileIO;
namespace Metadata_Translator
{
public class DataModel
{
Model Model { get; set; }
public string ContainerColumnHeader { get => "Object"; }
public ObservableCollection<ExpandoObject> Captions { get; private set; }
public ObservableCollection<ExpandoObject> Descriptions { get; private set; }
public ObservableCollection<ExpandoObject> DisplayFolders { get; private set; }
public string DefaultCulture { get; set; }
public List<string> CultureNames
{
get
{
List<string> cultures = new List<string> { Model.Culture };
cultures.AddRange(Model?.Cultures.Where(i => !i.Name.Equals(Model.Culture)).Select(x => x.Name).ToList());
return cultures;
}
}
public List<Language> SupportedLanguages { get; private set; }
public List<Language> SelectedLanguages { get => SupportedLanguages?.Where(x => x.IsSelected==true).ToList(); }
public bool HasTargetLanguages { get => SelectedLanguages?.Count > 1; }
public DataModel(string server, string database)
{
LoadLanguages();
Server pbiDesktop = new Server();
pbiDesktop.Connect($"Data Source={server}");
Database dataset = pbiDesktop.Databases.GetByName(database);
Model = dataset.Model;
DefaultCulture = Model.Culture;
LoadNamedObjectCollections();
}
/// <summary>
/// A static helper to get the DataModel object.
/// </summary>
/// <param name="server"></param>
/// <param name="database"></param>
/// <returns></returns>
public static DataModel Connect(string server, string database)
{
return new DataModel(server, database);
}
/// <summary>
/// Gets the tables from the dataset and within it all the columns, measures, and hierarchies
/// and adds these tabular objects to the collections for captions, descriptions, and display folders.
/// </summary>
private void LoadNamedObjectCollections()
{
Captions = new ObservableCollection<ExpandoObject>();
Descriptions = new ObservableCollection<ExpandoObject>();
DisplayFolders = new ObservableCollection<ExpandoObject>();
CultureCollection cultures = Model.Cultures;
Captions.Add(CreateRow(new MetadataObjectContainer(Model, TranslatedProperty.Caption), Model.Name, DefaultCulture, cultures));
if (!string.IsNullOrEmpty(Model.Description))
Descriptions.Add(CreateRow(new MetadataObjectContainer(Model, TranslatedProperty.Description), Model.Description, DefaultCulture, cultures));
foreach (Table table in Model.Tables)
{
Captions.Add(CreateRow(new MetadataObjectContainer(table, TranslatedProperty.Caption), table.Name, DefaultCulture, cultures));
if (!string.IsNullOrEmpty(table.Description))
Descriptions.Add(CreateRow(new MetadataObjectContainer(table, TranslatedProperty.Description), table.Description, DefaultCulture, cultures));
foreach (Column column in table.Columns)
{
if (column.Type != ColumnType.RowNumber)
{
Captions.Add(CreateRow(new MetadataObjectContainer(column, TranslatedProperty.Caption), column.Name, DefaultCulture, cultures));
if (!string.IsNullOrEmpty(column.Description))
Descriptions.Add(CreateRow(new MetadataObjectContainer(column, TranslatedProperty.Description), column.Description, DefaultCulture, cultures));
if (!string.IsNullOrEmpty(column.DisplayFolder))
DisplayFolders.AddDisplayFolder(column, column.DisplayFolder, DefaultCulture, cultures);
}
}
foreach (Measure measure in table.Measures)
{
Captions.Add(CreateRow(new MetadataObjectContainer(measure, TranslatedProperty.Caption), measure.Name, DefaultCulture, cultures));
if (!string.IsNullOrEmpty(measure.Description))
Descriptions.Add(CreateRow(new MetadataObjectContainer(measure, TranslatedProperty.Description), measure.Description, DefaultCulture, cultures));
if (!string.IsNullOrEmpty(measure.DisplayFolder))
DisplayFolders.AddDisplayFolder(measure, measure.DisplayFolder, DefaultCulture, cultures);
}
foreach (Hierarchy hierarchy in table.Hierarchies)
{
Captions.Add(CreateRow(new MetadataObjectContainer(hierarchy, TranslatedProperty.Caption), hierarchy.Name, DefaultCulture, cultures));
if (!string.IsNullOrEmpty(hierarchy.Description))
Descriptions.Add(CreateRow(new MetadataObjectContainer(hierarchy, TranslatedProperty.Description), hierarchy.Description, DefaultCulture, cultures));
if (!string.IsNullOrEmpty(hierarchy.DisplayFolder))
DisplayFolders.AddDisplayFolder(hierarchy, hierarchy.DisplayFolder, DefaultCulture, cultures);
}
}
}
/// <summary>
/// Loads the list of supported languages from the supportedlanguages.json file.
/// </summary>
private void LoadLanguages()
{
SupportedLanguages = new List<Language>();
string content = File.ReadAllText($"{System.AppDomain.CurrentDomain.BaseDirectory}Resources\\supportedlanguages.json");
foreach (Language lang in new JavaScriptSerializer().Deserialize<List<Language>>(content))
{
SupportedLanguages.Add(lang);
}
}
/// <summary>
/// Creates a new ExpandoObject for a source string (displayString).
/// </summary>
/// <param name="objectContainer"></param>
/// <param name="displayString"></param>
/// <param name="defaultCulture"></param>
/// <param name="cultures"></param>
/// <returns>An ExpandoObject representing a data row.</returns>
public ExpandoObject CreateRow(MetadataObjectContainer objectContainer, string displayString, string defaultCulture, CultureCollection cultures)
{
dynamic row = new ExpandoObject();
((IDictionary<String, Object>)row)[ContainerColumnHeader] = objectContainer;
foreach (var culture in cultures)
{
((IDictionary<String, Object>)row)[culture.Name] = culture.Name.Equals(defaultCulture) ? displayString :
culture.ObjectTranslations[objectContainer.TabularObject, objectContainer.TranslatedProperty]?.Value;
}
return row;
}
/// <summary>
/// Combine all collections for translation and updating.
/// </summary>
/// <returns></returns>
public List<ExpandoObject> GetAllDataRows()
{
var allRows = new List<ExpandoObject>();
foreach (var item in Captions) allRows.Add(item);
foreach (var item in Descriptions) allRows.Add(item);
foreach (var item in DisplayFolders) allRows.Add(item);
return allRows;
}
/// <summary>
/// Adds a translation to a Tabular metadata object.
/// </summary>
/// <param name="culture"></param>
/// <param name="metadataObjectContainer"></param>
/// <param name="translation"></param>
private void SetTranslation(Culture culture, MetadataObjectContainer metadataObjectContainer, string translation)
{
culture.ObjectTranslations.SetTranslation(
metadataObjectContainer.TabularObject, metadataObjectContainer.TranslatedProperty,
translation);
}
/// <summary>
/// Updates the Power BI dataset with the translations from the ExpandoObject collections and saves the changes.
/// </summary>
public void Update()
{
/// Delete any deselected cultures that still exist in the dataset.
///
List<string> cultureNames = SelectedLanguages?.Select(sl => sl.LanguageTag)?.ToList();
/// There must be at least the default culture in the cultureNames.
///
if (cultureNames == null || cultureNames.Count < 1) return;
var culturesToRemove = CultureNames.Where(cn1 => !cultureNames.Any(cn2 => cn2.Equals(cn1))).ToList();
culturesToRemove.Remove(DefaultCulture);
foreach(string cultureName in culturesToRemove)
{
if (Model.Cultures.Contains(cultureName))
{
Model.Cultures.Remove(cultureName);
}
}
/// Add any newly selected cultures.
///
foreach (string cultureName in cultureNames)
{
if (!Model.Cultures.Contains(cultureName))
{
Model.Cultures.Add(new Culture { Name = cultureName });
}
}
/// Add the translations to all the metadata objects.
///
foreach (ExpandoObject row in GetAllDataRows())
{
if (((IDictionary<string, Object>)row)[ContainerColumnHeader] is MetadataObjectContainer metadataObjectContainer)
{
/*
* Include this part when updating the default culture (i.e. updating the actual metadata objects) is supported.
*
switch (metadataObjectContainer.TranslatedProperty)
{
case TranslatedProperty.Caption:
metadataObjectContainer.TabularObject.Name = row.GetValue(DefaultCulture);
break;
case TranslatedProperty.Description:
if (metadataObjectContainer.TabularObject is Table table)
{
table.Description = row.GetValue(DefaultCulture);
}
else if (metadataObjectContainer.TabularObject is Column col)
{
col.Description = row.GetValue(DefaultCulture);
}
else if (metadataObjectContainer.TabularObject is Measure measure)
{
measure.Description = row.GetValue(DefaultCulture);
}
else if (metadataObjectContainer.TabularObject is Hierarchy hierarchy)
{
hierarchy.Description = row.GetValue(DefaultCulture);
}
break;
case TranslatedProperty.DisplayFolder:
if (metadataObjectContainer.TabularObject is Column column)
{
column.DisplayFolder = row.GetValue(DefaultCulture);
}
else if (metadataObjectContainer.TabularObject is Measure measure)
{
measure.DisplayFolder = row.GetValue(DefaultCulture);
}
else if (metadataObjectContainer.TabularObject is Hierarchy hierarchy)
{
hierarchy.DisplayFolder = row.GetValue(DefaultCulture);
}
break;
}
*/
foreach (string cultureName in cultureNames)
{
SetTranslation(Model.Cultures[cultureName],
metadataObjectContainer,
row.GetValue(cultureName));
}
}
}
/// Save the changes in the dataset.
///
Model.Database.Update(AS.UpdateOptions.ExpandFull);
}
/// <summary>
/// Exports the translations to individual language files.
/// The files are placed into the specified export folder.
/// </summary>
/// <param name="exportFolderPath"></param>
public void ExportToCsv(string exportFolderPath)
{
string separator = ",";
List<ExpandoObject> dataRows = GetAllDataRows();
if (dataRows != null && dataRows.Count > 0)
{
List<string> languages = SelectedLanguages.Where(l => l.IsModelDefault != true).Select(l => l.LanguageTag).ToList();
if (languages != null && languages.Count > 0)
{
foreach (string lcid in languages)
{
StringBuilder csvContent = new StringBuilder();
csvContent.AppendLine("Type,Original,Translation");
foreach (var stringValues in dataRows.GetValues(ContainerColumnHeader, DefaultCulture, lcid))
{
csvContent.AppendLine(
string.Join(
separator,
new string[] {
stringValues.Type.ToCsvString(),
stringValues.Original.ToCsvString(),
stringValues.Translation.ToCsvString()
})
);
}
using (var sw = File.Create(System.IO.Path.Combine(exportFolderPath, $"{lcid}.csv")))
{
var preamble = Encoding.UTF8.GetPreamble();
sw.Write(preamble, 0, preamble.Length);
var data = Encoding.UTF8.GetBytes(csvContent.ToString());
sw.Write(data, 0, data.Length);
}
}
}
}
}
/// <summary>
/// Imports translations from a csv file. The file name must match the LCID of the target language.
/// </summary>
/// <param name="filePath"></param>
/// <param name="lcid"></param>
/// <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)))
{
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)
{
parsedRows.Add(new CsvRow
{
Type = textFields[0],
Original = textFields[1],
Translation = textFields[2]
});
}
}
}
ApplyTranslation(lcid, parsedRows, replaceExistingTranslations);
}
catch { }
}
/// <summary>
/// Applies a list of translations to the ExpandoObject collections
/// </summary>
/// <param name="lcid"></param>
/// <param name="translatedRows"></param>
/// <param name="replaceExistingTranslations"></param>
private void ApplyTranslation(string lcid, List<CsvRow> translatedRows, bool replaceExistingTranslations)
{
var allDataRows = GetAllDataRows();
if(!MatchAllRows(allDataRows, lcid, translatedRows, replaceExistingTranslations))
{
/// Not all rows matched, so let's do this the slow way
/// matching strings.
///
foreach(ExpandoObject row in allDataRows)
{
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();
if(csvRow != null)
{
row.SetValue(lcid, csvRow.Translation, replaceExistingTranslations);
}
}
}
}
/// <summary>
/// Iterates over the dataRows and applies the translated strings with the assumption that
/// translatedRows matches the dataRows in number and order.
/// </summary>
private bool MatchAllRows(List<ExpandoObject> dataRows, string lcid, List<CsvRow> translatedRows, bool replaceExistingTranslations)
{
if(dataRows == null || dataRows.Count != translatedRows?.Count)
return false;
for(int i = 0; i < translatedRows.Count; i++)
{
ExpandoObject row = dataRows[i];
CsvRow csvRow = translatedRows[i];
if (row.GetValue(DefaultCulture) != csvRow.Original)
return false;
row.SetValue(lcid, csvRow.Translation, replaceExistingTranslations);
}
return true;
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.AnalysisServices.Tabular;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public class DisplayFolderContainer : MetadataObjectContainer
{
public override NamedMetadataObject TabularObject { get => TabularObjects.FirstOrDefault(); protected set { } }
public List<NamedMetadataObject> TabularObjects { get; private set; }
public DisplayFolderContainer(NamedMetadataObject metadataObject, TranslatedProperty translatedProperty) : base(metadataObject, translatedProperty)
{
TabularObjects = new List<NamedMetadataObject>();
TabularObjects.Add(metadataObject);
}
public override string ToString()
{
return (TabularObjects.Count > 1)?
$"DisplayFolder - {TabularObjects.Count} Objects" :
$"DisplayFolder - 1 {TabularObject.ObjectType}";
}
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.AnalysisServices.Tabular;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public class MetadataObjectContainer
{
public virtual NamedMetadataObject TabularObject { get; protected set; }
public TranslatedProperty TranslatedProperty { get; protected set; }
public MetadataObjectContainer(NamedMetadataObject metadataObject, TranslatedProperty translatedProperty)
{
TabularObject = metadataObject;
TranslatedProperty = translatedProperty;
}
public override string ToString()
{
switch(TranslatedProperty)
{
case TranslatedProperty.Caption:
return $"{TabularObject.ObjectType} - Caption";
case TranslatedProperty.Description:
return $"{TabularObject.ObjectType} - Description";
case TranslatedProperty.DisplayFolder:
return $"{TabularObject.ObjectType} - DisplayFolder";
default:
return TabularObject.ObjectType.ToString();
}
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public static class ExpandoObjectExtensions
{
public static string SeparateCamelCase(this ExpandoObject expando, string columnName)
{
if (expando != null && ((IDictionary<String, Object>)expando)[columnName] is string text)
{
char separator = ' ';
char lastChar = separator;
var sb = new StringBuilder();
foreach (var currentChar in text.Replace("_", ""))
{
if (char.IsUpper(currentChar) && lastChar != separator)
sb.Append(separator);
sb.Append(currentChar);
lastChar = currentChar;
}
return sb.ToString();
}
return string.Empty;
}
public static string GetValue(this ExpandoObject expando, string columnName)
{
return expando.GetObject(columnName)?.ToString();
}
public static object GetObject(this ExpandoObject expando, string columnName)
{
if (expando != null && ((IDictionary<String, Object>)expando).ContainsKey(columnName))
{
return ((IDictionary<String, Object>)expando)[columnName];
}
return null;
}
public static void SetValue(this ExpandoObject expando, string columnName, string value, bool overwrite)
{
if (expando != null)
{
if (overwrite || string.IsNullOrEmpty(expando.GetValue(columnName)))
{
((IDictionary<String, Object>)expando)[columnName] = value;
}
}
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Metadata_Translator
{
public class Hourglass : IDisposable
{
private Cursor previousCursor;
public Hourglass()
{
previousCursor = Mouse.OverrideCursor;
Mouse.OverrideCursor = Cursors.Wait;
}
public void Dispose()
{
Mouse.OverrideCursor = previousCursor;
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public static class ListExtensions
{
public static List<CsvRow> GetValues(this List<ExpandoObject> collection, string containerColumnName, string referenceColumnName, string columnName)
{
if (collection == null) return new List<CsvRow>();
var values = new List<CsvRow>();
foreach (ExpandoObject row in collection)
{
var metaContainer = (MetadataObjectContainer)row.GetObject(containerColumnName);
string refValue = row.GetValue(referenceColumnName);
if (!string.IsNullOrEmpty(refValue))
values.Add(new CsvRow { Type = metaContainer.TranslatedProperty.ToString(), Original = refValue, Translation = row.GetValue(columnName) });
}
return values;
}
}
}

View File

@ -0,0 +1,95 @@
using Microsoft.AnalysisServices.Tabular;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public static class ObservableCollectionExtensions
{
public static string GetValueAt(this ObservableCollection<ExpandoObject> collection, int index, string columnName)
{
if (collection == null) return string.Empty;
ExpandoObject row = collection[index];
return ((IDictionary<String, Object>)row)[columnName]?.ToString();
}
public static void SetValueAt(this ObservableCollection<ExpandoObject> collection, int index, string columnName, string value)
{
if (collection == null) return;
ExpandoObject row = collection[index];
((IDictionary<String, Object>)row)[columnName] = value;
}
public static void UpdateDataValues(this ObservableCollection<ExpandoObject> collection, List<string> sourcePhrases, string sourceLanguage, List<string> targetPhrases, string targetLanguage)
{
if (collection == null) return;
int rowOffset = 0;
for (int i = 0; i < collection.Count; i++)
{
string columnValue = collection.GetValueAt(i, sourceLanguage);
if (!string.IsNullOrEmpty(columnValue) && columnValue.Equals(sourcePhrases[i - rowOffset]))
{
collection.SetValueAt(i, targetLanguage, targetPhrases[i - rowOffset]);
}
else
{
rowOffset++;
}
}
}
public static List<string> GetValues(this ObservableCollection<ExpandoObject> collection, string columnName)
{
if (collection == null) return new List<string>();
List<string> values = new List<string>();
foreach (ExpandoObject row in collection)
{
string value = ((IDictionary<String, Object>)row)[columnName]?.ToString();
if (!string.IsNullOrEmpty(value))
values.Add(value);
}
return values;
}
public static void AddDisplayFolder(this ObservableCollection<ExpandoObject> collection, NamedMetadataObject metadataObject, string displayString, string defaultCulture, CultureCollection cultures)
{
if (collection == null) return;
if (!string.IsNullOrEmpty(displayString))
{
foreach (ExpandoObject item in collection)
{
if (((IDictionary<String, Object>)item)[defaultCulture] is string displayName && displayName.Equals(displayString))
{
var existingDisplayFolderContainer = ((IDictionary<String, Object>)item)["Object"] as DisplayFolderContainer;
existingDisplayFolderContainer.TabularObjects.Add(metadataObject);
return;
}
}
}
dynamic row = new ExpandoObject();
var displayFolderContainer = new DisplayFolderContainer(metadataObject, TranslatedProperty.DisplayFolder);
((IDictionary<String, Object>)row)["Object"] = displayFolderContainer;
foreach (var culture in cultures)
{
((IDictionary<String, Object>)row)[culture.Name] = culture.Name.Equals(defaultCulture) ? displayString :
culture.ObjectTranslations[displayFolderContainer.TabularObject, displayFolderContainer.TranslatedProperty]?.Value;
}
collection.Add(row);
}
}
}

View File

@ -0,0 +1,21 @@
using Microsoft.AnalysisServices.Tabular;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public static class StringExtensions
{
public static string ToCsvString(this string value)
{
if (value == null) return string.Empty;
return value.Contains("\"") ? $"\"{value.Replace("\"", "\"\"")}\"" : value;
}
}
}

View File

@ -0,0 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{CB7D493C-B67E-4438-B304-EFE5D418ADDF}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>Metadata_Translator</RootNamespace>
<AssemblyName>Metadata Translator</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AnalysisServices, Version=19.16.3.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AnalysisServices.retail.amd64.19.16.3\lib\net45\Microsoft.AnalysisServices.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AnalysisServices.AdomdClient, Version=19.16.3.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AnalysisServices.AdomdClient.retail.amd64.19.16.3\lib\net45\Microsoft.AnalysisServices.AdomdClient.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AnalysisServices.Core, Version=19.16.3.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AnalysisServices.retail.amd64.19.16.3\lib\net45\Microsoft.AnalysisServices.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AnalysisServices.SPClient.Interfaces, Version=19.16.3.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AnalysisServices.retail.amd64.19.16.3\lib\net45\Microsoft.AnalysisServices.SPClient.Interfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AnalysisServices.Tabular, Version=19.16.3.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AnalysisServices.retail.amd64.19.16.3\lib\net45\Microsoft.AnalysisServices.Tabular.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AnalysisServices.Tabular.Json, Version=19.16.3.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AnalysisServices.retail.amd64.19.16.3\lib\net45\Microsoft.AnalysisServices.Tabular.Json.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Converters\CollectionEmptyTrueFalseConverter.cs" />
<Compile Include="Converters\TranslationPropertyToolTipConverter.cs" />
<Compile Include="Data\CsvRow.cs" />
<Compile Include="Data\DisplayFolderContainer.cs" />
<Compile Include="Helpers\ExpandoObjectExtensions.cs" />
<Compile Include="Helpers\StringExtensions.cs" />
<Compile Include="Helpers\Hourglass.cs" />
<Compile Include="Helpers\ListExtensions.cs" />
<Compile Include="Helpers\ObservableCollectionExtensions.cs" />
<Compile Include="UI\ImportExportPanel.xaml.cs">
<DependentUpon>ImportExportPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Translations\Language.cs" />
<Compile Include="UI\LanguagePanel.xaml.cs">
<DependentUpon>LanguagePanel.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\PercentageConverter.cs" />
<Compile Include="UI\SettingsPanel.xaml.cs">
<DependentUpon>SettingsPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Translations\TranslationGroup.cs" />
<Compile Include="UI\TranslationGroupPanel.xaml.cs">
<DependentUpon>TranslationGroupPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Translations\TranslatorService.cs" />
<Compile Include="Converters\TrueFalseVisibilityConverter.cs" />
<Compile Include="Translations\Translation.cs" />
<Compile Include="Translations\TranslationResult.cs" />
<Page Include="UI\ImportExportPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UI\LanguagePanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UI\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Data\DataModel.cs" />
<Compile Include="UI\MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="UI\SettingsPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Resources\StringDictionary.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UI\TranslationGroupPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Data\MetadataObjectContainer.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="metadata-translator.pbitool.json" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<None Include="Resources\supportedlanguages.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Resource Include="icon.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Metadata Translator")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Metadata Translator")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,70 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Metadata_Translator.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Metadata_Translator.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,29 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Metadata_Translator.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -0,0 +1,43 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Metadata_Translator"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="Languages">Languages</system:String>
<system:String x:Key="LanguagesToolTip">Opens the Languages pane to choose the target languages.</system:String>
<system:String x:Key="Settings">Settings</system:String>
<system:String x:Key="SettingsToolTip">Opens the Settings pane to configure Machine Translator.</system:String>
<system:String x:Key="TranslatedProperty">Translated Property</system:String>
<system:String x:Key="Caption">Caption</system:String>
<system:String x:Key="CaptionToolTip">Displays the captions of the metadata objects and their translations.</system:String>
<system:String x:Key="Description">Description</system:String>
<system:String x:Key="DescriptionToolTip">Displays the descriptions of the metadata objects and their translations.|The metadata objects in the current dataset have no descriptions. Use Power BI Desktop to add descriptions.</system:String>
<system:String x:Key="DisplayFolder">DisplayFolder</system:String>
<system:String x:Key="DisplayFolderToolTip">Displays the display folder names of the metadata objects and their translations.|The current dataset does not have display folders. Use Power BI Desktop to group columns and measures in display folders.</system:String>
<system:String x:Key="Prepare">Prepare</system:String>
<system:String x:Key="Translate">Translate</system:String>
<system:String x:Key="TranslateToolTip">Translates the captions, descriptions, and display folder names using Microsoft Translator.</system:String>
<system:String x:Key="Apply">Apply</system:String>
<system:String x:Key="ApplyToolTip">Applies the translated captions, descriptions, and display folder names to the Power BI dataset.</system:String>
<system:String x:Key="ImportExport">Import/Export</system:String>
<system:String x:Key="ImportExportToolTip">Opens the Import/Export pane to export and import the captions, descriptions, and display folder names via .csv files.</system:String>
<system:String x:Key="InvalidArguments">Metadata Translator cannot start without server and database arguments. Double-check the 'arguments' setting in the metadata-translator.pbitool.json file in your Microsoft Shared\Power BI Desktop\External Tools folder. The value must be: "\"%server%\" \"%database%\"".</system:String>
<system:String x:Key="DatasetConnection">Dataset Connection String</system:String>
<system:String x:Key="SubscriptionKey">Translator Subscription Key</system:String>
<system:String x:Key="TranslatorEndpoint">Translator Endpoint</system:String>
<system:String x:Key="TranslatorLocation">Translator Location</system:String>
<system:String x:Key="OverwriteTranslation">Overwrite Translation</system:String>
<system:String x:Key="SubscriptionKeyPrompt">Enter your Microsoft Translator service subscription key.</system:String>
<system:String x:Key="TranslatorEndpointPrompt">Enter the Microsoft Translator URL, specifically: https://api.cognitive.microsofttranslator.com/</system:String>
<system:String x:Key="TranslatorLocationPrompt">This is typically the Global region, but could also be a single Azure region.</system:String>
<system:String x:Key="NothingToTranslate">Please select at least one language before you translate the metadata.</system:String>
<system:String x:Key="UnableToTranslate">Microsoft Translator was unable to translate the metadata strings into {0}. Please try again later or remove this language from the translation.</system:String>
<system:String x:Key="ExportHeading">Export .csv files</system:String>
<system:String x:Key="ExportDescription">You can export the metadata captions, descriptions, and display folder strings into .csv files based on the locale identifiers (LCIDs) of the selected languages. Metadata Translator creates a separate .csv file for each LCID.</system:String>
<system:String x:Key="ExportFolderDescription">Select the folder where you want to store your .csv files. Note that Metadata Translator might overwrite any existing files in this folder.</system:String>
<system:String x:Key="ExportButtonCaption">Export...</system:String>
<system:String x:Key="ImportHeading">Import .csv files</system:String>
<system:String x:Key="ImportDescription">You can import translated captions, descriptions, and display folder strings from .csv files. Each file name must match a locale identifier (LCID) of a supported language. You can import multiple .csv files in a single step.</system:String>
<system:String x:Key="ImportButtonCaption">Import...</system:String>
<system:String x:Key="TabularObjectColumnHeader">Object - Type</system:String>
<system:String x:Key="DefaultCultureColumnHeaderToolTip">The default culture is read-only. Use Power BI Desktop to change the names, descriptions, and display folder names for the default culture.</system:String>
</ResourceDictionary>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public class Language : INotifyPropertyChanged
{
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public string LanguageTag { get; set; }
public string TranslationId { get; set; }
public string TranslationGroup { get; set; }
public string DisplayName { get; set; }
public string NativeName { get; set; }
private bool isSelected = false;
public bool IsSelected
{
get => isSelected;
set
{
isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
public bool IsModelDefault { get; set; }
public bool IsNotModelDefault { get => !IsModelDefault; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public class Translation
{
public string text { get; set; }
public string to { get; set; }
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public class TranslationGroup
{
public string Name { get; set; }
public string Tag { get; set; }
public override bool Equals(object obj)
{
if (obj is TranslationGroup comparewith)
{
return Name == comparewith.Name && Tag == comparewith.Tag;
}
return false;
}
public override int GetHashCode()
{
return new { Tag, Name }.GetHashCode();
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Metadata_Translator
{
public class TranslationResult
{
public List<Translation> translations { get; set; }
}
}

View File

@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
namespace Metadata_Translator
{
public class TranslatorService
{
List<Language> Languages { get; set; }
string SourceLanguage { get; set; }
string SubscriptionKey { get; set; }
string Endpoint { get; set; }
string Location { get; set; }
/// <summary>
///
/// </summary>
/// <param name="sourceLanguage"></param>
/// <param name="targetLanguage"></param>
/// <param name="subscriptionKey"></param>
/// <param name="endpoint"></param>
/// <param name="location"></param>
public TranslatorService(List<Language> languages, string sourceLanguage, string subscriptionKey, string endpoint, string location)
{
Languages = languages;
SourceLanguage = sourceLanguage;
SubscriptionKey = subscriptionKey;
Endpoint = endpoint;
Location = location;
}
public void Translate(List<ExpandoObject> dataRows, bool replaceExistingTranslations)
{
/// No languages, or only the one source language? Nothing to translate!
///
if (Languages == null || Languages.Count < 2) return;
/// Get the target languages (i.e. not the SourceLanguage).
///
List<Language> targetLanguages = Languages.Where(l => !l.LanguageTag.Equals(SourceLanguage)).ToList();
/// Filter down the data rows to those with values in the source language.
///
List<ExpandoObject> filteredRows = dataRows.Where(dr => !string.IsNullOrEmpty(dr.GetValue(SourceLanguage)))?.ToList();
/// No rows? Nothing to translate!
///
if (filteredRows == null || filteredRows.Count == 0) return;
/// Iterate over the TranslationGroups, all languages within the same group
/// share the same translation id.
///
foreach(string id in targetLanguages.Select(tl => tl.TranslationId).Distinct())
{
Translate(filteredRows, targetLanguages.Where(tl => tl.TranslationId.Equals(id)), id, replaceExistingTranslations);
}
}
private void Translate(List<ExpandoObject> dataRows, IEnumerable<Language> targetLanguages, string translationId, bool replaceExistingTranslations)
{
List<ExpandoObject> rowsToTranslate = new List<ExpandoObject>();
/// Don't replace existing translations?
/// Filter down the data rows to those that are empty for at least one of the target languages.
///
if (!replaceExistingTranslations)
{
foreach(Language language in targetLanguages)
{
rowsToTranslate.AddRange(dataRows.Where(dr => string.IsNullOrEmpty(dr.GetValue(language.LanguageTag))));
}
rowsToTranslate = rowsToTranslate.Distinct()?.ToList();
}
else
{
/// Otherwise translate all data rows.
///
rowsToTranslate = dataRows;
}
/// Now translate the source strings recursively.
///
Translate(rowsToTranslate, targetLanguages, translationId, replaceExistingTranslations, 0);
}
private void Translate(List<ExpandoObject> dataRows, IEnumerable<Language> targetLanguages, string translationId, bool replaceExistingTranslations, int iterationId)
{
int maxBatchSize = 100;
int batchStart = maxBatchSize * iterationId;
/// Check if all strings have been translated.
///
if (dataRows.Count <= batchStart) return;
/// Assemble a translation batch of up to maxBatchSize.
///
maxBatchSize = (dataRows.Count - batchStart) < maxBatchSize? dataRows.Count - batchStart : maxBatchSize;
List<object> translationBatch = new List<object>();
for (int i = 0; i < maxBatchSize; i++)
{
translationBatch.Add(new { Text = dataRows[batchStart + i].GetValue(SourceLanguage) });
}
/// Translate the batch and assign the translated strings to the target languages.
///
var translatedStrings = TranslateBatch(translationBatch, translationId);
for (int i = 0; i < maxBatchSize; i++)
{
foreach (Language language in targetLanguages)
{
dataRows[batchStart + i].SetValue(language.LanguageTag, translatedStrings[i], replaceExistingTranslations);
}
}
Translate(dataRows, targetLanguages, translationId, replaceExistingTranslations, ++iterationId);
}
private List<string> TranslateBatch(List<object> sourceObjects, string targetLanguage)
{
List<string> translatedPhrases = new List<string>();
var requestBody = new JavaScriptSerializer().Serialize(sourceObjects);
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
/// Build the Web request.
///
request.Method = HttpMethod.Post;
request.RequestUri = new Uri($"{Endpoint}/translate?api-version=3.0&from={SourceLanguage}&to={targetLanguage}");
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
request.Headers.Add("Ocp-Apim-Subscription-Key", SubscriptionKey);
request.Headers.Add("Ocp-Apim-Subscription-Region", Location);
/// Send the translation request and get the response.
///
HttpResponseMessage response = client.SendAsync(request).Result;
string result = response.Content.ReadAsStringAsync().Result;
/// Parse the results and add the strings to the translated phrases if there was no error,
/// i.e. the target language was returned together with the translated string, which is
/// not the case if the service gives back an error message.
///
List<TranslationResult> parsedResults = new JavaScriptSerializer().Deserialize<List<TranslationResult>>(result);
if (parsedResults != null)
{
for (int n = 0; n < parsedResults.Count; n++)
{
translatedPhrases.Add((string.IsNullOrEmpty(parsedResults[n].translations[0].to))? "" : parsedResults[n].translations[0].text);
}
}
}
return translatedPhrases;
}
}
}

View File

@ -0,0 +1,90 @@
<UserControl x:Class="Metadata_Translator.ImportExportPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Metadata_Translator"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<local:PercentageConverter x:Key="PercentageConverter" />
</UserControl.Resources>
<Grid x:Name="underline" MinWidth="200" Margin="8,2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Height="16" Width="16" Background="Transparent" Margin="0,2,4,2"
BorderThickness="0" HorizontalAlignment="Right" VerticalAlignment="Top"
Click="CancelButton_Click">
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</DrawingGroup.Transform>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="1"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<RadialGradientBrush>
<GradientStop Color="Gray"/>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<StackPanel Grid.Row="1" Margin="4,0">
<Grid Margin="4">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" DockPanel.Dock="Top" Text="{StaticResource ExportHeading}" FontStyle="Italic" FontWeight="DemiBold"/>
<Rectangle Grid.Row="1" DockPanel.Dock="Top" Fill="Gray" Height="0.5"
Width="{Binding ElementName=underline, Path=ActualWidth, Converter={StaticResource PercentageConverter}}"
HorizontalAlignment="Left"/>
<TextBlock Grid.Row="2" Text="{StaticResource ExportDescription}" TextWrapping="Wrap" Width="260"
HorizontalAlignment="Left"/>
<Button Grid.Row="3" Margin="8,0" Padding="4" BorderThickness="0" Background="Transparent" Cursor="Hand"
Click="OnExportButton_Click" Tag="{StaticResource ExportFolderDescription}"
HorizontalAlignment="Right" >
<TextBlock TextDecorations="Underline" Text="{StaticResource ExportButtonCaption}" FontStyle="Italic" Foreground="Blue"/>
</Button>
</Grid>
<Grid Margin="4">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" DockPanel.Dock="Top" Text="{StaticResource ImportHeading}" FontStyle="Italic" FontWeight="DemiBold"/>
<Rectangle Grid.Row="1" DockPanel.Dock="Top" Fill="Gray" Height="0.5"
HorizontalAlignment="Left"
Width="{Binding ElementName=underline, Path=ActualWidth, Converter={StaticResource PercentageConverter}}" />
<TextBlock Grid.Row="2" Text="{StaticResource ImportDescription}" TextWrapping="Wrap" Width="260"
HorizontalAlignment="Left"/>
<Button Grid.Row="3" Click="OnImportButton_Click" HorizontalAlignment="Right" Margin="8,0" Padding="4" BorderThickness="0" Background="Transparent" Cursor="Hand">
<TextBlock TextDecorations="Underline" Text="{StaticResource ImportButtonCaption}" FontStyle="Italic" Foreground="Blue"/>
</Button>
</Grid>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,95 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using WF = System.Windows.Forms;
namespace Metadata_Translator
{
/// <summary>
/// Interaction logic for ImportExportPanel.xaml
/// </summary>
public partial class ImportExportPanel : UserControl
{
public ImportExportPanel()
{
InitializeComponent();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
var mainWnd = MainWindow.GetMainWindow(this);
if (mainWnd != null)
{
mainWnd.ImportExportToggle.IsChecked = false;
}
}
private void OnImportButton_Click(object sender, RoutedEventArgs e)
{
var mainWnd = MainWindow.GetMainWindow(this);
if (mainWnd != null)
{
Button importButton = sender as Button;
OpenFileDialog openFileDialog1 = new OpenFileDialog();
// Set filter options and filter index.
openFileDialog1.Filter = "CSV Files (.csv)|*.csv|All Files (*.*)|*.*";
openFileDialog1.FilterIndex = 1;
openFileDialog1.Multiselect = true;
openFileDialog1.CheckFileExists = true;
if (openFileDialog1.ShowDialog() == true)
{
using (new Hourglass())
{
foreach (string filePath in openFileDialog1.FileNames)
{
string lcid = Path.GetFileNameWithoutExtension(filePath);
mainWnd.AddColumn(lcid);
mainWnd.DataModel?.ImportFromCsv(filePath, lcid, mainWnd.OverwriteTranslation);
}
}
}
mainWnd.ImportExportToggle.IsChecked = false;
}
}
private void OnExportButton_Click(object sender, RoutedEventArgs e)
{
var mainWnd = MainWindow.GetMainWindow(this);
if (mainWnd != null)
{
Button exportButton = sender as Button;
WF.FolderBrowserDialog folderDlg = new WF.FolderBrowserDialog
{
SelectedPath = mainWnd.LastUsedExportFolder,
Description = (string)exportButton.Tag,
ShowNewFolderButton = true,
};
WF.DialogResult result = folderDlg.ShowDialog();
if (result == WF.DialogResult.OK)
{
mainWnd.LastUsedExportFolder = folderDlg.SelectedPath;
using (new Hourglass())
{
mainWnd.DataModel?.ExportToCsv(folderDlg.SelectedPath);
}
}
mainWnd.ImportExportToggle.IsChecked = false;
}
}
}
}

View File

@ -0,0 +1,89 @@
<UserControl x:Class="Metadata_Translator.LanguagePanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Metadata_Translator"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
x:Name="lPanel"
d:DesignHeight="450" d:DesignWidth="800" Background="#FFF9F5EB">
<UserControl.Resources>
<CollectionViewSource x:Key="TranslationGroups"
Source="{Binding TranslationGroups, ElementName=lPanel}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<local:PercentageConverter x:Key="PercentageConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Column="1" Height="16" Width="16" Background="Transparent" Margin="0,2,18,2"
BorderThickness="0" HorizontalAlignment="Right" VerticalAlignment="Top"
Click="CancelButton_Click">
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</DrawingGroup.Transform>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="1"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<RadialGradientBrush>
<GradientStop Color="Gray"/>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<ScrollViewer Grid.Row="1" x:Name="underline">
<ItemsControl ItemsSource="{Binding Source={StaticResource TranslationGroups}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Margin="4,0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" DockPanel.Dock="Top" Text="{Binding Name}" FontStyle="Italic" FontWeight="DemiBold" Margin="4,0"/>
<Rectangle Grid.Row="1" DockPanel.Dock="Top" Fill="Gray" Height="0.5" Width="{Binding ElementName=underline, Path=ActualWidth, Converter={StaticResource PercentageConverter}}" />
<local:TranslationGroupPanel Grid.Row="2" Grid.ColumnSpan="2" Margin="18,0,4,4"
Languages="{Binding Languages, ElementName=lPanel}"
TranslationGroupId="{Binding Tag}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Metadata_Translator
{
/// <summary>
/// Interaction logic for LanguagePanel.xaml
/// </summary>
public partial class LanguagePanel : UserControl
{
public LanguagePanel()
{
InitializeComponent();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
var mainWnd = MainWindow.GetMainWindow(this);
if (mainWnd != null)
{
mainWnd.LanguageToggle.IsChecked = false;
}
}
#region Dependency Properties
public static readonly DependencyProperty LanguagesProperty =
DependencyProperty.Register("Languages", typeof(ObservableCollection<Language>), typeof(LanguagePanel),
new PropertyMetadata(null, new PropertyChangedCallback(OnLanguagesChanged)));
public ObservableCollection<Language> Languages
{
get { return (ObservableCollection<Language>)GetValue(LanguagesProperty); }
set { SetValue(LanguagesProperty, value); }
}
private static void OnLanguagesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is LanguagePanel langPanel)
{
langPanel.OnLanguagesChanged(e);
}
}
private void OnLanguagesChanged(DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is ObservableCollection<Language> newItem)
{
newItem.CollectionChanged += LanguagesCollection_Changed;
}
else if (e.OldValue is ObservableCollection<Language> oldItem)
{
oldItem.CollectionChanged -= LanguagesCollection_Changed;
}
}
private void LanguagesCollection_Changed(object sender, NotifyCollectionChangedEventArgs e)
{
TranslationGroups = Languages.Select(x => new TranslationGroup { Name = x.TranslationGroup, Tag = x.TranslationId }).Distinct().ToList();
}
public static readonly DependencyProperty TranslationGroupsProperty =
DependencyProperty.Register("TranslationGroups", typeof(List<TranslationGroup>), typeof(LanguagePanel));
public List<TranslationGroup> TranslationGroups
{
get { return (List<TranslationGroup>)GetValue(TranslationGroupsProperty); }
set { SetValue(TranslationGroupsProperty, value); }
}
#endregion
}
}

View File

@ -0,0 +1,298 @@
<Window x:Class="Metadata_Translator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Metadata_Translator"
mc:Ignorable="d"
x:Name="main"
Title="Metadata Translator" Height="450" Width="800">
<Window.Resources>
<local:TrueFalseVisibilityConverter x:Key="TrueFalseVisibilityConverter" />
<local:FalseTrueVisibilityConverter x:Key="FalseTrueVisibilityConverter" />
<local:CollectionEmptyTrueFalseConverter x:Key="CollectionEmptyTrueFalseConverter" />
<local:TranslationPropertyToolTipConverter x:Key="TranslationPropertyToolTipConverter" />
</Window.Resources>
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ToolBarPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="0">
<ToolBar ToolBarTray.IsLocked="True" Loaded="ToolBar_Loaded">
<RadioButton x:Name="LanguageToggle" GroupName="PanelButtons" Height="48" Width="48"
ToolTip="{StaticResource LanguagesToolTip}"
Click="OnToggleButton_Click" Unchecked="OnToggleButton_Uncheck">
<StackPanel>
<Image Height="24" Width="32">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</DrawingGroup.Transform>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M152.1 236.2c-3.5-12.1-7.8-33.2-7.8-33.2h-.5s-4.3 21.1-7.8 33.2l-11.1 37.5H163zM616 96H336v320h280c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24zm-24 120c0 6.6-5.4 12-12 12h-11.4c-6.9 23.6-21.7 47.4-42.7 69.9 8.4 6.4 17.1 12.5 26.1 18 5.5 3.4 7.3 10.5 4.1 16.2l-7.9 13.9c-3.4 5.9-10.9 7.8-16.7 4.3-12.6-7.8-24.5-16.1-35.4-24.9-10.9 8.7-22.7 17.1-35.4 24.9-5.8 3.5-13.3 1.6-16.7-4.3l-7.9-13.9c-3.2-5.6-1.4-12.8 4.2-16.2 9.3-5.7 18-11.7 26.1-18-7.9-8.4-14.9-17-21-25.7-4-5.7-2.2-13.6 3.7-17.1l6.5-3.9 7.3-4.3c5.4-3.2 12.4-1.7 16 3.4 5 7 10.8 14 17.4 20.9 13.5-14.2 23.8-28.9 30-43.2H412c-6.6 0-12-5.4-12-12v-16c0-6.6 5.4-12 12-12h64v-16c0-6.6 5.4-12 12-12h16c6.6 0 12 5.4 12 12v16h64c6.6 0 12 5.4 12 12zM0 120v272c0 13.3 10.7 24 24 24h280V96H24c-13.3 0-24 10.7-24 24zm58.9 216.1L116.4 167c1.7-4.9 6.2-8.1 11.4-8.1h32.5c5.1 0 9.7 3.3 11.4 8.1l57.5 169.1c2.6 7.8-3.1 15.9-11.4 15.9h-22.9a12 12 0 0 1-11.5-8.6l-9.4-31.9h-60.2l-9.1 31.8c-1.5 5.1-6.2 8.7-11.5 8.7H70.3c-8.2 0-14-8.1-11.4-15.9z">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="10"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<RadialGradientBrush>
<GradientStop Color="#FF2F7C03"/>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock FontSize="9" HorizontalAlignment="Center" Text="{StaticResource Languages}"/>
</StackPanel>
</RadioButton>
<RadioButton x:Name="SettingsToggle" GroupName="PanelButtons" Height="48" Width="48"
ToolTip="{StaticResource SettingsToolTip}"
Click="OnToggleButton_Click" Unchecked="OnToggleButton_Uncheck">
<StackPanel>
<Image Height="24">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</DrawingGroup.Transform>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M113.595,133.642l-5.932-13.169c5.655-4.151,10.512-9.315,14.307-15.209l13.507,5.118c2.583,0.979,5.469-0.322,6.447-2.904
l4.964-13.103c0.47-1.24,0.428-2.616-0.117-3.825c-0.545-1.209-1.547-2.152-2.788-2.622l-13.507-5.118
c1.064-6.93,0.848-14.014-0.637-20.871l13.169-5.932c1.209-0.545,2.152-1.547,2.622-2.788c0.47-1.24,0.428-2.616-0.117-3.825
l-5.755-12.775c-1.134-2.518-4.096-3.638-6.612-2.505l-13.169,5.932c-4.151-5.655-9.315-10.512-15.209-14.307l5.118-13.507
c0.978-2.582-0.322-5.469-2.904-6.447L93.88,0.82c-1.239-0.469-2.615-0.428-3.825,0.117c-1.209,0.545-2.152,1.547-2.622,2.788
l-5.117,13.506c-6.937-1.07-14.033-0.849-20.872,0.636L55.513,4.699c-0.545-1.209-1.547-2.152-2.788-2.622
c-1.239-0.469-2.616-0.428-3.825,0.117L36.124,7.949c-2.518,1.134-3.639,4.094-2.505,6.612l5.932,13.169
c-5.655,4.151-10.512,9.315-14.307,15.209l-13.507-5.118c-1.239-0.469-2.615-0.427-3.825,0.117
c-1.209,0.545-2.152,1.547-2.622,2.788L0.326,53.828c-0.978,2.582,0.322,5.469,2.904,6.447l13.507,5.118
c-1.064,6.929-0.848,14.015,0.637,20.871L4.204,92.196c-1.209,0.545-2.152,1.547-2.622,2.788c-0.47,1.24-0.428,2.616,0.117,3.825
l5.755,12.775c0.544,1.209,1.547,2.152,2.787,2.622c1.241,0.47,2.616,0.429,3.825-0.117l13.169-5.932
c4.151,5.656,9.314,10.512,15.209,14.307l-5.118,13.507c-0.978,2.582,0.322,5.469,2.904,6.447l13.103,4.964
c0.571,0.216,1.172,0.324,1.771,0.324c0.701,0,1.402-0.147,2.054-0.441c1.209-0.545,2.152-1.547,2.622-2.788l5.117-13.506
c6.937,1.069,14.034,0.849,20.872-0.636l5.931,13.168c0.545,1.209,1.547,2.152,2.788,2.622c1.24,0.47,2.617,0.429,3.825-0.117
l12.775-5.754C113.607,139.12,114.729,136.16,113.595,133.642z M105.309,86.113c-4.963,13.1-17.706,21.901-31.709,21.901
c-4.096,0-8.135-0.744-12.005-2.21c-8.468-3.208-15.18-9.522-18.899-17.779c-3.719-8.256-4-17.467-0.792-25.935
c4.963-13.1,17.706-21.901,31.709-21.901c4.096,0,8.135,0.744,12.005,2.21c8.468,3.208,15.18,9.522,18.899,17.778
C108.237,68.434,108.518,77.645,105.309,86.113z M216.478,154.389c-0.896-0.977-2.145-1.558-3.469-1.615l-9.418-0.404
c-0.867-4.445-2.433-8.736-4.633-12.697l6.945-6.374c2.035-1.867,2.17-5.03,0.303-7.064l-6.896-7.514
c-0.896-0.977-2.145-1.558-3.47-1.615c-1.322-0.049-2.618,0.416-3.595,1.312l-6.944,6.374c-3.759-2.531-7.9-4.458-12.254-5.702
l0.404-9.418c0.118-2.759-2.023-5.091-4.782-5.209l-10.189-0.437c-2.745-0.104-5.091,2.023-5.209,4.781l-0.404,9.418
c-4.444,0.867-8.735,2.433-12.697,4.632l-6.374-6.945c-0.896-0.977-2.145-1.558-3.469-1.615c-1.324-0.054-2.618,0.416-3.595,1.312
l-7.514,6.896c-2.035,1.867-2.17,5.03-0.303,7.064l6.374,6.945c-2.531,3.759-4.458,7.899-5.702,12.254l-9.417-0.404
c-2.747-0.111-5.092,2.022-5.21,4.781l-0.437,10.189c-0.057,1.325,0.415,2.618,1.312,3.595c0.896,0.977,2.145,1.558,3.47,1.615
l9.417,0.403c0.867,4.445,2.433,8.736,4.632,12.698l-6.944,6.374c-0.977,0.896-1.558,2.145-1.615,3.469
c-0.057,1.325,0.415,2.618,1.312,3.595l6.896,7.514c0.896,0.977,2.145,1.558,3.47,1.615c1.319,0.053,2.618-0.416,3.595-1.312
l6.944-6.374c3.759,2.531,7.9,4.458,12.254,5.702l-0.404,9.418c-0.118,2.759,2.022,5.091,4.781,5.209l10.189,0.437
c0.072,0.003,0.143,0.004,0.214,0.004c1.25,0,2.457-0.468,3.381-1.316c0.977-0.896,1.558-2.145,1.615-3.469l0.404-9.418
c4.444-0.867,8.735-2.433,12.697-4.632l6.374,6.945c0.896,0.977,2.145,1.558,3.469,1.615c1.33,0.058,2.619-0.416,3.595-1.312
l7.514-6.896c2.035-1.867,2.17-5.03,0.303-7.064l-6.374-6.945c2.531-3.759,4.458-7.899,5.702-12.254l9.417,0.404
c2.756,0.106,5.091-2.022,5.21-4.781l0.437-10.189C217.847,156.659,217.375,155.366,216.478,154.389z M160.157,183.953
c-12.844-0.55-22.846-11.448-22.295-24.292c0.536-12.514,10.759-22.317,23.273-22.317c0.338,0,0.678,0.007,1.019,0.022
c12.844,0.551,22.846,11.448,22.295,24.292C183.898,174.511,173.106,184.497,160.157,183.953z">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="10"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<RadialGradientBrush>
<GradientStop Color="#FFDEA210"/>
<GradientStop Color="#DEA210" Offset="0.992"/>
<GradientStop Color="#AADEA210" Offset="0.527"/>
<GradientStop Color="#FFDEA210" Offset="0.644"/>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock FontSize="9" HorizontalAlignment="Center" Text="{StaticResource Settings}"/>
</StackPanel>
</RadioButton>
<Separator/>
<StackPanel>
<TextBlock FontSize="9" Text="{StaticResource TranslatedProperty}" HorizontalAlignment="Center" Margin="0,4,0,-4"/>
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<StackPanel ToolTip="{StaticResource CaptionToolTip}">
<RadioButton GroupName="TranslationProperty" IsChecked="True" HorizontalAlignment="Center" Click="OnCaptionRadioButton_Click"/>
<TextBlock FontSize="8" Text="{StaticResource Caption}" Padding="2,0"/>
</StackPanel>
<StackPanel ToolTip="{Binding DataModel.Descriptions, ElementName=main, Converter={StaticResource TranslationPropertyToolTipConverter}, ConverterParameter={StaticResource DescriptionToolTip}}">
<RadioButton GroupName="TranslationProperty" HorizontalAlignment="Center" Click="OnDescriptionRadioButton_Click"
IsEnabled="{Binding DataModel.Descriptions, ElementName=main, Converter={StaticResource CollectionEmptyTrueFalseConverter}}"/>
<TextBlock FontSize="8" Text="{StaticResource Description}" Padding="2,0"/>
</StackPanel>
<StackPanel ToolTip="{Binding DataModel.DisplayFolders, ElementName=main, Converter={StaticResource TranslationPropertyToolTipConverter}, ConverterParameter={StaticResource DisplayFolderToolTip}}">
<RadioButton GroupName="TranslationProperty" HorizontalAlignment="Center" Click="OnDisplayFolderRadioButton_Click"
IsEnabled="{Binding DataModel.DisplayFolders, ElementName=main, Converter={StaticResource CollectionEmptyTrueFalseConverter}}"/>
<TextBlock FontSize="8" Text="{StaticResource DisplayFolder}" Padding="2,0"/>
</StackPanel>
</StackPanel>
</StackPanel>
<Separator/>
<Button Height="48" Width="48" Click="OnPrepareButton_Click" Visibility="Collapsed">
<StackPanel>
<Image Height="24" Width="32">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</DrawingGroup.Transform>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M104 224H24c-13.255 0-24 10.745-24 24v240c0 13.255 10.745 24 24 24h80c13.255 0 24-10.745 24-24V248c0-13.255-10.745-24-24-24zM64 472c-13.255 0-24-10.745-24-24s10.745-24 24-24 24 10.745 24 24-10.745 24-24 24zM384 81.452c0 42.416-25.97 66.208-33.277 94.548h101.723c33.397 0 59.397 27.746 59.553 58.098.084 17.938-7.546 37.249-19.439 49.197l-.11.11c9.836 23.337 8.237 56.037-9.308 79.469 8.681 25.895-.069 57.704-16.382 74.757 4.298 17.598 2.244 32.575-6.148 44.632C440.202 511.587 389.616 512 346.839 512l-2.845-.001c-48.287-.017-87.806-17.598-119.56-31.725-15.957-7.099-36.821-15.887-52.651-16.178-6.54-.12-11.783-5.457-11.783-11.998v-213.77c0-3.2 1.282-6.271 3.558-8.521 39.614-39.144 56.648-80.587 89.117-113.111 14.804-14.832 20.188-37.236 25.393-58.902C282.515 39.293 291.817 0 312 0c24 0 72 8 72 81.452z">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="10"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<RadialGradientBrush>
<GradientStop Color="#FFAF800D"/>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock FontSize="9" HorizontalAlignment="Center" Text="{StaticResource Prepare}"/>
</StackPanel>
</Button>
<Button Height="48" Width="48" Click="OnTranslateButton_Click" ToolTip="{StaticResource TranslateToolTip}">
<StackPanel>
<Image Source="..\icon.ico" Height="24"/>
<TextBlock FontSize="9" HorizontalAlignment="Center" Text="{StaticResource Translate}"/>
</StackPanel>
</Button>
<Button Height="48" Width="48" Click="OnApplyButton_Click" ToolTip="{StaticResource ApplyToolTip}">
<StackPanel>
<Image Height="24" Width="32">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</DrawingGroup.Transform>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M400 480H48c-26.51 0-48-21.49-48-48V80c0-26.51 21.49-48 48-48h352c26.51 0 48 21.49 48 48v352c0 26.51-21.49 48-48 48zm-204.686-98.059l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.248-16.379-6.249-22.628 0L184 302.745l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.25 16.379 6.25 22.628.001z">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="10"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<RadialGradientBrush>
<GradientStop Color="#FF2F7C03"/>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock FontSize="9" HorizontalAlignment="Center" Text="{StaticResource Apply}"/>
</StackPanel>
</Button>
</ToolBar>
</ToolBarPanel>
<ToolBarPanel Grid.Row="0" Grid.Column="2" Margin="0">
<ToolBar ToolBarTray.IsLocked="True" Loaded="ToolBar_Loaded">
<RadioButton x:Name="ImportExportToggle" GroupName="PanelButtons" Height="48" Width="68"
ToolTip="{StaticResource ImportExportToolTip}"
Click="OnToggleButton_Click" Unchecked="OnToggleButton_Uncheck">
<StackPanel>
<Image Height="24">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</DrawingGroup.Transform>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm-96 144c0 4.42-3.58 8-8 8h-8c-8.84 0-16 7.16-16 16v32c0 8.84 7.16 16 16 16h8c4.42 0 8 3.58 8 8v16c0 4.42-3.58 8-8 8h-8c-26.51 0-48-21.49-48-48v-32c0-26.51 21.49-48 48-48h8c4.42 0 8 3.58 8 8v16zm44.27 104H160c-4.42 0-8-3.58-8-8v-16c0-4.42 3.58-8 8-8h12.27c5.95 0 10.41-3.5 10.41-6.62 0-1.3-.75-2.66-2.12-3.84l-21.89-18.77c-8.47-7.22-13.33-17.48-13.33-28.14 0-21.3 19.02-38.62 42.41-38.62H200c4.42 0 8 3.58 8 8v16c0 4.42-3.58 8-8 8h-12.27c-5.95 0-10.41 3.5-10.41 6.62 0 1.3.75 2.66 2.12 3.84l21.89 18.77c8.47 7.22 13.33 17.48 13.33 28.14.01 21.29-19 38.62-42.39 38.62zM256 264v20.8c0 20.27 5.7 40.17 16 56.88 10.3-16.7 16-36.61 16-56.88V264c0-4.42 3.58-8 8-8h16c4.42 0 8 3.58 8 8v20.8c0 35.48-12.88 68.89-36.28 94.09-3.02 3.25-7.27 5.11-11.72 5.11s-8.7-1.86-11.72-5.11c-23.4-25.2-36.28-58.61-36.28-94.09V264c0-4.42 3.58-8 8-8h16c4.42 0 8 3.58 8 8zm121-159L279.1 7c-4.5-4.5-10.6-7-17-7H256v128h128v-6.1c0-6.3-2.5-12.4-7-16.9z">
<GeometryDrawing.Pen>
<Pen Brush="White" Thickness="8"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<RadialGradientBrush>
<GradientStop Color="Black"/>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock FontSize="9" HorizontalAlignment="Center" Text="{StaticResource ImportExport}"/>
</StackPanel>
</RadioButton>
</ToolBar>
</ToolBarPanel>
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<local:SettingsPanel Grid.Row="0" Grid.ColumnSpan="3"
Visibility="{Binding IsChecked, ElementName=SettingsToggle, Converter={StaticResource FalseTrueVisibilityConverter}}"/>
<local:LanguagePanel Grid.Row="1" Visibility="{Binding IsChecked, ElementName=LanguageToggle, Converter={StaticResource FalseTrueVisibilityConverter}}"
Languages="{Binding Languages, ElementName=main}"/>
<ScrollViewer Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Auto">
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False" CanUserAddRows="False" BorderThickness="0"/>
</ScrollViewer>
<local:ImportExportPanel Grid.Row="1" Grid.Column="2" Visibility="{Binding IsChecked, ElementName=ImportExportToggle, Converter={StaticResource FalseTrueVisibilityConverter}}"/>
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,638 @@
using Microsoft.AnalysisServices.Tabular;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Metadata_Translator
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Configuration AppConfig { get; set; }
public MainWindow()
{
InitializeComponent();
AppConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
SetDependencyProperty("SubscriptionKey");
SetDependencyProperty("TranslatorEndpoint");
SetDependencyProperty("TranslatorLocation");
SetDependencyProperty("OverwriteTranslation");
SetDependencyProperty("LastUsedExportFolder");
if (string.IsNullOrEmpty(SubscriptionKey))
{
SettingsToggle.IsChecked = true;
}
}
public MainWindow(StartupEventArgs e) : this()
{
try
{
if (e.Args.Length != 2)
{
throw new Exception((string)Application.Current.FindResource("InvalidArguments"));
}
else
{
PowerBIEngine = e.Args[0];
DatabaseName = e.Args[1];
}
DataModel = DataModel.Connect(PowerBIEngine, DatabaseName);
SetDependencyProperty("Languages");
InitializeDataGrid();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
Application.Current.Shutdown();
}
}
public void AddColumn(string lcid)
{
Language language = GetLanguageByLcid(lcid);
if (language != null)
{
dataGrid.Columns.Add(new DataGridTextColumn
{
Header = language.DisplayName,
Binding = new Binding(lcid)
});
language.IsSelected = true;
}
}
#region Helper functions
/// <summary>
/// Set Dependency Properties that wrap apps ettings.
/// </summary>
/// <param name="parameterName"></param>
private void SetDependencyProperty(string parameterName)
{
switch (parameterName)
{
case "SubscriptionKey":
SubscriptionKey = AppConfig.AppSettings.Settings[parameterName].Value;
break;
case "TranslatorEndpoint":
TranslatorEndpoint = AppConfig.AppSettings.Settings[parameterName].Value;
break;
case "TranslatorLocation":
TranslatorLocation = AppConfig.AppSettings.Settings[parameterName].Value;
break;
case "OverwriteTranslation":
string configSetting = AppConfig.AppSettings.Settings[parameterName].Value;
OverwriteTranslation = bool.TryParse(configSetting, out bool value) && value;
break;
case "LastUsedExportFolder":
LastUsedExportFolder = AppConfig.AppSettings.Settings[parameterName].Value;
break;
case "Languages":
Languages = new ObservableCollection<Language>();
foreach (var language in DataModel.SupportedLanguages)
{
Languages.Add(language);
}
break;
}
}
/// <summary>
/// Initializes the datagrid columns and sets the ItemsSource to the default collection.
/// </summary>
private void InitializeDataGrid()
{
List<string> cultures = DataModel.CultureNames;
/// Create some setters and triggers for the
/// styles of the read-only columns.
///
Trigger iIsSelectedTrigger = new Trigger()
{
Property = DataGridTextColumn.IsReadOnlyProperty,
Value = true
};
var foregroundSetter = new Setter(DataGridCell.ForegroundProperty, new SolidColorBrush(Colors.Black));
iIsSelectedTrigger.Setters.Add(foregroundSetter);
var backgroundSetter = new Setter(DataGridCell.BackgroundProperty, new SolidColorBrush(Colors.LightGray));
/// The first column is for the metadata container object.
///
var objectColumnStyle = new Style(typeof(DataGridCell));
objectColumnStyle.Setters.Add(backgroundSetter);
objectColumnStyle.Setters.Add(foregroundSetter);
objectColumnStyle.Triggers.Add(iIsSelectedTrigger);
dataGrid.Columns.Add(new DataGridTextColumn
{
Header = FindResource("TabularObjectColumnHeader").ToString(),
Binding = new Binding(DataModel.ContainerColumnHeader),
IsReadOnly = true,
CellStyle = objectColumnStyle
});
/// The second column is for the default culture of the data model,
/// which is always the first language in the list of data model cultures.
///
var defaultLangColumnStyle = new Style(typeof(DataGridCell));
defaultLangColumnStyle.Setters.Add(foregroundSetter);
defaultLangColumnStyle.Triggers.Add(iIsSelectedTrigger);
/// Add a tooltip to flag that the default culture is readonly.
///
var objectHeaderStyle = new Style(typeof(DataGridColumnHeader));
objectHeaderStyle.Setters.Add(new Setter(ToolTipService.ToolTipProperty,
FindResource("DefaultCultureColumnHeaderToolTip").ToString()));
Language defaultLang = GetLanguageByLcid(cultures[0]);
dataGrid.Columns.Add(new DataGridTextColumn
{
Header = $"{defaultLang.DisplayName}*",
HeaderStyle = objectHeaderStyle,
Binding = new Binding(cultures[0]),
IsReadOnly = true,
CellStyle = defaultLangColumnStyle
});
SetLanguageFlags(cultures[0], true, true);
/// Add the remaining languages that already exist in the data model
/// and mark them as selected in the list of supported languages.
///
for (int i = 1; i< cultures.Count; i++)
{
AddColumn(cultures[i]);
}
/// And set Captions as the default content of the datagrid.
dataGrid.ItemsSource = DataModel.Captions;
}
/// <summary>
/// Marks a language specified by lcid as selected and as model default.
/// </summary>
/// <param name="lcid"></param>
/// <param name="isSelected"></param>
/// <param name="isModelDefault"></param>
private void SetLanguageFlags(string lcid, bool isSelected, bool isModelDefault = false)
{
Language language = Languages.Where(x => x.LanguageTag.Equals(lcid)).FirstOrDefault();
if (language != null)
{
language.IsSelected = isSelected;
language.IsModelDefault = isModelDefault;
}
}
/// <summary>
/// Gets the Language object based on the lcid (i.e. LanguageTag).
/// </summary>
/// <param name="lcid"></param>
/// <returns></returns>
private Language GetLanguageByLcid(string lcid)
{
return Languages.Where(x => x.LanguageTag.Equals(lcid)).FirstOrDefault();
}
/// <summary>
/// Get a handle to the main window object so that other user controls can
/// access the public properties of the main window object directly.
/// </summary>
/// <param name="child"></param>
/// <returns></returns>
public static MainWindow GetMainWindow(DependencyObject child)
{
if (child == null) return null;
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
if (parentObject is MainWindow parent)
{
return parent;
}
else
{
return GetMainWindow(parentObject);
}
}
/// <summary>
/// Save app settings to the application config file.
/// </summary>
/// <param name="settingName"></param>
/// <param name="newValue"></param>
private void OnAppSettingChanged(string settingName, string newValue)
{
AppConfig.AppSettings.Settings[settingName].Value = newValue;
AppConfig.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
}
#endregion
#region Dependency Properties
public static readonly DependencyProperty DataModelProperty =
DependencyProperty.Register("DataModel", typeof(DataModel), typeof(MainWindow));
public DataModel DataModel
{
get { return (DataModel)GetValue(DataModelProperty); }
set { SetValue(DataModelProperty, value); }
}
/// <summary>
/// Supported languages collection
/// </summary>
public static readonly DependencyProperty LanguagesProperty =
DependencyProperty.Register("Languages", typeof(ObservableCollection<Language>), typeof(MainWindow),
new PropertyMetadata(null, new PropertyChangedCallback(OnLanguagesChanged)));
public ObservableCollection<Language> Languages
{
get { return (ObservableCollection<Language>)GetValue(LanguagesProperty); }
set { SetValue(LanguagesProperty, value); }
}
private static void OnLanguagesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as MainWindow).OnLanguagesChanged(e);
}
private void OnLanguagesChanged(DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is ObservableCollection<Language> newCollection)
{
newCollection.CollectionChanged += LanguagesCollection_Changed;
if (newCollection.Count > 0)
AttachLanguagePropertyChangedEventHandler(newCollection);
}
else if (e.OldValue is ObservableCollection<Language> oldCollection)
{
oldCollection.CollectionChanged -= LanguagesCollection_Changed;
if (oldCollection.Count > 0)
RemoveLanguagePropertyChangedEventHandler(oldCollection);
}
}
private void LanguagesCollection_Changed(object sender, NotifyCollectionChangedEventArgs e)
{
switch(e.Action)
{
case NotifyCollectionChangedAction.Add:
AttachLanguagePropertyChangedEventHandler(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveLanguagePropertyChangedEventHandler(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
RemoveLanguagePropertyChangedEventHandler(e.OldItems);
AttachLanguagePropertyChangedEventHandler(e.NewItems);
break;
default:
throw new NotSupportedException();
}
}
private void AttachLanguagePropertyChangedEventHandler(System.Collections.IList items)
{
foreach (Language language in items)
{
language.PropertyChanged += LanguageProperty_Changed;
}
}
private void RemoveLanguagePropertyChangedEventHandler(System.Collections.IList items)
{
foreach (Language language in items)
{
language.PropertyChanged -= LanguageProperty_Changed;
}
}
/// <summary>
/// Event handler for LanguageProperty_Changed event to add or remove a lanugage
/// from the datagrid headers.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LanguageProperty_Changed(object sender, PropertyChangedEventArgs e)
{
if(sender is Language language
&& e.PropertyName == "IsSelected"
&& !language.LanguageTag.Equals(DataModel.DefaultCulture))
{
var existingColumn = dataGrid.Columns.Where(x => language.DisplayName.Equals(x.Header.ToString())).FirstOrDefault();
if (language.IsSelected)
{
if (existingColumn == null)
{
dataGrid.Columns.Add(new DataGridTextColumn
{
Header = language.DisplayName,
Binding = new Binding(language.LanguageTag)
});
}
}
else
{
if (existingColumn != null)
{
dataGrid.Columns.Remove(existingColumn);
}
}
}
}
/// <summary>
/// SubscriptionKey Property
/// </summary>
public static readonly DependencyProperty SubscriptionKeyProperty =
DependencyProperty.Register("SubscriptionKey", typeof(string), typeof(MainWindow),
new PropertyMetadata(new PropertyChangedCallback(OnSubscriptionKeyChanged)));
public string SubscriptionKey
{
get { return (string)GetValue(SubscriptionKeyProperty); }
set { SetValue(SubscriptionKeyProperty, value); }
}
private static void OnSubscriptionKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow)?.OnAppSettingChanged("SubscriptionKey", (string)e.NewValue);
}
/// <summary>
/// TranslatorEndpoint Property
/// </summary>
public static readonly DependencyProperty TranslatorEndpointProperty =
DependencyProperty.Register("TranslatorEndpoint", typeof(string), typeof(MainWindow),
new PropertyMetadata(new PropertyChangedCallback(OnTranslatorEndpointChanged)));
public string TranslatorEndpoint
{
get { return (string)GetValue(TranslatorEndpointProperty); }
set { SetValue(TranslatorEndpointProperty, value); }
}
private static void OnTranslatorEndpointChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow)?.OnAppSettingChanged("TranslatorEndpoint", (string)e.NewValue);
}
/// <summary>
/// TranslatorLocation Property
/// </summary>
public static readonly DependencyProperty TranslatorLocationProperty =
DependencyProperty.Register("TranslatorLocation", typeof(string), typeof(MainWindow),
new PropertyMetadata(new PropertyChangedCallback(OnTranslatorLocationChanged)));
public string TranslatorLocation
{
get { return (string)GetValue(TranslatorLocationProperty); }
set { SetValue(TranslatorLocationProperty, value); }
}
private static void OnTranslatorLocationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow)?.OnAppSettingChanged("TranslatorLocation", (string)e.NewValue);
}
/// <summary>
/// OverwriteTranslation Property
/// </summary>
public static readonly DependencyProperty OverwriteTranslationProperty =
DependencyProperty.Register("OverwriteTranslation", typeof(bool), typeof(MainWindow),
new PropertyMetadata(new PropertyChangedCallback(OnOverwriteTranslationChanged)));
public bool OverwriteTranslation
{
get { return (bool)GetValue(OverwriteTranslationProperty); }
set { SetValue(OverwriteTranslationProperty, value); }
}
private static void OnOverwriteTranslationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow)?.OnAppSettingChanged("OverwriteTranslation", e.NewValue.ToString());
}
/// <summary>
/// LastUsedExportFolder Property
/// </summary>
public static readonly DependencyProperty LastUsedExportFolderProperty =
DependencyProperty.Register("LastUsedExportFolder", typeof(string), typeof(MainWindow),
new PropertyMetadata(new PropertyChangedCallback(OnLastUsedExportFolderChanged)));
public string LastUsedExportFolder
{
get { return (string)GetValue(LastUsedExportFolderProperty); }
set { SetValue(LastUsedExportFolderProperty, value); }
}
private static void OnLastUsedExportFolderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow)?.OnAppSettingChanged("LastUsedExportFolder", (string)e.NewValue);
}
/// <summary>
/// PowerBIEngine Property
/// </summary>
public static readonly DependencyProperty PowerBIEngineProperty =
DependencyProperty.Register("PowerBIEngine", typeof(string), typeof(MainWindow));
public string PowerBIEngine
{
set { SetValue(PowerBIEngineProperty, value); }
get { return (string)GetValue(PowerBIEngineProperty); }
}
/// <summary>
/// DatabaseName Property
/// </summary>
public static readonly DependencyProperty DatabaseNameProperty =
DependencyProperty.Register("DatabaseName", typeof(string), typeof(MainWindow));
public string DatabaseName
{
set { SetValue(DatabaseNameProperty, value); }
get { return (string)GetValue(DatabaseNameProperty); }
}
#endregion
#region Event Handlers
/// <summary>
/// Event handler for ToolBar_Loaded event to remove some of the
/// unnecessary standard UI elements of the toolbar.
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ToolBar_Loaded(object sender, RoutedEventArgs e)
{
if(sender is ToolBar toolBar)
{
if (toolBar.Template.FindName("OverflowGrid", toolBar) is FrameworkElement overflowGrid)
{
overflowGrid.Visibility = Visibility.Collapsed;
}
if (toolBar.Template.FindName("MainPanelBorder", toolBar) is FrameworkElement mainPanelBorder)
{
mainPanelBorder.Margin = new Thickness();
}
}
}
/// <summary>
/// Enabling the deselect of a selected toggle button on mouseclick.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnToggleButton_Click(object sender, RoutedEventArgs e)
{
if(sender is RadioButton radio)
{
bool tagState = (radio.Tag == null);
radio.Tag = (radio.Tag == null) ? new object() : null;
radio.IsChecked = tagState;
}
}
/// <summary>
/// Unsets the tag that indicates if a toggle button was clicked to be deselected.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnToggleButton_Uncheck(object sender, RoutedEventArgs e)
{
if (sender is RadioButton radio)
{
radio.Tag = null;
}
}
/// <summary>
/// Display the Captions in the datagrid.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnCaptionRadioButton_Click(object sender, RoutedEventArgs e)
{
if (DataModel != null)
{
dataGrid.ItemsSource = DataModel.Captions;
}
}
/// <summary>
/// Display the Descriptions in the datagrid.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnDescriptionRadioButton_Click(object sender, RoutedEventArgs e)
{
if (DataModel != null)
{
dataGrid.ItemsSource = DataModel.Descriptions;
}
}
/// <summary>
/// Display the DisplayFolder strings in the datagrid.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnDisplayFolderRadioButton_Click(object sender, RoutedEventArgs e)
{
if (DataModel != null)
{
dataGrid.ItemsSource = DataModel.DisplayFolders;
}
}
/// <summary>
/// Make the terms in the default culture more translatable by splitting the strings
/// based on camel casing.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnPrepareButton_Click(object sender, RoutedEventArgs e)
{
if (DataModel == null) return;
string defaultCulture = DataModel.DefaultCulture;
foreach(ExpandoObject row in DataModel.GetAllDataRows())
{
((IDictionary<String, Object>)row)[defaultCulture] = row.SeparateCamelCase(defaultCulture);
}
}
/// <summary>
/// Translate the strings tn the default culture into the
/// selected target languages.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnTranslateButton_Click(object sender, RoutedEventArgs e)
{
if (DataModel.HasTargetLanguages)
{
using (new Hourglass())
{
var ts = new TranslatorService(Languages.Where(x => x.IsSelected == true)?.ToList(), DataModel.DefaultCulture, SubscriptionKey, TranslatorEndpoint, TranslatorLocation);
ts.Translate(DataModel.GetAllDataRows(), OverwriteTranslation);
}
}
else
{
MessageBox.Show(FindResource("NothingToTranslate").ToString());
LanguageToggle.IsChecked = true;
}
}
/// <summary>
/// Apply the current values in the Captions, Descriptions, and DisplayFolders collections to the data model.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnApplyButton_Click(object sender, RoutedEventArgs e)
{
DataModel.Update();
}
#endregion
}
}

View File

@ -0,0 +1,117 @@
<UserControl x:Class="Metadata_Translator.SettingsPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Metadata_Translator"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
IsVisibleChanged="Visibility_Changed" Background="#FFE7EBEC">
<Grid x:Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{StaticResource DatasetConnection}" Margin="4,10,8,0" VerticalContentAlignment="Center"/>
<Label Grid.Row="1" Content="{StaticResource SubscriptionKey}" Margin="4,0,8,0"/>
<Label Grid.Row="2" Content="{StaticResource TranslatorEndpoint}" Margin="4,0,8,0"/>
<Label Grid.Row="3" Content="{StaticResource TranslatorLocation}" Margin="4,0,8,0"/>
<TextBox x:Name="AsServerInfo" Grid.Column="1" BorderThickness="0" Background="Transparent" IsReadOnly="True" Margin="4,10,8,0" VerticalContentAlignment="Center" Foreground="Gray"/>
<TextBox x:Name="SubscriptionKey" Grid.Row="1" Grid.Column="1" Margin="4,2,26,2">
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Grid>
<Rectangle Width="3000" Height="100" Fill="White"/>
<Label Content="{StaticResource SubscriptionKeyPrompt}" Foreground="Gray" FontStyle="Italic" VerticalAlignment="Center"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="TranslatorEndpoint" Grid.Row="2" Grid.Column="1" Margin="4,2,26,2">
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Grid>
<Rectangle Width="3000" Height="100" Fill="White"/>
<Label Content="{StaticResource TranslatorEndpointPrompt}" Foreground="Gray" FontStyle="Italic" VerticalAlignment="Center"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="TranslatorLocation" Grid.Row="3" Grid.Column="1" Margin="4,2,26,2">
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Grid>
<Rectangle Width="3000" Height="100" Fill="White"/>
<Label Content="{StaticResource TranslatorLocationPrompt}" Foreground="Gray" FontStyle="Italic" VerticalAlignment="Center"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<StackPanel Grid.Column="1" Grid.Row="4" Orientation="Horizontal" Margin="4,0">
<CheckBox x:Name="OverwriteTranslation" VerticalAlignment="Center"/>
<Label Content="{StaticResource OverwriteTranslation}" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="OK" Margin="4" Width="48" Click="OKButton_Click"/>
<Button Content="Cancel" Margin="4" Width="48" Click="CancelButton_Click"/>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Metadata_Translator
{
/// <summary>
/// Interaction logic for SettingsPanel.xaml
/// </summary>
public partial class SettingsPanel : UserControl
{
public SettingsPanel()
{
InitializeComponent();
DataContext = this;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
var mainWnd = MainWindow.GetMainWindow(this);
if(mainWnd != null)
{
mainWnd.SettingsToggle.IsChecked = false;
}
}
private void Visibility_Changed(object sender, DependencyPropertyChangedEventArgs e)
{
var mainWnd = MainWindow.GetMainWindow(this);
if (mainWnd == null) return;
if (e.NewValue is Boolean show && show == true)
{
AsServerInfo.Text = $"Data Source={mainWnd.PowerBIEngine};Initial Catalog={mainWnd.DatabaseName}";
SubscriptionKey.Text = mainWnd.SubscriptionKey;
TranslatorEndpoint.Text = mainWnd.TranslatorEndpoint;
TranslatorLocation.Text = mainWnd.TranslatorLocation;
OverwriteTranslation.IsChecked = mainWnd.OverwriteTranslation;
}
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
var mainWnd = MainWindow.GetMainWindow(this);
if (mainWnd != null)
{
mainWnd.SubscriptionKey = SubscriptionKey.Text;
mainWnd.TranslatorEndpoint = TranslatorEndpoint.Text;
mainWnd.TranslatorLocation = TranslatorLocation.Text;
mainWnd.OverwriteTranslation = (bool) OverwriteTranslation.IsChecked;
mainWnd.SettingsToggle.IsChecked = false;
}
}
}
}

View File

@ -0,0 +1,37 @@
<UserControl x:Class="Metadata_Translator.TranslationGroupPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:Metadata_Translator"
mc:Ignorable="d"
x:Name="tgPanel"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<CollectionViewSource x:Key="FilteredLanguages"
Source="{Binding Languages, ElementName=tgPanel}"
Filter="Languages_Filter">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="DisplayName"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<Grid Margin="0,4,0,0">
<ItemsControl ItemsSource="{Binding Source={StaticResource FilteredLanguages}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" Margin="1" VerticalAlignment="Center" IsEnabled="{Binding IsNotModelDefault}"/>
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" Margin="2,0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Metadata_Translator
{
/// <summary>
/// Interaction logic for TranslationGroupPanel.xaml
/// </summary>
public partial class TranslationGroupPanel : UserControl
{
public TranslationGroupPanel()
{
InitializeComponent();
}
#region Dependency Properties
public static readonly DependencyProperty LanguagesProperty =
DependencyProperty.Register("Languages", typeof(ObservableCollection<Language>), typeof(TranslationGroupPanel));
public ObservableCollection<Language> Languages
{
get { return (ObservableCollection<Language>)GetValue(LanguagesProperty); }
set { SetValue(LanguagesProperty, value); }
}
public static readonly DependencyProperty TranslationGroupIdProperty =
DependencyProperty.Register("TranslationGroupId", typeof(string), typeof(TranslationGroupPanel));
public string TranslationGroupId
{
get { return (string)GetValue(TranslationGroupIdProperty); }
set { SetValue(TranslationGroupIdProperty, value); }
}
#endregion
private void Languages_Filter(object sender, FilterEventArgs e)
{
e.Accepted = (e.Item is Language langItem && langItem.TranslationId == TranslationGroupId);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AnalysisServices.AdomdClient.retail.amd64" version="19.16.3" targetFramework="net472" />
<package id="Microsoft.AnalysisServices.retail.amd64" version="19.16.3" targetFramework="net472" />
</packages>