├── LICENSE
├── LocalizationTracker
├── App.xaml
├── App.xaml.cs
├── AppConfig.cs
├── Components
│ ├── Authorization.cs
│ ├── ComboBoxExtensionMethods.cs
│ ├── ComboBoxWidthFromItemsBehavior.cs
│ ├── InlineTemplate.cs
│ ├── InlineTemplatesHelper.cs
│ ├── InlinesTextBlock.cs
│ ├── InlinesWrapper.cs
│ ├── LocaleDetailsView.xaml
│ ├── LocaleDetailsView.xaml.cs
│ ├── RepoConnection.cs
│ ├── StringDetailsView.xaml
│ ├── StringDetailsView.xaml.cs
│ ├── TargetSelector.xaml
│ ├── TargetSelector.xaml.cs
│ ├── TargetSelectorEntry.cs
│ ├── TraitDetailsView.xaml
│ ├── TraitDetailsView.xaml.cs
│ ├── TraitSelectorEntry.cs
│ ├── TraitsSelector.xaml
│ └── TraitsSelector.xaml.cs
├── Data
│ ├── BoundsHelper.cs
│ ├── ExportRequestResult.cs
│ ├── ExportResults.cs
│ ├── ImportEntry.cs
│ ├── ImportFileResult.cs
│ ├── ImportRequestResult.cs
│ ├── JsonLocalizationData.cs
│ ├── LocaleEntry.cs
│ ├── Shared
│ │ ├── Locale.cs
│ │ ├── LocaleTrait.cs
│ │ ├── LocalizedStringData.cs
│ │ ├── StringTrait.cs
│ │ └── TraitData.cs
│ ├── StringEntry.cs
│ ├── StringEntryComparison.cs
│ ├── TagsList.cs
│ ├── Unreal
│ │ ├── StringsArchiveUnreal.cs
│ │ ├── UnrealLocaleData.cs
│ │ ├── UnrealStringData.cs
│ │ └── UnrealTraitData.cs
│ └── Wrappers
│ │ ├── ILocaleData.cs
│ │ ├── IStringData.cs
│ │ ├── ITraitData.cs
│ │ ├── StringsArchive.cs
│ │ ├── StringsArchiveUnity.cs
│ │ └── TextFixupHelper.cs
├── GlobalUsings.cs
├── LocalizationTracker.csproj
├── Logic
│ ├── DublicatesResolver.cs
│ ├── Excel
│ │ ├── ExcelExctentions.cs
│ │ ├── ExcelImporter.cs
│ │ ├── ExcelStyles.cs
│ │ ├── ExcelStylesCreator.cs
│ │ ├── ExcelTagMismatchStyle.cs
│ │ ├── ExportWrapper.cs
│ │ ├── Exporters
│ │ │ ├── ExcelLocalizationExporter.cs
│ │ │ ├── ExcelSourceUpdateExporter.cs
│ │ │ ├── ExcelTagsMismathExporter.cs
│ │ │ ├── SoundExporter.cs
│ │ │ ├── SpeakersExport.cs
│ │ │ └── VoiceCommentsExport.cs
│ │ ├── IExcelStyler.cs
│ │ ├── SheetDataExtentions.cs
│ │ ├── SheetWrapper.cs
│ │ ├── SpreadsheetHelper.cs
│ │ ├── WorkbookWrapper.cs
│ │ └── Wrappers
│ │ │ ├── ExportHierarchyWrapper.cs
│ │ │ └── ExportSingleFileWrapper.cs
│ ├── ImportDiffExporter.cs
│ ├── JsonImporter.cs
│ ├── KnownTraits.cs
│ ├── LocalizationDiffExporter.cs
│ ├── LocalizationExporter.cs
│ ├── LocalizationImporter.cs
│ ├── OpenOffice
│ │ ├── OpenOfficeExporter.cs
│ │ ├── OpenOfficeImporter.cs
│ │ └── OpenOfficeXmlHelper.cs
│ ├── StringManager.cs
│ ├── TranslateUtility.cs
│ └── VoiceCommentsImporter.cs
├── OwlcatProtocolListener.cs
├── Properties
│ ├── PublishProfiles
│ │ └── FolderProfile.pubxml
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ ├── Settings.settings
│ └── launchSettings.json
├── Tools
│ ├── Diff.cs
│ ├── FilterFits.cs
│ ├── GlossaryTools
│ │ ├── DataStructure
│ │ │ ├── Glossary.cs
│ │ │ ├── TermEntry.cs
│ │ │ ├── TermTemplate.cs
│ │ │ └── TermTemplateCollection.cs
│ │ ├── Json
│ │ │ ├── BakingSheet.Json.asmdef
│ │ │ ├── BakingSheet.Json.asmdef.meta
│ │ │ ├── JsonSheetAssetPathConverter.cs
│ │ │ ├── JsonSheetAssetPathConverter.cs.meta
│ │ │ ├── JsonSheetContractResolver.cs
│ │ │ ├── JsonSheetContractResolver.cs.meta
│ │ │ ├── JsonSheetConverter.cs
│ │ │ ├── JsonSheetConverter.cs.meta
│ │ │ ├── JsonSheetReferenceConverter.cs
│ │ │ └── JsonSheetReferenceConverter.cs.meta
│ │ └── SheetImport
│ │ │ ├── GlossarySheet.cs
│ │ │ ├── GlossarySheetContainer.cs
│ │ │ ├── Logger.cs
│ │ │ └── SheetImporter.cs
│ ├── MaxLength.cs
│ ├── SVGTool
│ │ ├── GenerateSVG.cs
│ │ └── SvgWriter.cs
│ └── SpellCheck.cs
├── Utility
│ ├── ColorUtility.cs
│ ├── GenderSystem.cs
│ ├── JsonSerializerHelpers.cs
│ ├── LinqExtension.cs
│ ├── ObservableRangeCollection.cs
│ ├── RepairKit.cs
│ ├── StopWatchUtils.cs
│ ├── StringUtils.cs
│ ├── SystemExtentions.cs
│ ├── UnityAssets.cs
│ └── WinFormsUtility.cs
├── ViewModel
│ ├── FolderItemTreeModel.cs
│ ├── LocaleDetailsVM.cs
│ ├── SpeakersChangedVM.cs
│ ├── StringDetailsVM.cs
│ └── TraitDetailsVM.cs
├── Windows
│ ├── AuthorizationWindow.xaml
│ ├── AuthorizationWindow.xaml.cs
│ ├── ChangeTraitsDialog.xaml
│ ├── ChangeTraitsDialog.xaml.cs
│ ├── DublicateResolverWindow.xaml
│ ├── DublicateResolverWindow.xaml.cs
│ ├── ExportDialog.xaml
│ ├── ExportDialog.xaml.cs
│ ├── ExportResultsWindow.xaml
│ ├── ExportResultsWindow.xaml.cs
│ ├── FilterMode.cs
│ ├── ImportErrorWindow.xaml
│ ├── ImportErrorWindow.xaml.cs
│ ├── ImportResultsWindow.xaml
│ ├── ImportResultsWindow.xaml.cs
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ ├── MultilineSearch.xaml
│ ├── MultilineSearch.xaml.cs
│ ├── ProgressReporter.cs
│ ├── ProgressWindow.xaml
│ ├── ProgressWindow.xaml.cs
│ ├── SpeakerChangedWindow.xaml
│ ├── SpeakerChangedWindow.xaml.cs
│ ├── StringDetailsWindow.xaml
│ ├── StringDetailsWindow.xaml.cs
│ ├── StringsFilter.cs
│ └── TraitsFilter.cs
└── config.json
└── README.md
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Owlcat Games
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LocalizationTracker/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/ComboBoxExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Automation.Peers;
3 | using System.Windows.Automation.Provider;
4 | using System.Windows.Controls.Primitives;
5 | using System.Windows.Controls;
6 | using System.Windows;
7 |
8 | namespace LocalizationTracker.Components
9 | {
10 | public static class ComboBoxExtensionMethods
11 | {
12 | public static void SetWidthFromItems(this ComboBox comboBox)
13 | {
14 | double comboBoxWidth = 19;// comboBox.DesiredSize.Width;
15 |
16 | // Create the peer and provider to expand the comboBox in code behind.
17 | ComboBoxAutomationPeer peer = new ComboBoxAutomationPeer(comboBox);
18 | IExpandCollapseProvider provider = (IExpandCollapseProvider)peer.GetPattern(PatternInterface.ExpandCollapse);
19 | EventHandler? eventHandler = null;
20 | eventHandler = new EventHandler(delegate
21 | {
22 | if (comboBox.IsDropDownOpen &&
23 | comboBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
24 | {
25 | double width = 0;
26 | foreach (var item in comboBox.Items)
27 | {
28 | ComboBoxItem comboBoxItem = (ComboBoxItem)comboBox.ItemContainerGenerator.ContainerFromItem(item);
29 | comboBoxItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
30 | if (comboBoxItem.DesiredSize.Width > width)
31 | {
32 | width = comboBoxItem.DesiredSize.Width;
33 | }
34 | }
35 | comboBox.Width = comboBoxWidth + width;
36 | // Remove the event handler.
37 | comboBox.ItemContainerGenerator.StatusChanged -= eventHandler;
38 | comboBox.DropDownOpened -= eventHandler;
39 | provider.Collapse();
40 | }
41 | });
42 | comboBox.ItemContainerGenerator.StatusChanged += eventHandler;
43 | comboBox.DropDownOpened += eventHandler;
44 | // Expand the comboBox to generate all its ComboBoxItem's.
45 | provider.Expand();
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/ComboBoxWidthFromItemsBehavior.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Threading;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 |
6 | namespace LocalizationTracker.Components
7 | {
8 | public static class ComboBoxWidthFromItemsBehavior
9 | {
10 | public static readonly DependencyProperty ComboBoxWidthFromItemsProperty =
11 | DependencyProperty.RegisterAttached
12 | (
13 | "ComboBoxWidthFromItems",
14 | typeof(bool),
15 | typeof(ComboBoxWidthFromItemsBehavior),
16 | new UIPropertyMetadata(false, OnComboBoxWidthFromItemsPropertyChanged)
17 | );
18 | public static bool GetComboBoxWidthFromItems(DependencyObject obj)
19 | {
20 | return (bool)obj.GetValue(ComboBoxWidthFromItemsProperty);
21 | }
22 | public static void SetComboBoxWidthFromItems(DependencyObject obj, bool value)
23 | {
24 | obj.SetValue(ComboBoxWidthFromItemsProperty, value);
25 | }
26 | private static void OnComboBoxWidthFromItemsPropertyChanged(DependencyObject dpo,
27 | DependencyPropertyChangedEventArgs e)
28 | {
29 | ComboBox? comboBox = dpo as ComboBox;
30 | if (comboBox != null)
31 | {
32 | if ((bool)e.NewValue == true)
33 | {
34 | comboBox.Loaded += OnComboBoxLoaded;
35 | }
36 | else
37 | {
38 | comboBox.Loaded -= OnComboBoxLoaded;
39 | }
40 | }
41 | }
42 | private static void OnComboBoxLoaded(object sender, RoutedEventArgs e)
43 | {
44 | ComboBox comboBox = sender as ComboBox;
45 | Action action = () => { comboBox.SetWidthFromItems(); };
46 | comboBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/InlineTemplate.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Documents;
3 | using System.Windows.Media;
4 | using static LocalizationTracker.Components.InlineTemplate;
5 |
6 | namespace LocalizationTracker.Components;
7 |
8 | public record struct InlineTemplate(
9 | string Text,
10 | Color? Foreground = null,
11 | Color? Background = null,
12 | bool StrikeThrough = false,
13 | bool Underline = false,
14 | FontWeight? FontWeight = null,
15 | InlineType InlineType = InlineType.Default)
16 | {
17 | public InlineTemplate(string text, Color? foreground, Color? background, bool strikeThrough, FontWeight? fontWeight)
18 | :this(text, foreground, background, strikeThrough, false, fontWeight)
19 | {
20 | }
21 |
22 | public Inline MakeInline()
23 | {
24 | var run = new Run(Text);
25 | if (Foreground != null)
26 | run.Foreground = new SolidColorBrush(Foreground.Value);
27 | if (Background != null)
28 | run.Background = new SolidColorBrush(Background.Value);
29 | if (StrikeThrough)
30 | run.TextDecorations.Add(TextDecorations.Strikethrough);
31 | if (FontWeight != null)
32 | run.FontWeight = FontWeight.Value;
33 | if (Underline)
34 | run.TextDecorations.Add(GetUnderlineDecoration());
35 | return run;
36 | }
37 |
38 | private TextDecoration GetUnderlineDecoration()
39 | {
40 | TextDecoration myUnderline = new();
41 | Pen myPen = new();
42 | myPen.Brush = new SolidColorBrush(Colors.Blue);
43 | myPen.Brush.Opacity = 1;
44 | myPen.Thickness = 2;
45 | myUnderline.Pen = myPen;
46 | myUnderline.PenThicknessUnit = TextDecorationUnit.FontRecommended;
47 | return myUnderline;
48 | }
49 | }
50 | public enum InlineType
51 | {
52 | Default,
53 | DiffDelete,
54 | DiffInsert,
55 | SpellCheckError,
56 | GlossaryTerm,
57 | MaxLength
58 | }
59 |
60 | public enum InlineCollectionType
61 | {
62 | Default,
63 | TagsMismatch,
64 | DiffTrait,
65 | DiffSource,
66 | DiffSourceNoTags,
67 | SpellCheck,
68 | Filter,
69 | Glossary,
70 | MaxLength
71 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Components/InlineTemplatesHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace LocalizationTracker.Components;
6 |
7 | public static class InlineTemplatesHelper
8 | {
9 | public static InlinesWrapper MergeWith(this InlinesWrapper first, InlinesWrapper second) =>
10 | new InlinesWrapper(Merge(first.InlineTemplates, second.InlineTemplates));
11 |
12 | public static InlineTemplate[] Merge(InlineTemplate[] left, InlineTemplate[] right)
13 | {
14 | var leftLength = left.Sum(it => it.Text.Length);
15 | var rightLength = right.Sum(it => it.Text.Length);
16 | if (leftLength != rightLength) return left;
17 |
18 | IList current = new List();
19 |
20 | var li = 0; // left
21 | var ri = 0; // right
22 | var ci = 0; // current
23 |
24 | var lp = 0;
25 | var rp = 0;
26 | var cp = 0;
27 |
28 | while (lp < leftLength)
29 | {
30 | var le = lp + left[li].Text.Length;
31 | var re = rp + right[ri].Text.Length;
32 |
33 | InlineTemplate newInline = new InlineTemplate("",
34 | right[ri].Foreground ?? left[li].Foreground ,
35 | right[ri].Background ?? left[li].Background,
36 | right[ri].StrikeThrough ? right[ri].StrikeThrough : left[li].StrikeThrough,
37 | right[ri].Underline ? right[ri].Underline : left[li].Underline,
38 | right[ri].FontWeight ?? left[li].FontWeight,
39 | right[ri].InlineType != InlineType.Default ? right[ri].InlineType : left[li].InlineType
40 | );
41 |
42 | if (lp < rp) // left start first
43 | {
44 | if (le < re)
45 | {
46 | newInline.Text = right[ri].Text.Substring(0, le - rp);
47 | lp = le;
48 | li++;
49 | }
50 | else
51 | {
52 | newInline.Text = right[ri].Text;
53 | rp = re;
54 | ri++;
55 | if (le == re)
56 | {
57 | lp = le;
58 | li++;
59 | }
60 | }
61 | }
62 | else
63 | {
64 | if (le < re)
65 | {
66 | newInline.Text = left[li].Text;
67 | lp = le;
68 | li++;
69 | }
70 | else
71 | {
72 | newInline.Text = left[li].Text.Substring(0, re - lp);
73 | rp = re;
74 | ri++;
75 | if (le == re)
76 | {
77 | lp = le;
78 | li++;
79 | }
80 | }
81 | }
82 |
83 |
84 | current.Add(newInline);
85 | }
86 |
87 | return current.ToArray();
88 | }
89 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Components/InlinesTextBlock.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 |
4 | namespace LocalizationTracker.Components
5 | {
6 | public class InlinesTextBlock : TextBlock
7 | {
8 | public InlinesWrapper? InlinesWrapper { get; set; }
9 |
10 | public static readonly DependencyProperty LocaleEntryProperty =
11 | DependencyProperty.Register("InlinesWrapper", typeof(InlinesWrapper), typeof(InlinesTextBlock), new UIPropertyMetadata(null, InlinesContentPropertyChanged));
12 |
13 | private static void InlinesContentPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
14 | {
15 | if (sender is not InlinesTextBlock textBlock)
16 | return;
17 |
18 | textBlock.Inlines.Clear();
19 | if (e.NewValue is not InlinesWrapper wrapper)
20 | return;
21 |
22 | textBlock.Inlines.AddRange(wrapper.Inlines);
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Components/InlinesWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Documents;
7 | using System.Xml;
8 |
9 | namespace LocalizationTracker.Components
10 | {
11 | public class InlinesWrapper
12 | {
13 | public IEnumerable Inlines => InlineTemplates.Select(v => v.MakeInline());
14 |
15 | public readonly InlineTemplate[] InlineTemplates = Array.Empty();
16 |
17 | public bool HasAny => InlineTemplates.Length > 0;
18 |
19 | public InlinesWrapper()
20 | {
21 | }
22 |
23 | public InlinesWrapper(string text)
24 | {
25 | if (!string.IsNullOrEmpty(text))
26 | InlineTemplates = new[] { new InlineTemplate(text) };
27 | }
28 |
29 | public InlinesWrapper(InlineTemplate[] inlines)
30 | {
31 | InlineTemplates = inlines;
32 | }
33 |
34 | public override string ToString()
35 | {
36 | var sb = new StringBuilder();
37 | foreach (var inline in InlineTemplates)
38 | {
39 | sb.Append(inline.Text);
40 | }
41 |
42 | return sb.ToString();
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Components/LocaleDetailsView.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | Locale
13 |
14 |
15 |
16 | Updated
17 |
18 |
19 |
20 | Text
21 |
22 |
23 |
24 | Comment
25 |
26 |
27 |
28 |
29 |
30 | Translated From
31 |
32 |
33 |
34 | Translation Date
35 |
36 |
37 |
38 | Original Text
39 |
40 |
41 |
42 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/LocaleDetailsView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace LocalizationTracker.Components
17 | {
18 | ///
19 | /// Interaction logic for LocaleDetails.xaml
20 | ///
21 | public partial class LocaleDetailsView : UserControl
22 | {
23 | public LocaleDetailsView()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/StringDetailsView.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | Key
13 |
14 |
15 |
16 | Path
17 |
18 |
19 |
20 | Comment
21 |
22 |
23 |
24 | Kind
25 |
26 |
27 |
28 | Speaker
29 |
30 |
31 |
32 | Attachment
33 |
34 |
35 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
55 |
56 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/StringDetailsView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace LocalizationTracker.Components
17 | {
18 | ///
19 | /// Interaction logic for UserControl1.xaml
20 | ///
21 | public partial class StringDetailsView : UserControl
22 | {
23 | public StringDetailsView()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/TargetSelector.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
34 |
35 |
36 |
38 |
39 |
40 |
44 |
45 |
46 |
47 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/TargetSelector.xaml.cs:
--------------------------------------------------------------------------------
1 | using StringsCollector.Data;
2 | using LocalizationTracker.Data;
3 | using LocalizationTracker.Windows;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Collections.ObjectModel;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Input;
12 |
13 | namespace LocalizationTracker.Components
14 | {
15 | public partial class TargetSelector : UserControl
16 | {
17 | public ObservableCollection TargetEntries { get; } = new ObservableCollection();
18 |
19 |
20 | public ObservableCollection TargetSource
21 | {
22 | get { return (ObservableCollection)GetValue(TargetSourceProperty); }
23 | set { SetValue(TargetSourceProperty, value); }
24 | }
25 |
26 | public static readonly DependencyProperty TargetSourceProperty =
27 | DependencyProperty.Register("TargetSource", typeof(ObservableCollection), typeof(TargetSelector), new PropertyMetadata(TargetSourcePropertyChanged));
28 |
29 | public string SelectedTargetText
30 | {
31 | get { return (string)GetValue(SelectedTargetTextProperty); }
32 | set { SetValue(SelectedTargetTextProperty, value); }
33 | }
34 |
35 | public static readonly DependencyProperty SelectedTargetTextProperty =
36 | DependencyProperty.Register(nameof(SelectedTargetText), typeof(string), typeof(TargetSelector));
37 |
38 | public TargetSelector()
39 | {
40 | InitializeComponent();
41 | CheckedListBox.ItemsSource = TargetEntries;
42 | UpdateTargetsList();
43 | }
44 |
45 | private void ButtonShowPopup_Click(object sender, RoutedEventArgs e)
46 | {
47 | PopupTraits.IsOpen = !PopupTraits.IsOpen;
48 | }
49 |
50 | private static void TargetSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
51 | {
52 | var selector = sender as TargetSelector;
53 | selector?.UpdateTargetsList();
54 | }
55 |
56 | private void UpdateTargetsList()
57 | {
58 | TargetEntries.Clear();
59 |
60 | foreach (var line in Locale.Values)
61 | {
62 | if (line == StringEntry.TargetLocale)
63 | {
64 | TargetEntries.Add(new TargetSelectorEntry(line, true));
65 | SelectedTargetText = string.Join(", ", SelectedTargets);
66 | ButtonText.Text = $"{SelectedTargetText}";
67 | }
68 | else
69 | {
70 | TargetEntries.Add(new TargetSelectorEntry(line, false));
71 | }
72 | }
73 |
74 | CheckedListBox.SelectedItem = TargetEntries.FirstOrDefault();
75 |
76 | if (TargetSource != null)
77 | {
78 | foreach (var e in TargetEntries)
79 | {
80 | e.Selected = TargetSource.Contains(e.Target);
81 | }
82 | ElementSelected(null, null);
83 | }
84 |
85 | }
86 |
87 | public IEnumerable SelectedTargets =>
88 | TargetEntries
89 | .Where(t => t.Selected)
90 | .Select(t => t.Target);
91 |
92 | private void ElementSelected(object sender, RoutedEventArgs e)
93 | {
94 | SelectedTargetText = string.Join(", ", SelectedTargets);
95 |
96 | if (TargetSource != null)
97 | {
98 | TargetSource.Clear();
99 | foreach (var t in SelectedTargets)
100 | {
101 | TargetSource.Add(t);
102 | }
103 | }
104 |
105 | ButtonText.Text = $"{SelectedTargetText}";
106 | }
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/TargetSelectorEntry.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Runtime.CompilerServices;
3 | using JetBrains.Annotations;
4 | using StringsCollector.Data;
5 |
6 | namespace LocalizationTracker.Components
7 | {
8 | public class TargetSelectorEntry
9 | {
10 | private bool m_Selected;
11 |
12 | public bool Selected
13 | {
14 | get { return m_Selected; }
15 | set
16 | {
17 | {
18 | m_Selected = value;
19 | OnPropertyChanged(nameof(Selected));
20 | }
21 | }
22 | }
23 |
24 | public Locale Target { get; }
25 |
26 | public TargetSelectorEntry(Locale target, bool selected)
27 | {
28 | Target = target;
29 | Selected = selected;
30 | }
31 |
32 | public event PropertyChangedEventHandler? PropertyChanged;
33 |
34 | [NotifyPropertyChangedInvocator]
35 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
36 | {
37 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Components/TraitDetailsView.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | Trait
13 |
14 |
15 |
16 | Updated
17 |
18 |
19 |
20 | Text
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/TraitDetailsView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace LocalizationTracker.Components
17 | {
18 | ///
19 | /// Interaction logic for TraitDetailsView.xaml
20 | ///
21 | public partial class TraitDetailsView : UserControl
22 | {
23 | public TraitDetailsView()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LocalizationTracker/Components/TraitSelectorEntry.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Runtime.CompilerServices;
3 | using JetBrains.Annotations;
4 |
5 | namespace LocalizationTracker.Components
6 | {
7 | public class TraitSelectorEntry
8 | {
9 | private bool m_Selected;
10 |
11 | public bool Selected
12 | {
13 | get { return m_Selected; }
14 | set
15 | {
16 | {
17 | m_Selected = value;
18 | OnPropertyChanged(nameof(Selected));
19 | }
20 | }
21 | }
22 |
23 | public string Trait { get; }
24 |
25 | public TraitSelectorEntry(string trait, bool selected)
26 | {
27 | Trait = trait;
28 | Selected = selected;
29 | }
30 |
31 | public event PropertyChangedEventHandler? PropertyChanged;
32 |
33 | [NotifyPropertyChangedInvocator]
34 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
35 | {
36 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Components/TraitsSelector.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
31 |
32 |
33 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/LocalizationTracker/Data/BoundsHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace LocalizationTracker.Data
4 | {
5 | public delegate int Comparison(T x, Q y);
6 |
7 | public static class BoundsHelper
8 | {
9 | public static int LowerBound(this IList array, int start, int count, Q value, Comparison comparison)
10 | {
11 | while (count > 0)
12 | {
13 | int step = count / 2;
14 | int middle = start + step;
15 |
16 | if (comparison(array[middle], value) < 0)
17 | {
18 | start = middle + 1;
19 | count -= step + 1;
20 | }
21 | else
22 | count = step;
23 | }
24 |
25 | return start;
26 | }
27 |
28 | public static int LowerBound(this IList array, int start, int count, T value)
29 | => LowerBound(array, start, count, value, Comparer.Default.Compare);
30 |
31 | public static int LowerBound(this IList array, T value)
32 | => LowerBound(array, 0, array.Count, value, Comparer.Default.Compare);
33 |
34 | public static int LowerBound(this IList array, Q value, Comparison comparison)
35 | => LowerBound(array, 0, array.Count, value, comparison);
36 |
37 | public static int UpperBound(this IList array, int start, int count, Q value, Comparison comparer)
38 | {
39 | while (count > 0)
40 | {
41 | int step = count / 2;
42 | int middle = start + step;
43 |
44 | if (comparer(array[middle], value) <= 0)
45 | {
46 | start = middle + 1;
47 | count -= step + 1;
48 | }
49 | else
50 | count = step;
51 | }
52 | return start;
53 | }
54 |
55 | public static int UpperBound(this IList array, int start, int count, T value)
56 | => UpperBound(array, start, count, value, Comparer.Default.Compare);
57 |
58 | public static int UpperBound(this IList array, T value)
59 | => UpperBound(array, 0, array.Count, value, Comparer.Default.Compare);
60 |
61 | public static int UpperBound(this IList array, Q value, Comparison comparer)
62 | => UpperBound(array, 0, array.Count, value, comparer);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/LocalizationTracker/Data/ExportRequestResult.cs:
--------------------------------------------------------------------------------
1 | using StringsCollector.Data;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace LocalizationTracker.Data
7 | {
8 | public class ExportRequestResult
9 | {
10 | public ExportRequestResult(Locale source, Locale target)
11 | {
12 | SourceLocale = source;
13 | TargetLocale = target;
14 | }
15 |
16 | #region Fields
17 |
18 | LinkedList _fileNames = new LinkedList();
19 |
20 | #endregion Fields
21 | #region Properties
22 |
23 | public Locale SourceLocale { get; }
24 |
25 | public Locale TargetLocale { get; }
26 |
27 | public int SourceVocalWordCount { get; private set; }
28 | public int SourceTotalWordCount { get; private set; }
29 |
30 | public int SourceSymbolCount { get; private set; }
31 |
32 | public IReadOnlyCollection FileNames => _fileNames;
33 |
34 | public bool IsEmpty => FileNames.Count == 0;
35 |
36 | #endregion Properties
37 | #region Methods
38 |
39 | public void Append(ExportResults result)
40 | {
41 | if (result == default)
42 | return;
43 |
44 | if (result.SourceLocale == SourceLocale && result.TargetLocale == TargetLocale)
45 | {
46 | _fileNames.AddLast(result.FileName);
47 | SourceVocalWordCount += result.SourceVocalWordCount;
48 | SourceTotalWordCount += result.SourceTotalWordCount;
49 | SourceSymbolCount += result.SourceSymbolCount;
50 | }
51 | }
52 |
53 | public string GenerateText()
54 | {
55 | var sb = new StringBuilder();
56 | if (SourceLocale == TargetLocale)
57 | {
58 | sb.Append($"Exported for correction: {SourceLocale}\n");
59 | }
60 | else
61 | {
62 | sb.Append($"Exported for translation: {SourceLocale} => {TargetLocale}\n");
63 | }
64 |
65 | if (FileNames.Count == 1)
66 | sb.Append($"File: {FileNames.First()}\n");
67 | else
68 | sb.Append($"Files: {FileNames.Count}\n");
69 | if(TargetLocale == Locale.Sound)
70 | sb.Append($"Wordcount for VO: {SourceVocalWordCount}\n");
71 | sb.Append($"Total Word Count: {SourceTotalWordCount}\n");
72 | sb.Append($"Symbol Count: {SourceSymbolCount}\n");
73 | return sb.ToString();
74 | }
75 |
76 | #endregion Methods
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/LocalizationTracker/Data/ExportResults.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using StringsCollector.Data;
3 | using LocalizationTracker.Utility;
4 |
5 | namespace LocalizationTracker.Data
6 | {
7 | public class ExportResults
8 | {
9 | public Locale SourceLocale { get; }
10 |
11 | public Locale TargetLocale { get; }
12 |
13 | public string FileName { get; }
14 |
15 | public int SourceVocalWordCount { get; private set; }
16 | public int SourceTotalWordCount { get; private set; }
17 |
18 | public int SourceSymbolCount { get; private set; }
19 |
20 | public ExportResults(Locale sourceLocale, Locale targetLocale, string fileName)
21 | {
22 | SourceLocale = sourceLocale;
23 | TargetLocale = targetLocale;
24 | FileName = fileName;
25 | }
26 |
27 | public void AddText(string text)
28 | {
29 | SourceSymbolCount += text.Length;
30 | var wordCount = StringUtils.CountTotalWords(text);
31 | if(TargetLocale == Locale.Sound)
32 | SourceVocalWordCount += wordCount - StringUtils.CountNonVocalWords(text);
33 | SourceTotalWordCount += wordCount;
34 | }
35 |
36 | public string GenerateText()
37 | {
38 | var sb = new StringBuilder();
39 | if (SourceLocale == TargetLocale)
40 | {
41 | sb.Append($"Exported for correction: {SourceLocale}\n");
42 | }
43 | else
44 | {
45 | sb.Append($"Exported for translation: {SourceLocale} => {TargetLocale}\n");
46 | }
47 |
48 | sb.Append($"File: {FileName}\n");
49 | if (TargetLocale == Locale.Sound)
50 | sb.Append($"Wordcount for VO: {SourceVocalWordCount}\n");
51 | sb.Append($"Total Word Count: {SourceTotalWordCount}\n");
52 | sb.Append($"Symbols Count: {SourceSymbolCount}\n");
53 | return sb.ToString();
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/ImportEntry.cs:
--------------------------------------------------------------------------------
1 | using LocalizationTracker.Components;
2 | using LocalizationTracker.Tools;
3 |
4 | namespace LocalizationTracker.Data
5 | {
6 | public class ImportEntry
7 | {
8 | public ImportStatus Status { get; set; }
9 |
10 | public string Messages { get; set; }
11 |
12 | public string Key { get; set; }
13 |
14 | public string Path { get; set; }
15 |
16 | public string CurrentSource { get; set; }
17 |
18 | public string ImportSource { get; set; }
19 |
20 | public InlinesWrapper SourceDiffs { get; set; }
21 |
22 | public string CurrentTarget { get; set; }
23 |
24 | public string ImportTarget { get; set; }
25 |
26 | public InlinesWrapper TargetDiffs { get; set; }
27 |
28 | public string ImportResult { get; set; }
29 |
30 | public InlinesWrapper ResultDiffs { get; set; }
31 |
32 | public ImportEntry()
33 | {
34 | Status = ImportStatus.Ok;
35 | Messages = "";
36 | Key = "";
37 | Path = "";
38 | CurrentSource = "";
39 | ImportSource = "";
40 | SourceDiffs = new InlinesWrapper();
41 | CurrentTarget = "";
42 | ImportTarget = "";
43 | TargetDiffs = new InlinesWrapper();
44 | ImportResult = "";
45 | ResultDiffs = new InlinesWrapper();
46 | }
47 |
48 | public void AddMessage(string message)
49 | {
50 | if (Messages == "")
51 | {
52 | Messages = message;
53 | }
54 | else
55 | {
56 | Messages += "\n" + message;
57 | }
58 | }
59 |
60 | public void MakeDiffs()
61 | {
62 | SourceDiffs = Diff.MakeInlines(ImportSource, CurrentSource);
63 | TargetDiffs = Diff.MakeInlines(ImportTarget, CurrentTarget);
64 | ResultDiffs = Diff.MakeInlines(CurrentTarget, ImportResult);
65 | }
66 | }
67 |
68 | public enum ImportStatus
69 | {
70 | Ok,
71 | Warning,
72 | Error
73 | }
74 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/ImportFileResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace LocalizationTracker.Data
4 | {
5 | public class ImportResult
6 | {
7 | public ImportResult(string filePath, string langGroup, List entries)
8 | {
9 | LanguageGroup = langGroup;
10 | _importEntries = entries;
11 | _sourceFiles = new List() { filePath };
12 | }
13 |
14 | List _importEntries;
15 | List _sourceFiles;
16 |
17 | public readonly string LanguageGroup;
18 | public IReadOnlyList SourceFiles => _sourceFiles;
19 | public IReadOnlyList ImportEntries => _importEntries;
20 |
21 | public void UnionWith(ImportResult fileResult)
22 | {
23 | if (fileResult.LanguageGroup.Equals(LanguageGroup))
24 | {
25 | _sourceFiles.AddRange(fileResult._sourceFiles);
26 | _importEntries.AddRange(fileResult._importEntries);
27 | }
28 | }
29 |
30 | public override string ToString()
31 | => $"{LanguageGroup}. Строк: {ImportEntries.Count}";
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LocalizationTracker/Data/ImportRequestResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace LocalizationTracker.Data
4 | {
5 | public class ImportRequestResult
6 | {
7 | public ImportRequestResult()
8 | {
9 | _importResults = new Dictionary();
10 | }
11 |
12 | Dictionary _importResults;
13 |
14 | public IReadOnlyDictionary ImportResults => _importResults;
15 |
16 | public void AppendResult(ImportResult fileResult)
17 | {
18 | if (_importResults.TryGetValue(fileResult.LanguageGroup, out var existResult))
19 | existResult.UnionWith(fileResult);
20 | else _importResults.Add(fileResult.LanguageGroup, fileResult);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LocalizationTracker/Data/JsonLocalizationData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.Json.Serialization;
6 | using System.Threading.Tasks;
7 |
8 | namespace LocalizationTracker.Data
9 | {
10 | public class JsonLocalizationData
11 | {
12 | [JsonInclude]
13 | [JsonPropertyName("$id")]
14 | public string Id { get; set; }
15 |
16 | [JsonInclude]
17 | [JsonPropertyName("strings")]
18 | public Dictionary Strings { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Shared/LocaleTrait.cs:
--------------------------------------------------------------------------------
1 | namespace Kingmaker.Localization.Shared
2 | {
3 | public enum LocaleTrait
4 | {
5 | CheckMe,
6 | Translated,
7 | Final
8 | }
9 |
10 | public static class LocaleTraitExtensions
11 | {
12 | public static readonly LocaleTrait[] Values =
13 | {
14 | LocaleTrait.CheckMe,
15 | LocaleTrait.Translated,
16 | LocaleTrait.Final
17 | };
18 | }
19 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Shared/StringTrait.cs:
--------------------------------------------------------------------------------
1 | namespace Kingmaker.Localization.Shared
2 | {
3 | public enum StringTrait
4 | {
5 | Invalid,
6 | NotUsed
7 | }
8 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Shared/TraitData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json.Serialization;
3 | using JetBrains.Annotations;
4 |
5 | namespace Kingmaker.Localization.Shared;
6 |
7 | public class TraitData:ITraitData
8 | {
9 | [NotNull]
10 | [JsonInclude]
11 | [JsonPropertyName("trait")]
12 | public readonly string Trait = "";
13 |
14 | [JsonInclude]
15 | [JsonPropertyName("trait_date")]
16 | public DateTimeOffset ModificationDate;
17 |
18 | // locale text at the time trait was added
19 | [NotNull]
20 | [JsonInclude]
21 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
22 | [JsonPropertyName("locale_text")]
23 | public string LocaleText = "";
24 |
25 | [JsonConstructor]
26 | public TraitData([NotNull] string trait)
27 | {
28 | Trait = trait;
29 | }
30 |
31 | string ITraitData.Trait => Trait;
32 |
33 | DateTimeOffset ITraitData.ModificationDate
34 | {
35 | get => ModificationDate;
36 | set => ModificationDate=value;
37 | }
38 |
39 | string ITraitData.LocaleText
40 | {
41 | get => LocaleText;
42 | set => LocaleText=value;
43 | }
44 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/StringEntryComparison.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace LocalizationTracker.Data
5 | {
6 | public static class StringEntryComparison
7 | {
8 | public static int LimitLen(StringEntry x, string y)
9 | {
10 | if (x == null && y == null)
11 | return 0;
12 |
13 | if (x == null)
14 | return -1;
15 |
16 | if (y == null)
17 | return 1;
18 |
19 | int len = y.Length;
20 |
21 | var dirCompareResult = string.Compare(x.DirectoryRelativeToStringsFolder, 0, y, 0, len, StringComparison.InvariantCultureIgnoreCase);
22 | if(dirCompareResult == 0)
23 | {
24 | if (x.DirectoryRelativeToStringsFolder.Length == len)
25 | return 0;
26 | return x.DirectoryRelativeToStringsFolder[len-1]=='/' ? 0 : 1;
27 | }
28 | return dirCompareResult;
29 | }
30 |
31 | public static int Exact(StringEntry x, string y)
32 | {
33 | if (x == null && y == null)
34 | return 0;
35 |
36 | if (x == null)
37 | return -1;
38 |
39 | if (y == null)
40 | return 1;
41 |
42 | var dirCompareResult = string.Compare(x.DirectoryRelativeToStringsFolder, y, StringComparison.InvariantCultureIgnoreCase);
43 | return dirCompareResult;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Unreal/UnrealLocaleData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.Json.Serialization;
4 | using Kingmaker.Localization.Shared;
5 | using LocalizationTracker.Data.Wrappers;
6 | using LocalizationTracker.Utility;
7 |
8 | namespace LocalizationTracker.Data.Unreal;
9 |
10 | public class UnrealLocaleData : ILocaleData
11 | {
12 | [JsonInclude]
13 | [JsonPropertyName("locale")]
14 | public Locale m_Locale;
15 |
16 | [JsonInclude]
17 | [JsonPropertyName("sourceLocale")]
18 | public Locale? m_TranslatedFrom;
19 |
20 | [JsonInclude]
21 | [JsonPropertyName("text")]
22 | public string m_Text;
23 |
24 | [JsonInclude]
25 | [JsonPropertyName("modification_date")]
26 | public long m_ModificationDateInTicks;
27 |
28 | [JsonInclude]
29 | [JsonPropertyName("translation_date")]
30 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
31 | [JsonDefaultValue(0)]
32 | public long m_TranslationDateInTicks;
33 |
34 | [JsonInclude]
35 | [JsonPropertyName("original_text")]
36 | public string m_OriginalText;
37 |
38 | [JsonInclude]
39 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault), JsonDefaultValue("")]
40 | [JsonPropertyName("translation_comment")]
41 | public string m_Comment = "";
42 |
43 | [JsonInclude]
44 | [JsonPropertyName("traits")]
45 | public List m_Traits = new();
46 |
47 | [JsonIgnore]
48 | public Locale Locale => m_Locale;
49 |
50 | [JsonIgnore]
51 | public string Text
52 | {
53 | get => m_Text;
54 | set => m_Text = value;
55 | }
56 |
57 | [JsonIgnore]
58 | public DateTimeOffset ModificationDate
59 | {
60 | get => m_ModificationDateInTicks > 0
61 | ? DateTime.FromFileTimeUtc(m_ModificationDateInTicks - UnrealStringData.FileTimeOffset)
62 | : DateTimeOffset.MinValue;
63 | set => m_ModificationDateInTicks = value.UtcDateTime.ToFileTimeUtc() + UnrealStringData.FileTimeOffset;
64 | }
65 |
66 | [JsonIgnore]
67 | public Locale? TranslatedFrom
68 | {
69 | get => m_TranslatedFrom;
70 | set => m_TranslatedFrom = value;
71 | }
72 |
73 | [JsonIgnore]
74 | public DateTimeOffset? TranslationDate
75 | {
76 | get => m_TranslationDateInTicks > 0
77 | ? DateTime.FromFileTimeUtc(m_TranslationDateInTicks - UnrealStringData.FileTimeOffset)
78 | : null;
79 | set => m_TranslationDateInTicks = value?.UtcDateTime.ToFileTimeUtc() + UnrealStringData.FileTimeOffset ?? 0;
80 | }
81 |
82 | [JsonIgnore]
83 | public string OriginalText
84 | {
85 | get => m_OriginalText;
86 | set => m_OriginalText = value;
87 | }
88 |
89 | [JsonIgnore]
90 | public string TranslatedComment
91 | {
92 | get => m_Comment;
93 | set => m_Comment = value;
94 | }
95 |
96 | [JsonIgnore]
97 | public IEnumerable Traits => m_Traits ??= new List();
98 |
99 | public UnrealLocaleData(Locale locale)
100 | {
101 | m_Locale = locale;
102 | m_ModificationDateInTicks = DateTime.UtcNow.ToFileTimeUtc();
103 | }
104 | public void AddTraitInternal(ITraitData trait)
105 | {
106 | m_Traits ??= new List();
107 | m_Traits.Add((UnrealTraitData)trait);
108 | }
109 |
110 | public void RemoveTraitInternal(string trait)
111 | {
112 | m_Traits?.RemoveAll(t => t.Trait == trait);
113 | }
114 | [JsonConstructor]
115 | public UnrealLocaleData()
116 | {
117 | }
118 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Unreal/UnrealTraitData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json.Serialization;
3 | using Kingmaker.Localization.Shared;
4 |
5 | namespace LocalizationTracker.Data.Unreal;
6 |
7 | public class UnrealTraitData:ITraitData
8 | {
9 | [JsonInclude]
10 | [JsonPropertyName("trait")]
11 | public readonly string Trait = "";
12 |
13 | [JsonInclude]
14 | [JsonPropertyName("trait_date")]
15 | public long m_ModificationDateInTicks;
16 |
17 | // locale text at the time trait was added
18 | [JsonInclude]
19 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
20 | [JsonPropertyName("locale_text")]
21 | public string LocaleText = "";
22 |
23 | [JsonConstructor]
24 | public UnrealTraitData(string trait)
25 | {
26 | Trait = trait;
27 | }
28 |
29 | string ITraitData.Trait => Trait;
30 |
31 | [JsonIgnore]
32 | public DateTimeOffset ModificationDate
33 | {
34 | get => m_ModificationDateInTicks > 0
35 | ? DateTime.FromFileTimeUtc(m_ModificationDateInTicks - UnrealStringData.FileTimeOffset)
36 | : DateTimeOffset.MinValue;
37 | set => m_ModificationDateInTicks = value.UtcDateTime.ToFileTimeUtc() + UnrealStringData.FileTimeOffset;
38 | }
39 |
40 | string ITraitData.LocaleText
41 | {
42 | get => LocaleText;
43 | set => LocaleText=value;
44 | }
45 |
46 | // virtual traits get created when string is loaded and are never saved
47 | public bool IsVirtual { get; set; }
48 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Wrappers/ILocaleData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Kingmaker.Localization.Shared;
5 |
6 | namespace LocalizationTracker.Data.Wrappers;
7 |
8 | public interface ILocaleData
9 | {
10 | public Locale Locale { get; }
11 |
12 | public string Text { get; }
13 |
14 | public DateTimeOffset ModificationDate { get; }
15 |
16 | public Locale? TranslatedFrom { get; }
17 |
18 | public DateTimeOffset? TranslationDate { get; }
19 |
20 | public string OriginalText { get; set; }
21 |
22 | public string TranslatedComment { get; set; }
23 |
24 | public IEnumerable Traits { get; }
25 |
26 | public bool HasTrait(string trait)
27 | {
28 | return Traits != null && Traits.Any(t => t.Trait == trait);
29 | }
30 |
31 | void AddTraitInternal(ITraitData trait);
32 | void RemoveTraitInternal(string trait);
33 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Wrappers/IStringData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 | using Kingmaker.Localization.Shared;
6 | using LocalizationTracker.Data.Unreal;
7 | using static LocalizationTracker.Data.Unreal.UnrealStringData;
8 |
9 | namespace LocalizationTracker.Data.Wrappers;
10 |
11 | public interface IStringData
12 | {
13 | public Locale Source { get; }
14 |
15 | public string Key { get; }
16 |
17 | public string Comment { get; set; }
18 |
19 | public StringKind Kind { get; }
20 |
21 | public string Speaker { get; }
22 |
23 | public string SpeakerGender { get; }
24 |
25 | public ParentId ParentId { get; }
26 |
27 | public string OwnerLink { get; }
28 | public string StringPath { get; }
29 | public DateTimeOffset ModificationDate { get; }
30 |
31 | public string AttachmentPath { get; }
32 |
33 |
34 | public IEnumerable Languages { get; }
35 |
36 | public IEnumerable StringTraits { get; }
37 | bool ShouldCount { get; }
38 |
39 | bool UpdateText(Locale locale, string text, bool updateDate = true);
40 | void UpdateTranslation(Locale locale, string text, Locale translatedFrom, string originalText);
41 |
42 | void AddTraitInternal(ITraitData trait);
43 | void RemoveTraitInternal(string trait);
44 |
45 | public void AddTrait(Locale locale, string trait)
46 | {
47 | var localeData = GetLocale(locale);
48 | if (localeData == null)
49 | return;
50 |
51 | var traitData = localeData.Traits.FirstOrDefault(t => t.Trait == trait);
52 | if (traitData == null)
53 | {
54 | traitData = CreateTraitData(trait);
55 | localeData.AddTraitInternal(traitData);
56 | }
57 |
58 | traitData.LocaleText = localeData.Text;
59 | traitData.ModificationDate = DateTimeOffset.UtcNow;
60 | }
61 |
62 | ITraitData CreateTraitData(string trait, bool isVirtual=false);
63 |
64 | public void RemoveTrait(Locale locale, string trait)
65 | {
66 | var localeData = GetLocale(locale);
67 | localeData?.RemoveTraitInternal(trait);
68 | }
69 |
70 | public void AddStringTrait(string trait, bool isVirtual = false)
71 | {
72 | var traitData = StringTraits.FirstOrDefault(t => t.Trait == trait);
73 | if (traitData == null)
74 | {
75 | traitData = CreateTraitData(trait, isVirtual);
76 | AddTraitInternal(traitData);
77 | }
78 |
79 | traitData.ModificationDate = DateTimeOffset.UtcNow;
80 | }
81 |
82 | public void RemoveStringTrait(string trait)
83 | {
84 | RemoveTraitInternal(trait);
85 | }
86 |
87 | public bool HasStringTrait(string trait)
88 | {
89 | return StringTraits != null && StringTraits.Any(t => t.Trait == trait);
90 | }
91 | public string[] GetStringTraits()
92 | {
93 | if (StringTraits == null)
94 | return new string[] { };
95 |
96 | return StringTraits
97 | .Select(t => t.Trait)
98 | .ToArray();
99 | }
100 |
101 | public ILocaleData? GetLocale(Locale locale) => Languages.FirstOrDefault(s => s.Locale == locale);
102 |
103 | public ILocaleData EnsureLocale(Locale locale);
104 |
105 | public ITraitData? GetTraitData(Locale locale, string trait)
106 | {
107 | var localeData = GetLocale(locale);
108 | return localeData?.Traits?.FirstOrDefault(td => td.Trait == trait);
109 | }
110 |
111 | public ITraitData? GetStringTraitData(string trait)
112 | {
113 | return StringTraits?.FirstOrDefault(td => td.Trait == trait);
114 | }
115 |
116 | public string GetText(Locale locale)
117 | {
118 | var localeData = GetLocale(locale);
119 | return localeData?.Text ?? "";
120 | }
121 |
122 | public string[] GetTraits(Locale locale)
123 | {
124 | var localeData = GetLocale(locale);
125 | if (localeData == null)
126 | return new string[] { };
127 |
128 | if (localeData.Traits == null)
129 | return new string[] { };
130 |
131 | return localeData.Traits
132 | .Select(t => t.Trait)
133 | .ToArray();
134 | }
135 |
136 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Wrappers/ITraitData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Kingmaker.Localization.Shared;
4 |
5 | public interface ITraitData
6 | {
7 | string Trait { get; }
8 |
9 | DateTimeOffset ModificationDate { get; set; }
10 |
11 | // locale text at the time trait was added
12 | string LocaleText { get; set; }
13 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Wrappers/StringsArchive.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using System.Xml.Linq;
7 | using LocalizationTracker.Logic;
8 |
9 | namespace LocalizationTracker.Data.Wrappers;
10 |
11 | ///
12 | /// Wraps the whole set of strings on disk. For Unity this is a folder with string files, for Unreal this is one giant json
13 | ///
14 | public abstract class StringsArchive
15 | {
16 | public abstract List LoadAll(
17 | DirectoryInfo rootDir, StringEntry[]? skipUnmodified, CancellationToken ct);
18 | public abstract Task SaveAll();
19 | public abstract void Save(IStringData str);
20 | public abstract IStringData Reload(IStringData str);
21 | public abstract void Delete(IStringData str);
22 | public virtual bool HasUnsavedChanges => false; // Unity can save any string immediately, but Unreal has to track changes and save the whole list
23 |
24 | public virtual bool IsFileModified(IStringData str) => false;
25 |
26 | public event Action StringChanged= () => { };
27 |
28 | protected void RaiseStringChanged()
29 | {
30 | StringChanged();
31 | }
32 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Wrappers/StringsArchiveUnity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text.Json;
7 | using System.Text.RegularExpressions;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Kingmaker.Localization.Shared;
11 | using LocalizationTracker.Utility;
12 |
13 | namespace LocalizationTracker.Data.Wrappers;
14 |
15 | partial class StringsArchiveUnity : StringsArchive
16 | {
17 | public override List LoadAll(DirectoryInfo rootDir, StringEntry[]? skipUnmodified, CancellationToken ct)
18 | {
19 | HashSet files = rootDir.EnumerateFiles("*.json", SearchOption.AllDirectories)
20 | .AsParallel()
21 | .WithCancellation(ct)
22 | .Select(f => f.FullName)
23 | .ToHashSet();
24 |
25 | if (skipUnmodified!=null)
26 | {
27 | foreach (var se in skipUnmodified)
28 | files.Remove(se.AbsolutePath);
29 | }
30 |
31 | var newLoadedEntries = files
32 | .AsParallel()
33 | .WithCancellation(ct)
34 | .Select(LoadData);
35 |
36 | return newLoadedEntries.ToList();
37 | }
38 |
39 | private IStringData LoadData(string absolutePath)
40 | {
41 | if (absolutePath.Length >= 270)
42 | {
43 | throw new IOException($"The path is too long: {absolutePath}");
44 | }
45 |
46 | LocalizedStringData? Data = null;
47 |
48 | var modificationTime = File.GetLastWriteTime(absolutePath);
49 |
50 | var rootUri = new Uri(Path.GetFullPath(AppConfig.Instance.StringsFolder), UriKind.Absolute);
51 | var fileUri = new Uri(absolutePath, UriKind.Absolute);
52 |
53 | try
54 | {
55 | using (var sr = new StreamReader(absolutePath))
56 | {
57 | var text = sr.ReadToEnd();
58 | Data = JsonSerializer.Deserialize(text, JsonSerializerHelpers.JsonSerializerOptions);
59 | }
60 | }
61 | catch (Exception e)
62 | {
63 | throw new IOException($"Failed to parse json file: {absolutePath}", e);
64 | }
65 |
66 | if (Data == null)
67 | throw new IOException($"Failed to parse json file (data is null): {absolutePath}");
68 |
69 | Data.AbsolutePath = absolutePath;
70 | Data.StringPath = rootUri.MakeRelativeUri(fileUri).ToString();
71 | Data.ModificationDate = modificationTime;
72 |
73 | return Data;
74 | }
75 |
76 | public override Task SaveAll()
77 | {
78 | throw new System.NotImplementedException();
79 | }
80 |
81 | public override void Save(IStringData str)
82 | {
83 | var absolutePath = ((LocalizedStringData)str).AbsolutePath;
84 | if (!File.Exists(absolutePath))
85 | {
86 | throw new Exception("Строка больше не существует по старому пути. Нажмите Rescan или перезапустите локтулзу прежде, чем продолжить");
87 | }
88 |
89 | using (var sw = new StreamWriter(absolutePath))
90 | {
91 | var json = JsonSerializer.Serialize((LocalizedStringData)str, JsonSerializerHelpers.JsonSerializerOptions);
92 | var unescaped = JsonSerializerHelpers.UnescapeUnicodeSymbols(json);
93 | sw.Write(unescaped);
94 | }
95 | }
96 |
97 | public override IStringData Reload(IStringData str)
98 | {
99 | var absolutePath = ((LocalizedStringData)str).AbsolutePath;
100 | if (!File.Exists(absolutePath))
101 | {
102 | throw new Exception("Строка больше не существует по старому пути. Нажмите Rescan или перезапустите локтулзу прежде, чем продолжить");
103 | }
104 |
105 | return LoadData(absolutePath);
106 | }
107 |
108 | public override void Delete(IStringData str)
109 | {
110 | File.Delete(((LocalizedStringData)str).AbsolutePath);
111 | }
112 |
113 | public override bool IsFileModified(IStringData str)
114 | {
115 | var absolutePath = ((LocalizedStringData)str).AbsolutePath;
116 | return !File.Exists(absolutePath) || File.GetLastWriteTime(absolutePath) > str.ModificationDate;
117 | }
118 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Data/Wrappers/TextFixupHelper.cs:
--------------------------------------------------------------------------------
1 | using Kingmaker.Localization.Shared;
2 |
3 | namespace LocalizationTracker.Data.Wrappers;
4 |
5 | public class TextFixupHelper
6 | {
7 | public static string ApplyFixups(Locale locale, string text)
8 | {
9 | if (locale == Locale.ruRU || locale == Locale.enGB) // todo: this needs some sort of config
10 | {
11 | text = text.Replace("«", "\"");
12 | text = text.Replace("»", "\"");
13 | text = text.Replace("“", "\"");
14 | text = text.Replace("”", "\"");
15 | text = text.Replace("’", "'");
16 | }
17 |
18 | text = text.Replace(" - ", " — ");
19 | text = text.Replace("\r", "");
20 | //text = text.TrimEnd();
21 | while (text.Contains(" "))
22 | {
23 | text = text.Replace(" ", " ");
24 | }
25 | while (text.Contains(" \n"))
26 | {
27 | text = text.Replace(" \n", "\n");
28 | }
29 | while (text.Contains("\n "))
30 | {
31 | text = text.Replace("\n ", "\n");
32 | }
33 | while (text.Contains("\n\n"))
34 | {
35 | text = text.Replace("\n\n", "\n");
36 | }
37 |
38 | return text;
39 | }
40 | }
--------------------------------------------------------------------------------
/LocalizationTracker/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using StringsCollector;
2 | global using StringsCollector.Data;
3 | global using StringsCollector.Utility;
4 |
5 |
--------------------------------------------------------------------------------
/LocalizationTracker/LocalizationTracker.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net7.0-windows
4 | WinExe
5 | 1
6 | 1.0.0.%2a
7 | true
8 | true
9 | embedded
10 | win-x64
11 | true
12 |
13 |
14 | FolderProfile
15 | enable
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | SettingsSingleFileGenerator
42 |
43 |
44 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/ExcelExctentions.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml.Packaging;
2 | using DocumentFormat.OpenXml.Spreadsheet;
3 | using System.Linq;
4 |
5 | namespace LocalizationTracker.Excel
6 | {
7 | public static class ExcelExctentions
8 | {
9 | public static SharedStringTablePart GetSharedTable(this SpreadsheetDocument spreadSheet)
10 | {
11 | SharedStringTablePart sharedStringPart;
12 | if (spreadSheet.WorkbookPart.GetPartsOfType().Count() > 0)
13 | {
14 | sharedStringPart = spreadSheet.WorkbookPart.GetPartsOfType().First();
15 | }
16 | else
17 | {
18 | sharedStringPart = spreadSheet.WorkbookPart.AddNewPart();
19 | sharedStringPart.SharedStringTable = new SharedStringTable();
20 | }
21 |
22 | return sharedStringPart;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/ExcelStyles.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml;
2 | using DocumentFormat.OpenXml.Spreadsheet;
3 | using LocalizationTracker.Components;
4 | using LocalizationTracker.Logic.Excel;
5 | using System.Windows.Media;
6 |
7 | namespace LocalizationTracker.Excel
8 | {
9 | public class ExcelStyles: IExcelStyler
10 | {
11 | public virtual SharedStringItem DiffToSharedString(InlinesWrapper inlineWrapper)
12 | {
13 | SharedStringItem item = new SharedStringItem();
14 | foreach (var inline in inlineWrapper.InlineTemplates)
15 | {
16 | var run = inline;
17 | var xmlRun = new Run();
18 | if (!CheckRedStrike(run, xmlRun))
19 | CheckColor(run, xmlRun);
20 |
21 | var text = new Text() { Space = SpaceProcessingModeValues.Preserve };
22 | text.Text = run.Text;
23 | xmlRun.Append(text);
24 | item.Append(xmlRun);
25 | }
26 |
27 | return item;
28 | }
29 |
30 | protected virtual bool CheckRedStrike(InlineTemplate baseRun, Run xmlRun)
31 | {
32 | if (baseRun.Background == ColorUtility.Red)
33 | {
34 | xmlRun.Append(GetRedStriked());
35 | return true;
36 | }
37 |
38 | return false;
39 | }
40 |
41 | public virtual void CheckColor(InlineTemplate baseRun, Run xmlRun)
42 | {
43 | if (baseRun.Background != null)
44 | {
45 | var prop = GetColorProp(baseRun.Background == ColorUtility.Green ? ColorUtility.ExportDiffGreen : baseRun.Background.Value);
46 | xmlRun.Append(prop);
47 | }
48 | }
49 |
50 | protected virtual RunProperties GetRedStriked()
51 | {
52 | RunProperties runProperties1 = new RunProperties();
53 | var strike = new Strike();
54 | var color = ColorUtility.MediaColorToOXMLColor(ColorUtility.ExportDiffRed);
55 | runProperties1.Append(strike);
56 | runProperties1.Append(color);
57 |
58 | return runProperties1;
59 | }
60 |
61 | public virtual RunProperties GetColorProp(System.Windows.Media.Color color)
62 | {
63 | var colorProp = new RunProperties();
64 | var redColor = ColorUtility.MediaColorToOXMLColor(color);
65 | colorProp.Append(redColor);
66 |
67 | return colorProp;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/ExcelTagMismatchStyle.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml;
2 | using DocumentFormat.OpenXml.Spreadsheet;
3 | using LocalizationTracker.Components;
4 | using LocalizationTracker.Excel;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace LocalizationTracker.Logic.Excel
12 | {
13 | public class ExcelTagMismatchStyle : ExcelStyles
14 | {
15 | public override SharedStringItem DiffToSharedString(InlinesWrapper inlineWrapper)
16 | {
17 | SharedStringItem item = new SharedStringItem();
18 | foreach (var inline in inlineWrapper.InlineTemplates)
19 | {
20 | var run = inline;
21 | var xmlRun = new Run();
22 |
23 | CheckColor(run, xmlRun);
24 |
25 | var text = new Text() { Space = SpaceProcessingModeValues.Preserve };
26 | text.Text = run.Text;
27 | xmlRun.Append(text);
28 | item.Append(xmlRun);
29 | }
30 |
31 | return item;
32 | }
33 |
34 | public override void CheckColor(InlineTemplate baseRun, Run xmlRun)
35 | {
36 | if (baseRun.Foreground != null && baseRun.Background == null)
37 | {
38 | var foreProp = GetColorProp(baseRun.Foreground == ColorUtility.Green ? ColorUtility.ExportDiffGreen : baseRun.Foreground.Value);
39 | xmlRun.Append(foreProp);
40 | }
41 |
42 | if (baseRun.Background != null)
43 | {
44 | var backProp = GetColorProp(System.Windows.Media.Colors.Orange);
45 | xmlRun.Append(backProp);
46 | }
47 | }
48 |
49 | public override RunProperties GetColorProp(System.Windows.Media.Color color)
50 | {
51 | var colorProp = new RunProperties();
52 | var redColor = ColorUtility.MediaColorToOXMLColor(color);
53 |
54 | colorProp.Append(redColor);
55 |
56 | if (color == System.Windows.Media.Colors.Red || color == System.Windows.Media.Colors.Orange)
57 | {
58 | colorProp.Append(new Bold());
59 | }
60 |
61 | return colorProp;
62 | }
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/ExportWrapper.cs:
--------------------------------------------------------------------------------
1 | using LocalizationTracker.Data;
2 | using LocalizationTracker.Windows;
3 | using System;
4 | using System.Linq;
5 |
6 | namespace LocalizationTracker.Logic.Excel
7 | {
8 | public abstract class ExportWrapper
9 | {
10 | public ExportWrapper(IExporter exporter, ExportData data)
11 | {
12 | ExportResult = new ExportRequestResult(data.ExportParams.Source, string.Join(",", data.ExportParams.Target));
13 | _exporter = exporter;
14 | _data = data;
15 | }
16 |
17 | public ExportData _data;
18 |
19 | protected IExporter _exporter;
20 |
21 | protected IProgress _progressReporter;
22 |
23 | public ExportRequestResult ExportResult { get; private set; }
24 |
25 | public void Export(IProgress progress)
26 | {
27 | _progressReporter = progress;
28 | ExportInternal();
29 | }
30 |
31 | protected abstract void ExportInternal();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/Exporters/ExcelSourceUpdateExporter.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml.Packaging;
2 | using DocumentFormat.OpenXml.Spreadsheet;
3 | using StringsCollector.Data;
4 | using LocalizationTracker.Components;
5 | using LocalizationTracker.Data;
6 | using LocalizationTracker.Utility;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Text;
10 | namespace LocalizationTracker.Excel
11 | {
12 | public class ExcelSourceUpdateExporter : ExcelLocalizationExporter
13 | {
14 | ExcelStyles _style;
15 |
16 | public ExcelSourceUpdateExporter(ExcelStyles excelStyles)
17 | {
18 | _style = excelStyles;
19 | }
20 | public override ExportData PrepareDataToExport(ExportData data)
21 | {
22 | var prevSrcLocale = StringEntry.SourceLocale;
23 | var prevTrgLocale = StringEntry.TargetLocale;
24 |
25 | StringEntry.SourceLocale = Locale.TranslationSource;
26 | StringEntry.TargetLocale = data.ExportParams.Target.FirstOrDefault();
27 |
28 | Dictionary pairs = new Dictionary(data.Items.Length);
29 | var builder = new StringBuilder();
30 | foreach (var line in data.Items)
31 | {
32 | line.UpdateInlines();
33 | if (data.ExportParams.TagRemovalPolicy == TagRemovalPolicy.DeleteUpdatedTag)
34 | {
35 | System.Windows.Media.Color color = new System.Windows.Media.Color
36 | {
37 | R = 215,
38 | G = 227,
39 | B = 188,
40 | A = 255
41 | };
42 |
43 | var inlines = line.SourceLocaleEntry.Inlines.InlineTemplates.ToList();
44 |
45 | for (int i = 0; i < inlines.Count; i++)
46 | {
47 | if (inlines[i].Background == color || inlines[i].StrikeThrough == true)
48 | {
49 | var text = StringUtils.RemoveTags(inlines[i].Text, LocalizationExporter.TagsEncyclopedia);
50 | line.SourceLocaleEntry.Inlines.InlineTemplates[i].Text = text;
51 | }
52 | }
53 |
54 | }
55 |
56 | var source = _style.DiffToSharedString(line.SourceLocaleEntry.Inlines);
57 | var target = _style.DiffToSharedString(line.TargetLocaleEntry.Inlines);
58 | builder.Clear();
59 | AddClearText(builder, line.SourceLocaleEntry.Inlines);
60 | pairs.Add(line, new DiffPair(source, target, builder.ToString()));
61 | }
62 |
63 | StringEntry.SourceLocale = prevSrcLocale;
64 | StringEntry.TargetLocale = prevTrgLocale;
65 |
66 | return new DiffExportData(data, pairs);
67 | }
68 |
69 | void AddClearText(StringBuilder builder, InlinesWrapper inlineWrapper)
70 | {
71 | foreach (var inline in inlineWrapper.InlineTemplates)
72 | {
73 | builder.Append(inline.Text);
74 | }
75 | }
76 |
77 | protected override void AddTextCells(in ExportData data, SpreadsheetDocument doc, Row row, StringEntry s, ExportResults result)
78 | {
79 | if (data is DiffExportData exportData && exportData.DiffPairs.TryGetValue(s, out var pair))
80 | {
81 | row.Append(new Cell()
82 | .SetSharedText(doc.AddSharedString(pair.SourceLocale))
83 | .SetStyle(CellStyle.WordWrap));
84 | row.Append(new Cell()
85 | .SetSharedText(doc.AddSharedString(pair.TargetLocaleEntry))
86 | .SetStyle(CellStyle.WordWrap)
87 | );
88 |
89 | result.AddText(pair.SoruceText);
90 | }
91 | }
92 | }
93 |
94 | public class DiffExportData : ExportData
95 | {
96 | public DiffExportData(ExportData data, Dictionary diffPairs)
97 | {
98 | DistFolder = data.DistFolder;
99 | Items = data.Items;
100 | ExportParams = data.ExportParams;
101 | DiffPairs = diffPairs;
102 | }
103 |
104 | public Dictionary DiffPairs;
105 | }
106 |
107 | public class DiffPair
108 | {
109 | public DiffPair(SharedStringItem source, SharedStringItem target, string sourceText)
110 | {
111 | SourceLocale = source;
112 | TargetLocaleEntry = target;
113 | SoruceText = sourceText;
114 | }
115 |
116 | public string SoruceText { get; }
117 | public SharedStringItem SourceLocale { get; }
118 | public SharedStringItem TargetLocaleEntry { get; }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/Exporters/ExcelTagsMismathExporter.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml.Packaging;
2 | using DocumentFormat.OpenXml.Spreadsheet;
3 | using LocalizationTracker.Components;
4 | using LocalizationTracker.Data;
5 | using LocalizationTracker.Excel;
6 | using LocalizationTracker.Utility;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 |
13 | namespace LocalizationTracker.Logic.Excel.Exporters
14 | {
15 | public class ExcelTagsMismathExporter : ExcelLocalizationExporter
16 | {
17 | ExcelTagMismatchStyle _style;
18 | public ExcelTagsMismathExporter(ExcelTagMismatchStyle excelStyle)
19 | {
20 | _style = excelStyle;
21 | }
22 |
23 | public override ExportData PrepareDataToExport(ExportData data)
24 | {
25 | foreach (var line in data.Items)
26 | {
27 | TagsList.Compare(line.SourceLocaleEntry.TagsList, line.TargetLocaleEntry.TagsList);
28 | line.UpdateInlines();
29 |
30 | }
31 |
32 | return data;
33 | }
34 |
35 | protected override void AddTextCells(in ExportData data, SpreadsheetDocument doc, Row row, StringEntry entry, ExportResults result)
36 | {
37 | if (entry.SourceLocaleEntry.Inlines != null && entry.TargetLocaleEntry.Inlines != null)
38 | {
39 | var sourceSharedString = _style.DiffToSharedString(entry.SourceLocaleEntry.Inlines);
40 | var targetSharedString = _style.DiffToSharedString(entry.TargetLocaleEntry.Inlines);
41 |
42 | row.Append(new Cell()
43 | .SetSharedText(doc.AddSharedString(sourceSharedString))
44 | .SetStyle(LocalizationTracker.Excel.CellStyle.WordWrap));
45 |
46 | row.Append(new Cell()
47 | .SetSharedText(doc.AddSharedString(targetSharedString))
48 | .SetStyle(LocalizationTracker.Excel.CellStyle.WordWrap));
49 |
50 | StringBuilder builder = new StringBuilder();
51 | AddClearText(builder, entry.SourceLocaleEntry.Inlines);
52 | result.AddText(builder.ToString());
53 | }
54 | }
55 |
56 | private void AddClearText(StringBuilder builder, InlinesWrapper inlineWrapper)
57 | {
58 | foreach (var inline in inlineWrapper.InlineTemplates)
59 | {
60 | builder.Append(inline.Text);
61 | }
62 | }
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/IExcelStyler.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml.Spreadsheet;
2 | using LocalizationTracker.Components;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace LocalizationTracker.Logic.Excel
10 | {
11 | public interface IExcelStyler
12 | {
13 | public SharedStringItem DiffToSharedString(InlinesWrapper inlineWrapper);
14 | protected void CheckColor(InlineTemplate baseRun, Run xmlRun);
15 | protected RunProperties GetColorProp(System.Windows.Media.Color color);
16 |
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/SheetDataExtentions.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml;
2 | using DocumentFormat.OpenXml.Packaging;
3 | using DocumentFormat.OpenXml.Spreadsheet;
4 | using System.Linq;
5 |
6 | namespace LocalizationTracker.Excel
7 | {
8 | public static class SheetDataExtentions
9 | {
10 | public static Cell CreateCellWithSharedText(this Row row, string cellRef, int sharedTextIndex)
11 | {
12 | Cell cell = CreateOrGetCellInRow(cellRef, row);
13 | cell.CellValue = new CellValue(sharedTextIndex.ToString());
14 | cell.DataType = new EnumValue(CellValues.SharedString);
15 |
16 | return cell;
17 | }
18 |
19 | public static Cell CreateCellWithText(this Row row, string cellRef, string text)
20 | {
21 | Cell cell = CreateOrGetCellInRow(cellRef, row);
22 | cell.CellValue = new CellValue(text);
23 | cell.DataType = new EnumValue(CellValues.SharedString);
24 |
25 | return cell;
26 | }
27 |
28 | public static Cell CreateEmptyCell(this Row row, string cellRef)
29 | {
30 | Cell cell = CreateOrGetCellInRow(cellRef, row);
31 | return cell;
32 | }
33 |
34 | public static Row CreateOrGetRow(this SheetData sheetData, uint rowIndex)
35 | {
36 | /*if (!sheetData.Elements().TryFind(c => c.RowIndex.Value == rowIndex, out var row))
37 | {
38 | row = new Row() { RowIndex = rowIndex };
39 | sheetData.Append(row);
40 | }*/
41 |
42 | var row = new Row() { RowIndex = rowIndex };
43 | sheetData.Append(row);
44 | return row;
45 | }
46 |
47 | static Cell CreateOrGetCellInRow(string cellReference, Row row)
48 | {
49 | /*Cell refCell = null;
50 | foreach (var cell in row.Elements())
51 | {
52 | if (cell.CellReference.Value == cellReference)
53 | return cell;
54 | if (string.Compare(cell.CellReference.Value, cellReference, true) > 0)
55 | {
56 | refCell = cell;
57 | break;
58 | }
59 | }
60 |
61 | var newCell = new Cell() { CellReference = cellReference };
62 | row.InsertBefore(newCell, refCell);*/
63 |
64 | var newCell = new Cell() { CellReference = cellReference };
65 | row.InsertBefore(newCell, null);
66 | return newCell;
67 | }
68 |
69 | public static int NextRowID = 0;
70 |
71 | public static int GetIDForSharedText(this SharedStringTablePart shareStringPart, string text)
72 | {
73 | if (shareStringPart.SharedStringTable == null)
74 | {
75 | shareStringPart.SharedStringTable = new SharedStringTable();
76 | }
77 |
78 | //int count = shareStringPart.SharedStringTable.Elements().Count();
79 | shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new Text(text)));
80 | return NextRowID++;
81 | //return count;
82 |
83 | //int i = 0;
84 | //foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements())
85 | //{
86 | // if (item.InnerText == text)
87 | // {
88 | // return i;
89 | // }
90 |
91 | // i++;
92 | //}
93 |
94 | //shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new Text(text)));
95 | //return i;
96 | }
97 |
98 | public static int GetIDForSharedText(this SharedStringTablePart shareStringPart, SharedStringItem textItem)
99 | {
100 | if (shareStringPart.SharedStringTable == null)
101 | {
102 | shareStringPart.SharedStringTable = new SharedStringTable();
103 | }
104 |
105 | //int count = shareStringPart.SharedStringTable.Elements().Count();
106 | shareStringPart.SharedStringTable.AppendChild(textItem);
107 | return NextRowID++;
108 | //return count;
109 |
110 | //int i = 0;
111 | //foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements())
112 | //{
113 | // if (item.InnerText == textItem.InnerText)
114 | // {
115 | // return i;
116 | // }
117 |
118 | // i++;
119 | //}
120 |
121 | //shareStringPart.SharedStringTable.AppendChild(textItem);
122 | //return i;
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/SheetWrapper.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml;
2 | using DocumentFormat.OpenXml.Packaging;
3 | using DocumentFormat.OpenXml.Spreadsheet;
4 | using System;
5 |
6 | namespace LocalizationTracker.Excel
7 | {
8 | class SheetWrapper
9 | {
10 | public SheetWrapper(SheetData sheet, SharedStringTablePart sharedStrings)
11 | {
12 | _worksheet = sheet;
13 | _sharedStrings = sharedStrings;
14 | IsReadyToWork = _worksheet != null && _sharedStrings != null;
15 | }
16 |
17 | #region Fields
18 |
19 | private readonly SheetData _worksheet;
20 |
21 | private readonly SharedStringTablePart _sharedStrings;
22 |
23 | uint _rowIndex = 0;
24 |
25 | int _columnIndex = 0;
26 |
27 | Row _currentRow;
28 |
29 | #endregion Fields
30 | #region Properties
31 |
32 | public bool IsReadyToWork { get; }
33 |
34 | public OpenXmlElementList ChildElements => _worksheet.ChildElements;
35 |
36 | #endregion Properties
37 | #region Methods
38 |
39 | public void NewRow()
40 | {
41 | _rowIndex++;
42 | _columnIndex = 0;
43 | _currentRow = _worksheet.CreateOrGetRow(_rowIndex);
44 | }
45 |
46 | public void AddCell(string text, CellStyle cellStyle)
47 | {
48 | var refID = _sharedStrings.GetIDForSharedText(text);
49 | AddCell(refID, cellStyle);
50 | }
51 |
52 | public void AddCell(SharedStringItem stringItem, CellStyle cellStyle)
53 | {
54 | var refID = _sharedStrings.GetIDForSharedText(stringItem);
55 | AddCell(refID, cellStyle);
56 | }
57 |
58 | void AddCell(int refID, CellStyle cellStyle)
59 | {
60 | var cellRef = GetCellRef();
61 | var cell = _currentRow.CreateCellWithSharedText(cellRef, refID);
62 | cell.StyleIndex = new UInt32Value((uint)cellStyle);
63 | }
64 |
65 | string GetCellRef()
66 | {
67 | if (_currentRow == null)
68 | throw new Exception("Попытка вставить ячейку в несуществующий столбец");
69 |
70 | return SpreadsheetHelper.GetCellRef(_rowIndex, _columnIndex++);
71 | }
72 |
73 | #endregion Methods
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/WorkbookWrapper.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml;
2 | using DocumentFormat.OpenXml.Packaging;
3 | using DocumentFormat.OpenXml.Spreadsheet;
4 | using LocalizationTracker.Utility;
5 | using System;
6 | using System.Linq;
7 |
8 | namespace LocalizationTracker.Excel
9 | {
10 | public class WorkbookWrapper : IDisposable
11 | {
12 | public WorkbookWrapper(string path, WrapperMod wrapperMod, ColumnSettings[]? columnDatas = null)
13 | {
14 | _path = path;
15 | _sheet = SpreadsheetHelper.GetSpreadsheet(path, wrapperMod, columnDatas);
16 | _wrapperMod = wrapperMod;
17 | if (_sheet != null && !string.IsNullOrEmpty(_path))
18 | {
19 | try
20 | {
21 | SharedString = _sheet.GetSharedTable();
22 | _worksheet = _sheet.WorkbookPart.WorksheetParts.First().Worksheet;
23 | var sheetData = _worksheet.GetFirstChild();
24 | _currentSheet = new SheetWrapper(sheetData, SharedString);
25 |
26 | IsReadyToWork = SharedString != null && _worksheet != null && _currentSheet.IsReadyToWork;
27 | }
28 | catch
29 | {
30 | _sheet.Dispose();
31 | IsReadyToWork = false;
32 | }
33 | }
34 | }
35 |
36 | #region Fields
37 |
38 | private readonly WrapperMod _wrapperMod;
39 |
40 | private readonly string _path;
41 |
42 | SpreadsheetDocument? _sheet;
43 | private readonly Worksheet _worksheet;
44 |
45 | private readonly SheetWrapper _currentSheet;
46 |
47 | #endregion Fields
48 | #region Properties
49 |
50 | public OpenXmlElementList ChildElementsOfCurrentSheet => _currentSheet?.ChildElements;
51 |
52 | public SharedStringTablePart SharedString { get; }
53 |
54 | public bool IsReadyToWork { get; }
55 |
56 | public SpreadsheetDocument? Document => _sheet;
57 |
58 | #endregion Properties
59 | #region Methods
60 |
61 | public void NewRow()
62 | => _currentSheet.NewRow();
63 |
64 | public void AddCell(string text, CellStyle cellStyle = CellStyle.Base)
65 | => _currentSheet.AddCell(text, cellStyle);
66 |
67 | public void AddCell(SharedStringItem textItem, CellStyle cellStyle = CellStyle.Base)
68 | => _currentSheet.AddCell(textItem, cellStyle);
69 |
70 | public void Save()
71 | {
72 | SharedString.SharedStringTable?.Save();
73 | _sheet.WorkbookPart.Workbook.RemoveAllChildren();
74 | _sheet.WorkbookPart.Workbook.Save();
75 | _worksheet.Save();
76 | _sheet.Save();
77 | }
78 |
79 | void IDisposable.Dispose()
80 | {
81 | if (IsReadyToWork)
82 | {
83 | _sheet.Dispose();
84 | if (_wrapperMod != WrapperMod.Import)
85 | RepairKit.RemovePrefixs(_path);
86 | }
87 | }
88 |
89 | #endregion Methods
90 | }
91 |
92 | public enum WrapperMod
93 | {
94 | Import,
95 | ExportAdd,
96 | ExportNewFile,
97 | ExportTemplateFile
98 | }
99 |
100 | public enum CellStyle
101 | {
102 | Default = 0,
103 | Base = 1,
104 | WordWrap = 2,
105 | YellowSolid = 3,
106 | RedSolid = 4,
107 | GreenSolid = 5,
108 | Context = 6,
109 | GraySolid = 7
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/Wrappers/ExportHierarchyWrapper.cs:
--------------------------------------------------------------------------------
1 | using LocalizationTracker.Data;
2 | using LocalizationTracker.Excel;
3 | using LocalizationTracker.Windows;
4 | using System.Collections.Generic;
5 | using System.IO;
6 |
7 | namespace LocalizationTracker.Logic.Excel
8 | {
9 | class ExportHierarchyWrapper : ExportWrapper
10 | {
11 | public ExportHierarchyWrapper(IExporter exporter, ExportData data)
12 | : base(exporter, data) { }
13 |
14 | int _currentFile;
15 | int _targetFiles;
16 | float _percentPerFile;
17 |
18 | protected override void ExportInternal()
19 | {
20 | var exportEntries = SplitIntoFolders(_data.Items);
21 | _currentFile = 0;
22 | _targetFiles = exportEntries.Length;
23 | _percentPerFile = 1f / _targetFiles;
24 |
25 | var baseRoot = _data.DistFolder;
26 | foreach (var entry in exportEntries)
27 | {
28 | _currentFile++;
29 | var path = GetExportPath(baseRoot, entry.EntryName, _data.ExportParams.Source.Code, _data.ExportParams.Target[0].Code);
30 | _data.DistFolder = path;
31 | _data.Items = entry.Strings;
32 | var result = _exporter.Export(_data, SendProgress);
33 | ExportResult.Append(result);
34 | }
35 | }
36 |
37 | private void SendProgress(int current, int target)
38 | {
39 | var doneFilesProgress = (_currentFile - 1) * _percentPerFile;
40 | var currentProgress = doneFilesProgress + _percentPerFile * current / target;
41 | var progressText = $"Export file: {_currentFile}/{_targetFiles}\nExport line: {current}/{target}";
42 | _progressReporter.Report(new ProgressState(progressText, currentProgress));
43 | }
44 |
45 | string GetExportPath(string rootPath, string entryFolder, string fromLang, string toLang, bool removeOldFile = true)
46 | {
47 | var entryName = Path.GetFileName(entryFolder);
48 | var folderPath = Path.Combine(rootPath, Path.GetDirectoryName(entryFolder));
49 | if (!Directory.Exists(folderPath))
50 | Directory.CreateDirectory(folderPath);
51 |
52 | var fileName = $"{entryName}_{fromLang}_{toLang}.xlsx";
53 | var filePath = Path.Combine(folderPath, fileName);
54 | if (removeOldFile && File.Exists(filePath))
55 | File.Delete(filePath);
56 |
57 | return filePath;
58 | }
59 |
60 | ExportEntry[] SplitIntoFolders(StringEntry[] selectedStrings)
61 | {
62 | var sortedEntries = GetSortedStrings(selectedStrings);
63 | List exportEntries = new List();
64 | foreach (var pair in sortedEntries)
65 | {
66 | if (pair.Value.Count > 0)
67 | {
68 | var entry = new ExportEntry(pair.Key, pair.Value.ToArray());
69 | exportEntries.Add(entry);
70 | }
71 | }
72 |
73 | return exportEntries.ToArray();
74 | }
75 |
76 | static Dictionary> GetSortedStrings(StringEntry[] selectedStrings)
77 | {
78 | Dictionary> _folderSortedEntries = new Dictionary>();
79 | foreach (var entry in selectedStrings)
80 | {
81 | var strPath = entry.PathRelativeToStringsFolder;
82 | var folder = Path.GetDirectoryName(strPath);
83 | if (!_folderSortedEntries.TryGetValue(folder, out var list))
84 | {
85 | list = new List();
86 | _folderSortedEntries.Add(folder, list);
87 | }
88 |
89 | list.Add(entry);
90 | }
91 |
92 |
93 | return _folderSortedEntries;
94 | }
95 | }
96 |
97 | public class ExportEntry
98 | {
99 | public ExportEntry(string name, StringEntry[] strings)
100 | {
101 | EntryName = name;
102 | Strings = strings;
103 | }
104 |
105 | public string EntryName { get; }
106 | public StringEntry[] Strings { get; }
107 | }
108 |
109 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/Excel/Wrappers/ExportSingleFileWrapper.cs:
--------------------------------------------------------------------------------
1 | using LocalizationTracker.Windows;
2 |
3 | namespace LocalizationTracker.Logic.Excel.Wrappers
4 | {
5 | class ExportSingleFileWrapper : ExportWrapper
6 | {
7 | public ExportSingleFileWrapper(IExporter exporter, ExportData data)
8 | : base(exporter, data) { }
9 |
10 | protected override void ExportInternal()
11 | {
12 | var result = _exporter.Export(_data, SendProgress);
13 | ExportResult.Append(result);
14 | }
15 |
16 | private void SendProgress(int current, int target)
17 | {
18 | var progress = $"Export line: {current}/{target}";
19 | _progressReporter.Report(new ProgressState(progress, (float)current / target));
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/ImportDiffExporter.cs:
--------------------------------------------------------------------------------
1 | using LocalizationTracker.Data;
2 | using LocalizationTracker.Excel;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using wpf4gp;
7 |
8 | namespace LocalizationTracker.Logic
9 | {
10 | static class ImportDiffExporter
11 | {
12 | static ExcelStyles ExcelStyles = new ExcelStyles();
13 |
14 | static ColumnSettings[] _columnSettings = new[]
15 | {
16 | new ColumnSettings()
17 | {
18 | MinIndex = 5,
19 | MaxIndex = 7,
20 | Width = 60
21 | }
22 | };
23 |
24 | public static void SaveResultAsFile(ImportResult result)
25 | {
26 | if (TryGetPath(true, out var rootPath))
27 | {
28 | SaveResultAsFile(result, rootPath);
29 | }
30 | }
31 |
32 | public static void SaveResultsAsFile(ICollection results)
33 | {
34 | if (results.Count == 0)
35 | return;
36 |
37 | if (TryGetPath(results.Count == 1, out var rootPath))
38 | {
39 | foreach (var result in results)
40 | {
41 | SaveResultAsFile(result, rootPath);
42 | }
43 | }
44 | }
45 |
46 | static void SaveResultAsFile(ImportResult result, string rootPath)
47 | {
48 | try
49 | {
50 | var path = Path.Combine(rootPath, $"{result.LanguageGroup.Replace(':', '_')}_diffs.xlsx");
51 | ExportResults(path, result);
52 | }
53 | catch (Exception exc)
54 | {
55 | exc.ShowMessageBox();
56 | }
57 | }
58 |
59 | static void ExportResults(string path, ImportResult result)
60 | {
61 | using (var wrapper = new WorkbookWrapper(path, WrapperMod.ExportNewFile, _columnSettings))
62 | {
63 | SheetDataExtentions.NextRowID = 0;
64 |
65 | AddHeader(wrapper);
66 | var entries = result.ImportEntries;
67 | for (uint i = 0; i < entries.Count; i++)
68 | {
69 | var pair = entries[(int)i];
70 | AddImportEntry(pair, wrapper);
71 | }
72 |
73 | wrapper.Save();
74 | }
75 | }
76 |
77 | static void AddHeader(WorkbookWrapper wrapper)
78 | {
79 | wrapper.NewRow();
80 |
81 | wrapper.AddCell("Key");
82 | wrapper.AddCell("Path");
83 | wrapper.AddCell("Status");
84 | wrapper.AddCell("Messages");
85 | wrapper.AddCell("Source");
86 | wrapper.AddCell("Old Target");
87 | wrapper.AddCell("Result");
88 | }
89 |
90 | static void AddImportEntry(ImportEntry entry, WorkbookWrapper wrapper)
91 | {
92 | wrapper.NewRow();
93 |
94 | wrapper.AddCell(entry.Key);
95 | wrapper.AddCell(entry.Path);
96 | wrapper.AddCell(entry.Status.ToString(), GetStyleForStatus(entry.Status));
97 | wrapper.AddCell(entry.Messages);
98 | wrapper.AddCell(ExcelStyles.DiffToSharedString(entry.SourceDiffs), CellStyle.WordWrap);
99 | wrapper.AddCell(ExcelStyles.DiffToSharedString(entry.TargetDiffs), CellStyle.WordWrap);
100 | wrapper.AddCell(ExcelStyles.DiffToSharedString(entry.ResultDiffs), CellStyle.WordWrap);
101 | }
102 |
103 | static CellStyle GetStyleForStatus(ImportStatus status)
104 | {
105 | switch (status)
106 | {
107 | case ImportStatus.Ok:
108 | return CellStyle.GreenSolid;
109 |
110 | case ImportStatus.Warning:
111 | return CellStyle.YellowSolid;
112 |
113 | case ImportStatus.Error:
114 | return CellStyle.RedSolid;
115 |
116 | default:
117 | return CellStyle.Base;
118 | }
119 | }
120 |
121 | static bool TryGetPath(bool isSingle, out string path)
122 | {
123 | using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
124 | {
125 | dlg.SelectedPath = Directory.GetCurrentDirectory();
126 | dlg.Description = $"Укажите папку для {(!isSingle ? "файлов" : "файла")} сравнения";
127 | if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
128 | {
129 | path = dlg.SelectedPath;
130 | return true;
131 | }
132 | }
133 |
134 | path = string.Empty;
135 | return false;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/JsonImporter.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml.Spreadsheet;
2 | using LocalizationTracker.Data;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text.Json;
7 |
8 | namespace LocalizationTracker.Logic
9 | {
10 | public class JsonImporter : IImporter
11 | {
12 | private Locale m_TargetLocale;
13 |
14 | private string m_Key;
15 |
16 | private string m_Result;
17 |
18 | public ImportResult Import(string fileName)
19 | {
20 | var results = new List();
21 | JsonLocalizationData strings;
22 |
23 | m_TargetLocale = Locale.Values.Where(w => w.Code == Path.GetFileNameWithoutExtension(fileName)).FirstOrDefault();
24 |
25 | using (var sr = new StreamReader(fileName))
26 | {
27 | var text = sr.ReadToEnd();
28 | strings = JsonSerializer.Deserialize(text);
29 | }
30 |
31 | foreach (var str in strings.Strings)
32 | {
33 | var result = ImportString(str);
34 |
35 | if (result != null && !string.IsNullOrEmpty(result.Key))
36 | {
37 | result.MakeDiffs();
38 | results.Add(result);
39 | }
40 | }
41 |
42 | var importResult = new ImportResult(fileName, $"\"\":{m_TargetLocale.Code}", results);
43 | return importResult;
44 | }
45 |
46 | private ImportEntry ImportString(KeyValuePair str)
47 | {
48 | var result = new ImportEntry();
49 | StringEntry se = null;
50 |
51 | if (!string.IsNullOrEmpty(str.Key))
52 | {
53 | result.Key = str.Key;
54 | if (StringManager.StringsByKey.TryGetValue(result.Key, out se))
55 | {
56 | se.Reload();
57 | result.Path = se.AbsolutePath;
58 | result.ImportResult = se.Data.GetText(m_TargetLocale);
59 | }
60 | }
61 |
62 | result.ImportSource = "";
63 | result.ImportTarget = "";
64 |
65 | if (!string.IsNullOrEmpty(str.Value))
66 | {
67 | result.ImportResult = str.Value;
68 | }
69 |
70 | if (string.IsNullOrEmpty(str.Key))
71 | {
72 | result.Status = ImportStatus.Error;
73 | result.AddMessage("String key is empty");
74 | }
75 | else if (se == null)
76 | {
77 | result.Status = ImportStatus.Error;
78 | result.AddMessage("Key doesn't exist in database.");
79 | }
80 |
81 | if (string.IsNullOrEmpty(str.Value))
82 | {
83 | result.Status = ImportStatus.Error;
84 | result.AddMessage("Imported string is empty");
85 | }
86 |
87 | if (se == null || result.Status == ImportStatus.Error)
88 | {
89 | result.Status = ImportStatus.Error;
90 | return result;
91 | }
92 |
93 | se.Data.UpdateTranslation(m_TargetLocale, result.ImportResult, null, result.ImportSource);
94 |
95 | se.Save();
96 |
97 | return result;
98 | }
99 | }
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/KnownTraits.cs:
--------------------------------------------------------------------------------
1 | namespace LocalizationTracker.Logic;
2 |
3 | public class KnownTraits
4 | {
5 | public static readonly string Draft = "Draft";
6 | public static readonly string ToTranslate = "ToTranslate";
7 | public static readonly string SentToTr = "SentToTr";
8 | public static readonly string Translated = "Translated";
9 | public static readonly string SentToEd = "SentToEd";
10 | public static readonly string Edited = "Edited";
11 | public static readonly string MajorChange = "MajorChange";
12 | public static readonly string EditorChange = "ChangedInEngine";
13 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/LocalizationDiffExporter.cs:
--------------------------------------------------------------------------------
1 | using LocalizationTracker.Data;
2 | using LocalizationTracker.Excel;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using wpf4gp;
7 |
8 | namespace LocalizationTracker.Logic
9 | {
10 | static class LocalizationDiffExporter
11 | {
12 | static ColumnSettings[] _columnSettings = new[]
13 | {
14 | new ColumnSettings()
15 | {
16 | MinIndex = 5,
17 | MaxIndex = 7,
18 | Width = 60
19 | }
20 | };
21 |
22 | public static void SaveResultAsFile(ImportResult result)
23 | {
24 | if (TryGetPath(true, out var rootPath))
25 | {
26 | SaveResultAsFile(result, rootPath);
27 | }
28 | }
29 |
30 | public static void SaveResultsAsFile(ICollection results)
31 | {
32 | if (results.Count == 0)
33 | return;
34 |
35 | if (TryGetPath(results.Count == 1, out var rootPath))
36 | {
37 | foreach (var result in results)
38 | {
39 | SaveResultAsFile(result, rootPath);
40 | }
41 | }
42 | }
43 |
44 | static void SaveResultAsFile(ImportResult result, string rootPath)
45 | {
46 | try
47 | {
48 | var path = Path.Combine(rootPath, $"{result.LanguageGroup.Replace(':', '_')}_diffs.xlsx");
49 | ExportResults(path, result);
50 | }
51 | catch (Exception exc)
52 | {
53 | exc.ShowMessageBox();
54 | }
55 | }
56 |
57 | static void ExportResults(string path, ImportResult result)
58 | {
59 | using (var wrapper = new WorkbookWrapper(path, WrapperMod.ExportNewFile, _columnSettings))
60 | {
61 | SheetDataExtentions.NextRowID = 0;
62 |
63 | AddHeader(wrapper);
64 | var entries = result.ImportEntries;
65 | for (uint i = 0; i < entries.Count; i++)
66 | {
67 | var pair = entries[(int)i];
68 | AddImportEntry(pair, wrapper);
69 | }
70 |
71 | wrapper.Save();
72 | }
73 | }
74 |
75 | static void AddHeader(WorkbookWrapper wrapper)
76 | {
77 | wrapper.NewRow();
78 |
79 | wrapper.AddCell("Key");
80 | wrapper.AddCell("Path");
81 | wrapper.AddCell("Status");
82 | wrapper.AddCell("Messages");
83 | wrapper.AddCell("Source");
84 | wrapper.AddCell("Old Target");
85 | wrapper.AddCell("Result");
86 | }
87 |
88 | static void AddImportEntry(ImportEntry entry, WorkbookWrapper wrapper)
89 | {
90 | wrapper.NewRow();
91 |
92 | wrapper.AddCell(entry.Key);
93 | wrapper.AddCell(entry.Path);
94 | wrapper.AddCell(entry.Status.ToString(), GetStyleForStatus(entry.Status));
95 | wrapper.AddCell(entry.Messages);
96 | wrapper.AddCell(ExcelStyles.DiffToSharedString(entry.SourceDiffs), CellStyle.WordWrap);
97 | wrapper.AddCell(ExcelStyles.DiffToSharedString(entry.TargetDiffs), CellStyle.WordWrap);
98 | wrapper.AddCell(ExcelStyles.DiffToSharedString(entry.ResultDiffs), CellStyle.WordWrap);
99 | }
100 |
101 | static CellStyle GetStyleForStatus(ImportStatus status)
102 | {
103 | switch (status)
104 | {
105 | case ImportStatus.Ok:
106 | return CellStyle.GreenSolid;
107 |
108 | case ImportStatus.Warning:
109 | return CellStyle.YellowSolid;
110 |
111 | case ImportStatus.Error:
112 | return CellStyle.RedSolid;
113 |
114 | default:
115 | return CellStyle.Base;
116 | }
117 | }
118 |
119 | static bool TryGetPath(bool isSingle, out string path)
120 | {
121 | using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
122 | {
123 | dlg.SelectedPath = Directory.GetCurrentDirectory();
124 | dlg.Description = $"Укажите папку для {(!isSingle ? "файлов" : "файла")} сравнения";
125 | if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
126 | {
127 | path = dlg.SelectedPath;
128 | return true;
129 | }
130 | }
131 |
132 | path = string.Empty;
133 | return false;
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/LocalizationTracker/Logic/OpenOffice/OpenOfficeXmlHelper.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 |
6 | namespace LocalizationTracker.OpenOffice
7 | {
8 | public static class OpenOfficeXmlHelper
9 | {
10 | public static XmlNode GetTable(this XmlDocument document)
11 | => document
12 | ["office:document-content"]?
13 | ["office:body"]?
14 | ["office:spreadsheet"]?
15 | ["table:table"];
16 |
17 | public static IEnumerable GetRows(this XmlNode table)
18 | => table.OfType().Where(n => n.Name == "table:table-row");
19 |
20 | public static IEnumerable GetCells(this XmlNode row)
21 | => row.OfType().Where(n => n.Name == "table:table-cell");
22 |
23 | public static XmlNode GetText(this XmlNode cell)
24 | => cell.OfType().FirstOrDefault(i => i.Name == "text:p");
25 |
26 | [CanBeNull]
27 | public static string ReadCellValueAt(this IEnumerable source, int i)
28 | {
29 | int ci = 0;
30 | foreach (var cell in source)
31 | {
32 | var repeatAttribute = cell.Attributes?.GetNamedItem("table:number-columns-repeated");
33 | if (repeatAttribute != null &&
34 | int.TryParse(repeatAttribute.InnerText, out int repeat) &&
35 | repeat > 0)
36 | {
37 | ci += repeat;
38 | }
39 | else
40 | {
41 | ci += 1;
42 | }
43 |
44 | if (ci > i)
45 | {
46 | return cell.GetText()?.InnerText ?? "";
47 | }
48 | }
49 |
50 | return null;
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Properties/PublishProfiles/FolderProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | Release
9 | publish\win-x64\
10 | Any CPU
11 | net7.0-windows
12 | win-x64
13 | true
14 | false
15 |
16 |
--------------------------------------------------------------------------------
/LocalizationTracker/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace LocalizationTracker.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LocalizationTracker.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/LocalizationTracker/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | template.xlsx
7 |
8 |
9 | ../Project Kingmaker/Strings
10 |
11 |
12 | ../Project Kingmaker/Assets
13 |
14 |
15 | 100
16 |
17 |
18 | 100
19 |
20 |
21 | 1500
22 |
23 |
24 | 800
25 |
26 |
27 | 50
28 |
29 |
30 | 50
31 |
32 |
33 | 800
34 |
35 |
36 | 1000
37 |
38 |
39 | Normal
40 |
41 |
42 | Normal
43 |
44 |
45 | Normal
46 |
47 |
48 | 200
49 |
50 |
51 | 200
52 |
53 |
54 | 600
55 |
56 |
57 | Normal
58 |
59 |
60 | 1500
61 |
62 |
63 | 500
64 |
65 |
66 | 500
67 |
68 |
69 | 300
70 |
71 |
72 | 150
73 |
74 |
75 |
76 |
77 |
78 | template.ods
79 |
80 |
81 |
--------------------------------------------------------------------------------
/LocalizationTracker/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "LocalizationTracker": {
4 | "commandName": "Project",
5 | "commandLineArgs": "config.json",
6 | "workingDirectory": "d:\\pf2-production-2\\Localization\\"
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/Diff.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Documents;
6 | using System.Windows.Media;
7 | using DiffMatchPatch;
8 | using JetBrains.Annotations;
9 | using LocalizationTracker.Components;
10 |
11 | namespace LocalizationTracker.Tools
12 | {
13 | public static class Diff
14 | {
15 | [NotNull]
16 | public static InlinesWrapper MakeInlines(string before, string after)
17 | {
18 | if (before == null)
19 | before = "";
20 | if (after == null)
21 | after = "";
22 |
23 | List diffs;
24 | try
25 | {
26 | diffs = DiffMatchPatchModule.Default.DiffMain(before, after);
27 | DiffMatchPatchModule.Default.DiffCleanupSemantic(diffs);
28 | DiffMatchPatchModule.Default.DiffCleanupEfficiency(diffs);
29 | }
30 | catch (Exception x)
31 | {
32 | throw new Exception($"Error when diffing strings:\n before <{before}>\nafter <{after}>", x);
33 | }
34 |
35 | var inlines = diffs
36 | .Select(MakeInline)
37 | .ToArray();
38 |
39 | return new InlinesWrapper(inlines);
40 | }
41 |
42 | [NotNull]
43 | private static InlineTemplate MakeInline(DiffMatchPatch.Diff diff)
44 | {
45 | var run = new InlineTemplate(diff.Text);
46 | if (Equals(diff.Operation, Operation.Delete))
47 | {
48 | run.Background = ColorUtility.Red;
49 | run.StrikeThrough = true;
50 | run.InlineType = InlineType.DiffDelete;
51 | }
52 | else if (Equals(diff.Operation, Operation.Insert))
53 | {
54 | run.Background = ColorUtility.Green;
55 | run.InlineType = InlineType.DiffInsert;
56 | }
57 | return run;
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/FilterFits.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows;
4 | using System.Windows.Media;
5 | using JetBrains.Annotations;
6 | using LocalizationTracker.Components;
7 | using LocalizationTracker.Logic;
8 | using LocalizationTracker.Windows;
9 |
10 | namespace LocalizationTracker.Tools;
11 |
12 | public class FilterFits
13 | {
14 | [NotNull]
15 | public static InlinesWrapper MakeInlines(string text, Color? selectedColor = null)
16 | {
17 | return MakeInlines(new[] { new InlineTemplate(text) }, selectedColor);
18 | }
19 |
20 | public static InlinesWrapper MakeInlines(InlineTemplate[] inlineTemplates, Color? selectedColor = null)
21 | {
22 | if (string.IsNullOrWhiteSpace(StringsFilter.Filter.Text)) return new InlinesWrapper(inlineTemplates);
23 |
24 | var result = new List();
25 |
26 | var searchText = StringsFilter.Filter.Text;
27 |
28 | if (StringsFilter.Filter.IgnoreCase == true)
29 | {
30 | searchText = searchText.ToLower();
31 | }
32 |
33 | int index;
34 |
35 | Color? filterColor = selectedColor ?? Brushes.LightBlue.Color;
36 |
37 | foreach (var i in inlineTemplates)
38 | {
39 | if (string.IsNullOrWhiteSpace(StringsFilter.Filter.Text)) searchText = "";
40 |
41 | var text = StringsFilter.Filter.IgnoreCase == true ? i.Text.ToLower() : i.Text;
42 | var foregroundColor = i.Foreground;
43 | Color? backgroundColor = i.Background;
44 | bool strikeThrough = i.StrikeThrough;
45 | FontWeight? fontWeight = i.FontWeight;
46 |
47 | if (text.Contains(searchText))
48 | {
49 | int startIndex = 0;
50 |
51 | while ((index = text.IndexOf(searchText, startIndex)) != -1)
52 | {
53 | if (index > startIndex)
54 | {
55 | string beforeMatch = i.Text.Substring(startIndex, index - startIndex);
56 | result.Add(MakeInline(beforeMatch, foregroundColor, backgroundColor, strikeThrough, fontWeight));
57 | }
58 |
59 | string match = i.Text.Substring(index, searchText.Length);
60 |
61 | if (!backgroundColor.HasValue)
62 | {
63 | result.Add(MakeInline(match, foregroundColor, filterColor, strikeThrough, fontWeight));
64 | }
65 | else
66 | {
67 | result.Add(MakeInline(match, foregroundColor, backgroundColor, strikeThrough, fontWeight));
68 | }
69 |
70 | startIndex = index + searchText.Length;
71 | }
72 |
73 | if (startIndex < i.Text.Length)
74 | {
75 | string remaining = i.Text.Substring(startIndex);
76 | result.Add(MakeInline(remaining, foregroundColor, backgroundColor, strikeThrough, fontWeight));
77 | }
78 | }
79 | else
80 | {
81 | result.Add(i);
82 | }
83 |
84 | }
85 |
86 | return new InlinesWrapper(result.ToArray());
87 | }
88 |
89 | private static InlineTemplate MakeInline(string text,
90 | Color? foregroundColor = null,
91 | Color? color = null,
92 | bool strikeThrough = false,
93 | FontWeight? fontWeight = null)
94 | => new(text)
95 | {
96 | Background = color,
97 | Foreground = foregroundColor,
98 | StrikeThrough = strikeThrough,
99 | FontWeight = fontWeight
100 | };
101 |
102 |
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/DataStructure/TermEntry.cs:
--------------------------------------------------------------------------------
1 | namespace LocalizationTracker.Tools.GlossaryTools;
2 |
3 | ///
4 | /// Term entry represents piece of text in LocaleEntry of StringEntry
5 | ///
6 | public class TermEntry
7 | {
8 | public string TermId;
9 | public string StringKey;
10 | public string Comment;
11 | public int StartIndex;
12 | public int EndIndex;
13 | public bool CaseError;
14 | public int Length => EndIndex - StartIndex + 1;
15 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/DataStructure/TermTemplateCollection.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace LocalizationTracker.Tools.GlossaryTools;
4 |
5 | public class TermTemplateCollection
6 | {
7 | private Dictionary>> m_IdToLocaleToTemplateMap = new();
8 |
9 | public void AddTermTemplate(string termId, Locale locale, TermTemplate template)
10 | {
11 | if (!m_IdToLocaleToTemplateMap.ContainsKey(termId))
12 | m_IdToLocaleToTemplateMap[termId] = new Dictionary>();
13 |
14 | if (!m_IdToLocaleToTemplateMap[termId].ContainsKey(locale))
15 | m_IdToLocaleToTemplateMap[termId][locale] = new (){};
16 |
17 | m_IdToLocaleToTemplateMap[termId][locale].Add(template);
18 | }
19 |
20 | public bool TryGetTermTemplates(string termId, Locale locale, out List template)
21 | {
22 | template = new List();
23 | return m_IdToLocaleToTemplateMap.TryGetValue(termId, out var locales) &&
24 | locales.TryGetValue(locale, out template);
25 | }
26 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/BakingSheet.Json.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BakingSheet.Json",
3 | "rootNamespace": "",
4 | "references": [
5 | "BakingSheet"
6 | ],
7 | "includePlatforms": [],
8 | "excludePlatforms": [],
9 | "allowUnsafeCode": false,
10 | "overrideReferences": false,
11 | "precompiledReferences": [],
12 | "autoReferenced": true,
13 | "defineConstraints": [],
14 | "versionDefines": [],
15 | "noEngineReferences": false
16 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/BakingSheet.Json.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: df02fc2854da34e60839910beb076b2b
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/JsonSheetAssetPathConverter.cs:
--------------------------------------------------------------------------------
1 | // BakingSheet, Maxwell Keonwoo Kang , 2022
2 |
3 | using System;
4 | using Newtonsoft.Json;
5 |
6 | namespace Cathei.BakingSheet
7 | {
8 | public class JsonSheetAssetPathConverter : JsonConverter
9 | {
10 | public override ISheetAssetPath ReadJson(JsonReader reader, Type objectType, ISheetAssetPath existingValue, bool hasExistingValue, JsonSerializer serializer)
11 | {
12 | string path = (string)reader.Value;
13 | return (ISheetAssetPath)Activator.CreateInstance(objectType, path);
14 | }
15 |
16 | public override void WriteJson(JsonWriter writer, ISheetAssetPath value, JsonSerializer serializer)
17 | {
18 | if (!value.IsValid())
19 | {
20 | writer.WriteNull();
21 | return;
22 | }
23 |
24 | writer.WriteValue(value.RawValue);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/JsonSheetAssetPathConverter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 23aa15708e86242549c7a3f76a4ad3b8
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/JsonSheetContractResolver.cs:
--------------------------------------------------------------------------------
1 | // BakingSheet, Maxwell Keonwoo Kang , 2022
2 |
3 | using System;
4 | using System.Reflection;
5 | using Newtonsoft.Json;
6 | using Newtonsoft.Json.Converters;
7 | using Newtonsoft.Json.Serialization;
8 |
9 | namespace Cathei.BakingSheet
10 | {
11 | public class JsonSheetContractResolver : DefaultContractResolver
12 | {
13 | public static readonly JsonSheetContractResolver Instance = new JsonSheetContractResolver();
14 |
15 | protected override JsonContract CreateContract(System.Type objectType)
16 | {
17 | if (typeof(ISheetRow).IsAssignableFrom(objectType) ||
18 | typeof(ISheetRowElem).IsAssignableFrom(objectType))
19 | {
20 | return CreateObjectContract(objectType);
21 | }
22 |
23 | if (typeof(ISheetReference).IsAssignableFrom(objectType))
24 | {
25 | var contract = base.CreateContract(objectType);
26 | contract.Converter = new JsonSheetReferenceConverter();
27 | return contract;
28 | }
29 |
30 | if (typeof(ISheetAssetPath).IsAssignableFrom(objectType))
31 | {
32 | var contract = base.CreateContract(objectType);
33 | contract.Converter = new JsonSheetAssetPathConverter();
34 | return contract;
35 | }
36 |
37 | if (objectType.IsEnum || Nullable.GetUnderlyingType(objectType)?.IsEnum == true)
38 | {
39 | var contract = base.CreateContract(objectType);
40 | contract.Converter = new StringEnumConverter();
41 | return contract;
42 | }
43 |
44 | return base.CreateContract(objectType);
45 | }
46 |
47 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
48 | {
49 | JsonProperty property = base.CreateProperty(member, memberSerialization);
50 |
51 | if (member is PropertyInfo pi)
52 | {
53 | var nonSerialize = pi.IsDefined(typeof(NonSerializedAttribute));
54 | var hasSetMethod = pi.SetMethod != null;
55 |
56 | property.Writable = !nonSerialize && hasSetMethod;
57 | property.ShouldSerialize = property.ShouldDeserialize = _ => !nonSerialize && hasSetMethod;
58 | }
59 |
60 | return property;
61 | }
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/JsonSheetContractResolver.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f93f015347c974920ae6ff72b64ad05d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/JsonSheetConverter.cs:
--------------------------------------------------------------------------------
1 | // BakingSheet, Maxwell Keonwoo Kang , 2022
2 |
3 | using System;
4 | using System.IO;
5 | using System.Threading.Tasks;
6 | using Cathei.BakingSheet.Internal;
7 | using Microsoft.Extensions.Logging;
8 | using Newtonsoft.Json;
9 | using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
10 |
11 | namespace Cathei.BakingSheet
12 | {
13 | public class JsonSheetConverter : ISheetConverter
14 | {
15 | public virtual string Extension => "json";
16 |
17 | private string _loadPath;
18 | private IFileSystem _fileSystem;
19 |
20 | public JsonSheetConverter(string path, IFileSystem fileSystem = null)
21 | {
22 | _loadPath = path;
23 | _fileSystem = fileSystem ?? new FileSystem();
24 | }
25 |
26 | public static void ErrorHandler(ILogger logError, ErrorEventArgs err)
27 | {
28 | if (err.ErrorContext.Member?.ToString() == nameof(ISheetRow.Id) &&
29 | err.ErrorContext.OriginalObject is ISheetRow &&
30 | !(err.CurrentObject is ISheet))
31 | {
32 | // if Id has error, the error must be handled on the sheet level
33 | return;
34 | }
35 |
36 | using (logError.BeginScope(err.ErrorContext.Path))
37 | logError.LogError(err.ErrorContext.Error, err.ErrorContext.Error.Message);
38 |
39 | err.ErrorContext.Handled = true;
40 | }
41 |
42 | public virtual JsonSerializerSettings GetSettings(ILogger logError)
43 | {
44 | var settings = new JsonSerializerSettings();
45 |
46 | settings.Error = (_, err) => ErrorHandler(logError, err);
47 | settings.ContractResolver = JsonSheetContractResolver.Instance;
48 |
49 | return settings;
50 | }
51 |
52 | protected virtual string Serialize(object obj, Type type, ILogger logger)
53 | {
54 | return JsonConvert.SerializeObject(obj, type, GetSettings(logger));
55 | }
56 |
57 | protected virtual object Deserialize(string json, Type type, ILogger logger)
58 | {
59 | return JsonConvert.DeserializeObject(json, type, GetSettings(logger));
60 | }
61 |
62 | public async Task Import(SheetConvertingContext context)
63 | {
64 | var sheetProps = context.Container.GetSheetProperties();
65 |
66 | foreach (var pair in sheetProps)
67 | {
68 | using (context.Logger.BeginScope(pair.Key))
69 | {
70 | var path = Path.Combine(_loadPath, $"{pair.Key}.{Extension}");
71 |
72 | if (!_fileSystem.Exists(path))
73 | continue;
74 |
75 | string data;
76 |
77 | using (var stream = _fileSystem.OpenRead(path))
78 | using (var reader = new StreamReader(stream))
79 | data = await reader.ReadToEndAsync();
80 |
81 | var sheet = Deserialize(data, pair.Value.PropertyType, context.Logger) as ISheet;
82 | pair.Value.SetValue(context.Container, sheet);
83 | }
84 | }
85 |
86 | return true;
87 | }
88 |
89 | public async Task Export(SheetConvertingContext context)
90 | {
91 | var sheetProps = context.Container.GetSheetProperties();
92 |
93 | _fileSystem.CreateDirectory(_loadPath);
94 |
95 | foreach (var pair in sheetProps)
96 | {
97 | using (context.Logger.BeginScope(pair.Key))
98 | {
99 | var sheet = pair.Value.GetValue(context.Container);
100 | var data = Serialize(sheet, pair.Value.PropertyType, context.Logger);
101 |
102 | var path = Path.Combine(_loadPath, $"{pair.Key}.{Extension}");
103 |
104 | using (var stream = _fileSystem.OpenWrite(path))
105 | using (var writer = new StreamWriter(stream))
106 | await writer.WriteAsync(data);
107 | }
108 | }
109 |
110 | return true;
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/JsonSheetConverter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 92892f2418fd347d5a0c519b1bd6b4fd
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/JsonSheetReferenceConverter.cs:
--------------------------------------------------------------------------------
1 | // BakingSheet, Maxwell Keonwoo Kang , 2022
2 |
3 | using System;
4 | using Newtonsoft.Json;
5 |
6 | namespace Cathei.BakingSheet
7 | {
8 | public class JsonSheetReferenceConverter : JsonConverter
9 | {
10 | public override ISheetReference ReadJson(JsonReader reader, Type objectType, ISheetReference existingValue, bool hasExistingValue, JsonSerializer serializer)
11 | {
12 | if (existingValue == null)
13 | existingValue = (ISheetReference)Activator.CreateInstance(objectType);
14 |
15 | existingValue.Id = serializer.Deserialize(reader, existingValue.IdType);
16 | return existingValue;
17 | }
18 |
19 | public override void WriteJson(JsonWriter writer, ISheetReference value, JsonSerializer serializer)
20 | {
21 | serializer.Serialize(writer, value.Id);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/Json/JsonSheetReferenceConverter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 00e7b3552d0d434f981c7d440c619881
3 | timeCreated: 1663385507
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/SheetImport/GlossarySheet.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Cathei.BakingSheet;
3 | using DocumentFormat.OpenXml.Drawing.Diagrams;
4 |
5 | namespace LocalizationTracker.Tools.GlossaryTools;
6 |
7 | public interface IGlossary
8 | {
9 | string Id { get; }
10 | public string ruRU { get; }
11 | public string Example_ruRU { get; }
12 | public string enGB { get; }
13 | public string Example_enGB { get; }
14 |
15 | public Dictionary Example { get; }
16 | public Dictionary Exact { get; }
17 | public string Comment { get; }
18 | bool TryGetTranslation(Locale locale, out string translation);
19 | string GetExampleTranslation(Locale? locale);
20 |
21 | }
22 |
23 |
24 | public class GlossarySheet : Sheet
25 | {
26 | ///
27 | /// Table markdown
28 | ///
29 | public class Term : SheetRow, IGlossary
30 | {
31 | public string ruRU { get; private set; }
32 | public string Example_ruRU { get; private set; }
33 | public string enGB { get; private set; }
34 | public string Example_enGB { get; private set; }
35 |
36 | public Dictionary Example { get; private set; } = new Dictionary();
37 | public Dictionary Exact { get; private set; } = new Dictionary();
38 | public string Comment { get; private set; }
39 |
40 | public bool TryGetTranslation(Locale locale, out string translation)
41 | {
42 | translation = string.Empty;
43 | if (!Exact.TryGetValue(locale, out translation))
44 | {
45 | if (locale == Locale.enGB)
46 | translation = enGB;
47 | if (locale == Locale.ruRU)
48 | translation = ruRU;
49 | }
50 |
51 | return !string.IsNullOrEmpty(translation);
52 | }
53 |
54 | public string GetExampleTranslation(Locale? locale)
55 | {
56 | var translation = string.Empty;
57 | if (locale == null)
58 | return Example_enGB;
59 |
60 | if (Exact.TryGetValue(locale, out translation))
61 | return translation;
62 |
63 | if (locale == Locale.enGB)
64 | return Example_enGB;
65 | if (locale == Locale.ruRU)
66 | return Example_ruRU;
67 |
68 | return Example_enGB;
69 | }
70 | }
71 |
72 | public class AmberGlossarySheet : Sheet
73 | {
74 | ///
75 | /// Table markdown
76 | ///
77 | public class AmberTerm : SheetRow, IGlossary
78 | {
79 | public string ruRU { get; private set; }
80 | public string Example_ruRU { get; private set; }
81 | public string enUS { get; private set; }
82 | public string Example_enUS { get; private set; }
83 | public string Description { get; private set; }
84 | public string enGB => enUS;
85 | public string Example_enGB => Example_enUS;
86 | public string Comment => Description;
87 |
88 | public Dictionary Example { get; private set; } = new Dictionary();
89 | public Dictionary Exact { get; private set; } = new Dictionary();
90 |
91 | public bool TryGetTranslation(Locale locale, out string translation)
92 | {
93 | translation = string.Empty;
94 | if (!Exact.TryGetValue(locale, out translation))
95 | {
96 | if (locale == Locale.enUS)
97 | translation = enUS;
98 | if (locale == Locale.ruRU)
99 | translation = ruRU;
100 | }
101 |
102 | return !string.IsNullOrEmpty(translation);
103 | }
104 |
105 | public string GetExampleTranslation(Locale? locale)
106 | {
107 | var translation = string.Empty;
108 | if (locale == null)
109 | return Example_enUS;
110 |
111 | if (Exact.TryGetValue(locale, out translation))
112 | return translation;
113 |
114 | if (locale == Locale.enUS)
115 | return Example_enUS;
116 | if (locale == Locale.ruRU)
117 | return Example_ruRU;
118 |
119 | return Example_enUS;
120 | }
121 | }
122 | }
123 | }
124 |
125 |
126 |
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/SheetImport/GlossarySheetContainer.cs:
--------------------------------------------------------------------------------
1 | using Cathei.BakingSheet;
2 | using static LocalizationTracker.Tools.GlossaryTools.GlossarySheet;
3 |
4 | namespace LocalizationTracker.Tools.GlossaryTools;
5 |
6 | public class GlossarySheetContainer : SheetContainerBase
7 | {
8 | public GlossarySheetContainer(Microsoft.Extensions.Logging.ILogger logger) : base(logger) { }
9 | public GlossarySheet Main { get; set; }
10 |
11 | }
12 |
13 | public class AmberGlossarySheetContainer : SheetContainerBase
14 | {
15 | public AmberGlossarySheetContainer(Microsoft.Extensions.Logging.ILogger logger) : base(logger) { }
16 | public AmberGlossarySheet Main { get; set; }
17 | }
--------------------------------------------------------------------------------
/LocalizationTracker/Tools/GlossaryTools/SheetImport/Logger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.Extensions.Logging;
4 |
5 | namespace LocalizationTracker.Tools.GoogleSheet;
6 |
7 | public class Logger : ILogger
8 | {
9 | public static readonly Logger Default = new Logger();
10 |
11 | private IExternalScopeProvider scopeProvider = new LoggerExternalScopeProvider();
12 |
13 | private IList |