2
0
Эх сурвалжийг харах

Merge branch 'master' into release

Krzysztof Krysiński 3 өдөр өмнө
parent
commit
715675962c
100 өөрчлөгдсөн 4676 нэмэгдсэн , 1829 устгасан
  1. 23 0
      .github/workflows/localization-key-reference.yml
  2. 1 0
      .github/workflows/localization/check-key-references.pip.txt
  3. 70 0
      .github/workflows/localization/check-key-references.py
  4. 10 2
      src/ChunkyImageLib/Chunk.cs
  5. 6 6
      src/ChunkyImageLib/ChunkyImage.cs
  6. 8 7
      src/ChunkyImageLib/ChunkyImageEx.cs
  7. 2 2
      src/ChunkyImageLib/IReadOnlyChunkyImage.cs
  8. 5 5
      src/ChunkyImageLib/Operations/ChunkyImageOperation.cs
  9. 0 81
      src/Custom.ruleset
  10. 2 8
      src/Directory.Build.props
  11. 1 1
      src/Drawie
  12. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs
  13. 3 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  14. 5 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs
  15. 84 27
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs
  16. 15 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Image/MaskNode.cs
  17. 29 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  18. 14 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  19. 163 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NoiseNode.cs
  20. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  21. 11 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs
  22. 10 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs
  23. 32 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/LineNode.cs
  24. 37 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RectangleNode.cs
  25. 7 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  26. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TextureCache.cs
  27. 8 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs
  28. 2 1
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs
  29. 21 6
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  30. 5 3
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  31. 5 0
      src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs
  32. 44 17
      src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs
  33. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  34. 1 0
      src/PixiEditor.IdentityProvider.PixiAuth/PixiAuthIdentityProvider.cs
  35. 4 0
      src/PixiEditor.UI.Common/Accents/Base.axaml
  36. 0 1
      src/PixiEditor.sln
  37. 124 124
      src/PixiEditor/Data/Localization/Languages/ar.json
  38. 1133 1129
      src/PixiEditor/Data/Localization/Languages/en.json
  39. 106 100
      src/PixiEditor/Data/Localization/Languages/es.json
  40. 131 1
      src/PixiEditor/Data/Localization/Languages/ru.json
  41. 1127 0
      src/PixiEditor/Data/Localization/Languages/tr.json
  42. 10 3
      src/PixiEditor/Data/Localization/LocalizationData.json
  43. 27 17
      src/PixiEditor/Helpers/Converters/EnumToLocalizedStringConverter.cs
  44. 0 19
      src/PixiEditor/Helpers/EnumDescriptionConverter.cs
  45. 135 1
      src/PixiEditor/Helpers/Extensions/EnumerableExtensions.cs
  46. 18 0
      src/PixiEditor/Helpers/LocalizeEnumAttribute.cs
  47. 26 7
      src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs
  48. 2 1
      src/PixiEditor/Models/DocumentModels/DocumentStructureHelper.cs
  49. 9 0
      src/PixiEditor/Models/DocumentModels/DocumentTransformMode.cs
  50. 3 2
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  51. 16 15
      src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs
  52. 7 0
      src/PixiEditor/Models/DocumentPassthroughActions/RefreshPreviews_PassthroughAction.cs
  53. 126 0
      src/PixiEditor/Models/EnumTranslations.cs
  54. 2 1
      src/PixiEditor/Models/Handlers/IDocument.cs
  55. 11 7
      src/PixiEditor/Models/Handlers/INodeHandler.cs
  56. 1 0
      src/PixiEditor/Models/Handlers/INodePropertyHandler.cs
  57. 1 0
      src/PixiEditor/Models/Handlers/IToolsHandler.cs
  58. 5 1
      src/PixiEditor/Models/Handlers/Toolbars/PaintBrushShape.cs
  59. 29 11
      src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs
  60. 47 22
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  61. 40 3
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  62. 35 13
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  63. 144 0
      src/PixiEditor/Models/Structures/ObservableHashSet.cs
  64. 5 1
      src/PixiEditor/Models/Tools/BrightnessMode.cs
  65. 2 2
      src/PixiEditor/Properties/AssemblyInfo.cs
  66. 4 7
      src/PixiEditor/Styles/Templates/NodeFrameView.axaml
  67. 4 10
      src/PixiEditor/Styles/Templates/NodeGraphView.axaml
  68. 15 1
      src/PixiEditor/Styles/Templates/NodePicker.axaml
  69. 15 0
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  70. 81 0
      src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs
  71. 21 1
      src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ApplyFilterNodeViewModel.cs
  72. 1 1
      src/PixiEditor/ViewModels/Document/Nodes/ModifyImageLeftNodeViewModel.cs
  73. 1 1
      src/PixiEditor/ViewModels/Document/Nodes/ModifyImageRightNodeViewModel.cs
  74. 29 1
      src/PixiEditor/ViewModels/Document/Nodes/NoiseNodeViewModel.cs
  75. 9 0
      src/PixiEditor/ViewModels/Document/Nodes/Shapes/LineNodeViewModel.cs
  76. 9 0
      src/PixiEditor/ViewModels/Document/Nodes/Shapes/RectangleNodeViewModel.cs
  77. 2 2
      src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs
  78. 2 1
      src/PixiEditor/ViewModels/Document/StructureTree.cs
  79. 6 0
      src/PixiEditor/ViewModels/Nodes/IPairNodeEndViewModel.cs
  80. 6 0
      src/PixiEditor/ViewModels/Nodes/IPairNodeStartViewModel.cs
  81. 2 28
      src/PixiEditor/ViewModels/Nodes/NodeFrameViewModel.cs
  82. 23 21
      src/PixiEditor/ViewModels/Nodes/NodeFrameViewModelBase.cs
  83. 2 0
      src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs
  84. 99 24
      src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs
  85. 222 35
      src/PixiEditor/ViewModels/Nodes/NodeZoneViewModel.cs
  86. 19 0
      src/PixiEditor/ViewModels/Nodes/Traverse.cs
  87. 4 0
      src/PixiEditor/ViewModels/PixiObservableObject.cs
  88. 16 3
      src/PixiEditor/ViewModels/SettingsWindowViewModel.cs
  89. 17 0
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  90. 19 0
      src/PixiEditor/ViewModels/SubViewModels/ViewOptionsViewModel.cs
  91. 4 3
      src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs
  92. 28 0
      src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs
  93. 21 0
      src/PixiEditor/ViewModels/UserPreferences/Settings/PerformanceSettings.cs
  94. 9 0
      src/PixiEditor/ViewModels/UserPreferences/Settings/SceneSettings.cs
  95. 16 1
      src/PixiEditor/ViewModels/UserPreferences/SettingsGroup.cs
  96. 2 0
      src/PixiEditor/ViewModels/UserPreferences/SettingsViewModel.cs
  97. 1 0
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml
  98. 5 9
      src/PixiEditor/Views/Main/CommandSearch/CommandSearchControl.axaml.cs
  99. 1 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  100. 8 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

+ 23 - 0
.github/workflows/localization-key-reference.yml

@@ -0,0 +1,23 @@
+name: Localization Reference Key Check
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+
+jobs:
+  check-key-references:
+    name: "Check Localization Keys are referenced"
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: '3.13'
+          cache: 'pip'
+          cache-dependency-path: .github/workflows/localization/check-key-references.pip.txt
+      - name: Install dependencies
+        run: pip install -r .github/workflows/localization/check-key-references.pip.txt
+      - name: Check Localization Key References
+        run: python .github/workflows/localization/check-key-references.py

+ 1 - 0
.github/workflows/localization/check-key-references.pip.txt

@@ -0,0 +1 @@
+pyahocorasick

+ 70 - 0
.github/workflows/localization/check-key-references.py

@@ -0,0 +1,70 @@
+import json
+import os
+import logging
+from collections import OrderedDict
+import time
+import ahocorasick
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger()
+
+# PATHS
+REFERENCE_LANGUAGE = "src/PixiEditor/Data/Localization/Languages/en.json"
+SEARCH_DIRECTORIES = ["src/PixiEditor/Views", "src/PixiEditor/ViewModels", "src/PixiEditor", "src/"]
+IGNORE_DIRECTORIES = ["src/PixiEditor/Data/Localization"]
+
+def load_json(file_path):
+    """Load language JSON"""
+    try:
+        with open(file_path, "r", encoding="utf-8-sig") as f:
+            return json.load(f, object_pairs_hook=OrderedDict)
+    except FileNotFoundError:
+        print(f"::error::File not found: {file_path}")
+        return OrderedDict()
+    except json.JSONDecodeError as e:
+        print(f"::error::Failed to parse JSON in {file_path}: {e}")
+        return OrderedDict()
+
+def build_automaton(keys: list[str]) -> ahocorasick.Automaton:
+    A = ahocorasick.Automaton()
+    for i, k in enumerate(keys):
+        A.add_word(k, (i, k))
+    A.make_automaton()
+    return A
+
+def find_missing_keys(keys):
+    automaton = build_automaton(keys)
+    present = set()
+
+    ignore_prefixes = tuple(os.path.abspath(p) for p in IGNORE_DIRECTORIES)
+    for base_dir in SEARCH_DIRECTORIES:
+        for root, dirs, files in os.walk(base_dir, topdown=True):
+            dirs[:] = [d for d in dirs if not os.path.abspath(os.path.join(root, d)).startswith(ignore_prefixes)]
+            for file in files:
+                with open(os.path.join(root, file), "r", encoding="utf‑8", errors="ignore") as f:
+                    for _, (_, k) in automaton.iter(f.read()):
+                        present.add(k)
+                        if len(present) == len(keys):
+                            return []
+    return sorted(set(keys) - present)
+
+def main():
+    keys = load_json(REFERENCE_LANGUAGE)
+
+    print("Searching trough keys...")
+    start = time.time()
+    missing_keys = find_missing_keys(keys)
+    end = time.time()
+    print(f"Done, searching took {end - start}s")
+
+    if len(missing_keys) > 0:
+        print("Unreferenced keys have been found")
+        for key in missing_keys:
+            print(f"::error file={REFERENCE_LANGUAGE},title=Unreferenced key::No reference to '{key}' found")
+        return 1
+    else:
+        print("All keys have been referenced")
+        return 0
+    
+if __name__ == "__main__":
+    exit(main())

+ 10 - 2
src/ChunkyImageLib/Chunk.cs

@@ -83,9 +83,17 @@ public class Chunk : IDisposable
     /// </summary>
     /// <param name="pos">The destination for the <paramref name="surface"/></param>
     /// <param name="paint">The paint to use while drawing</param>
-    public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null)
+    public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
-        surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
+        if (samplingOptions == null || samplingOptions == SamplingOptions.Default)
+        {
+            surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
+        }
+        else
+        {
+            using var snapshot = Surface.DrawingSurface.Snapshot();
+            surface.Canvas.DrawImage(snapshot, (float)pos.X, (float)pos.Y, samplingOptions.Value, paint);
+        }
     }
 
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)

+ 6 - 6
src/ChunkyImageLib/ChunkyImage.cs

@@ -365,7 +365,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     /// </returns>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
-        Paint? paint = null)
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
         lock (lockObject)
         {
@@ -390,7 +390,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             {
                 if (committedChunk is null)
                     return false;
-                committedChunk.DrawChunkOn(surface, pos, paint);
+                committedChunk.DrawChunkOn(surface, pos, paint, samplingOptions);
                 return true;
             }
 
@@ -399,7 +399,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             {
                 if (latestChunk.IsT2)
                 {
-                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint);
+                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint, samplingOptions);
                     return true;
                 }
 
@@ -415,7 +415,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                 blendModePaint);
             if (lockTransparency)
                 OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
-            tempChunk.DrawChunkOn(surface, pos, paint);
+            tempChunk.DrawChunkOn(surface, pos, paint, samplingOptions);
 
             return true;
         }
@@ -458,7 +458,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
-        Paint? paint = null)
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
         lock (lockObject)
         {
@@ -466,7 +466,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             var chunk = GetCommittedChunk(chunkPos, resolution);
             if (chunk is null)
                 return false;
-            chunk.DrawChunkOn(surface, pos, paint);
+            chunk.DrawChunkOn(surface, pos, paint, samplingOptions);
             return true;
         }
     }

+ 8 - 7
src/ChunkyImageLib/ChunkyImageEx.cs

@@ -19,9 +19,10 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawMostUpToDateRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null)
+    (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface,
+        VecD pos, Paint? paint = null, SamplingOptions? sampling = null)
     {
-        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint);
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint, sampling);
     }
     
     /// <summary>
@@ -35,9 +36,9 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawCommittedRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
-        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint);
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint, samplingOptions);
     }
     
     private static void DrawRegionOn(
@@ -45,8 +46,8 @@ public static class IReadOnlyChunkyImageEx
         ChunkResolution resolution,
         DrawingSurface surface,
         VecD pos,
-        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, bool> drawingFunc,
-        Paint? paint = null)
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawingFunc,
+        Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
         int count = surface.Canvas.Save();
         surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
@@ -61,7 +62,7 @@ public static class IReadOnlyChunkyImageEx
             for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
             {
                 var chunkPos = new VecI(i, j);
-                drawingFunc(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint);
+                drawingFunc(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint, samplingOptions);
             }
         }
 

+ 2 - 2
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -10,8 +10,8 @@ namespace ChunkyImageLib;
 
 public interface IReadOnlyChunkyImage
 {
-    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null);
-    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null);
+    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
+    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
     RectI? FindChunkAlignedMostUpToDateBounds();
     RectI? FindChunkAlignedCommittedBounds();
     RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full, bool fallbackToChunkAligned = false);

+ 5 - 5
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -63,13 +63,13 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
         VecI bottomLeft = OperationHelper.GetChunkPos(
             new VecI(chunkCenterOnImage.X - halfChunk.X, chunkCenterOnImage.Y + halfChunk.Y), ChunkyImage.FullChunkSize);
 
-        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, bool> drawMethod = drawUpToDate ? imageToDraw.DrawMostUpToDateChunkOn : imageToDraw.DrawCommittedChunkOn;
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawMethod = drawUpToDate ? imageToDraw.DrawMostUpToDateChunkOn : imageToDraw.DrawCommittedChunkOn;
         
         drawMethod(
             topLeft,
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
-            (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()), null);
+            (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()), null, null);
 
         VecI gridShift = targetPos % ChunkyImage.FullChunkSize;
         if (gridShift.X != 0)
@@ -79,7 +79,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         if (gridShift.Y != 0)
         {
@@ -88,7 +88,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
         if (gridShift.X != 0 && gridShift.Y != 0)
         {
@@ -97,7 +97,7 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
             targetChunk.Resolution,
             targetChunk.Surface.DrawingSurface,
             (VecI)((bottomRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * targetChunk.Resolution.Multiplier()),
-            null);
+            null, null);
         }
 
         targetChunk.Surface.DrawingSurface.Canvas.Restore();

+ 0 - 81
src/Custom.ruleset

@@ -13,85 +13,4 @@
     <Rule Id="CA1303" Action="None" />
     <Rule Id="CA1416" Action="None" />
   </Rules>
-  <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
-    <Rule Id="SA0001" Action="None" />
-    <Rule Id="SA1000" Action="None" />
-    <Rule Id="SA1005" Action="None" />
-    <Rule Id="SA1008" Action="None" />
-    <Rule Id="SA1009" Action="None" />
-    <Rule Id="SA1011" Action="None" />
-    <Rule Id="SA1023" Action="None" />
-    <Rule Id="SA1028" Action="None" />
-    <Rule Id="SA1101" Action="None" />
-    <Rule Id="SA1110" Action="None" />
-    <Rule Id="SA1111" Action="None" />
-    <Rule Id="SA1112" Action="None" />
-    <Rule Id="SA1117" Action="None" />
-    <Rule Id="SA1119" Action="None" />
-    <Rule Id="SA1121" Action="None" />
-    <Rule Id="SA1122" Action="None" />
-    <Rule Id="SA1124" Action="None" />
-    <Rule Id="SA1127" Action="None" />
-    <Rule Id="SA1128" Action="None" />
-    <Rule Id="SA1129" Action="None" />
-    <Rule Id="SA1130" Action="None" />
-    <Rule Id="SA1132" Action="None" />
-    <Rule Id="SA1135" Action="None" />
-    <Rule Id="SA1136" Action="None" />
-    <Rule Id="SA1139" Action="None" />
-    <Rule Id="SA1200" Action="None" />
-    <Rule Id="SA1201" Action="None" />
-    <Rule Id="SA1202" Action="None" />
-    <Rule Id="SA1204" Action="None" />
-    <Rule Id="SA1207" Action="None" />
-    <Rule Id="SA1208" Action="None" />
-    <Rule Id="SA1209" Action="None" />
-    <Rule Id="SA1210" Action="None" />
-    <Rule Id="SA1211" Action="None" />
-    <Rule Id="SA1214" Action="None" />
-    <Rule Id="SA1216" Action="None" />
-    <Rule Id="SA1217" Action="None" />
-    <Rule Id="SA1303" Action="None" />
-    <Rule Id="SA1304" Action="None" />
-    <Rule Id="SA1307" Action="None" />
-    <Rule Id="SA1309" Action="None" />
-    <Rule Id="SA1310" Action="None" />
-    <Rule Id="SA1311" Action="None" />
-    <Rule Id="SA1313" Action="None" />
-    <Rule Id="SA1316" Action="None" />
-    <Rule Id="SA1400" Action="None" />
-    <Rule Id="SA1401" Action="None" />
-    <Rule Id="SA1402" Action="None" />
-    <Rule Id="SA1405" Action="None" />
-    <Rule Id="SA1406" Action="None" />
-    <Rule Id="SA1407" Action="None" />
-    <Rule Id="SA1408" Action="None" />
-    <Rule Id="SA1410" Action="None" />
-    <Rule Id="SA1411" Action="None" />
-    <Rule Id="SA1413" Action="None" />
-    <Rule Id="SA1501" Action="None" />
-    <Rule Id="SA1502" Action="None" />
-    <Rule Id="SA1503" Action="None" />
-    <Rule Id="SA1505" Action="None" />
-    <Rule Id="SA1507" Action="None" />
-    <Rule Id="SA1508" Action="None" />
-    <Rule Id="SA1512" Action="None" />
-    <Rule Id="SA1513" Action="None" />
-    <Rule Id="SA1515" Action="None" />
-    <Rule Id="SA1516" Action="None" />
-    <Rule Id="SA1518" Action="None" />
-    <Rule Id="SA1600" Action="None" />
-    <Rule Id="SA1601" Action="None" />
-    <Rule Id="SA1602" Action="None" />
-    <Rule Id="SA1604" Action="None" />
-    <Rule Id="SA1605" Action="None" />
-    <Rule Id="SA1606" Action="None" />
-    <Rule Id="SA1607" Action="None" />
-    <Rule Id="SA1623" Action="None" />
-    <Rule Id="SA1629" Action="None" />
-    <Rule Id="SA1633" Action="None" />
-    <Rule Id="SA1642" Action="None" />
-    <Rule Id="SA1643" Action="None" />
-    <Rule Id="SA1648" Action="None" />
-  </Rules>
 </RuleSet>

+ 2 - 8
src/Directory.Build.props

@@ -1,15 +1,9 @@
 <Project>
     <PropertyGroup>
-        <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
+        <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Custom.ruleset</CodeAnalysisRuleSet>
 		    <AvaloniaVersion>11.3.0</AvaloniaVersion>
     </PropertyGroup>
-    <ItemGroup>
-        <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
-    </ItemGroup>
-    <ItemGroup>
-        <AdditionalFiles Include="$(SolutionDir)/stylecop.json" />
-    </ItemGroup>
-
+  
   <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows')) AND '$(Platform)' == 'x64'">
     <RuntimeIdentifier>win-x64</RuntimeIdentifier>
   </PropertyGroup>

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 1be85ac9f4bc6b584e6a3a5a3d0287201c6a5f03
+Subproject commit b6c34c96ac5b01abad69604465445270270270d2

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs

@@ -13,7 +13,7 @@ public class SceneObjectRenderContext : RenderContext
     public RenderOutputProperty TargetPropertyOutput { get; }
 
     public SceneObjectRenderContext(RenderOutputProperty targetPropertyOutput, DrawingSurface surface, RectD localBounds, KeyFrameTime frameTime,
-        ChunkResolution chunkResolution, VecI renderOutputSize, VecI documentSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, double opacity) : base(surface, frameTime, chunkResolution, renderOutputSize, documentSize, processingColorSpace, opacity)
+        ChunkResolution chunkResolution, VecI renderOutputSize, VecI documentSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, SamplingOptions desiredSampling, double opacity) : base(surface, frameTime, chunkResolution, renderOutputSize, documentSize, processingColorSpace, desiredSampling, opacity)
     {
         TargetPropertyOutput = targetPropertyOutput;
         LocalBounds = localBounds;

+ 3 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -72,10 +72,9 @@ public class CreateImageNode : Node, IPreviewRenderable
 
         int saved = surface.DrawingSurface.Canvas.Save();
 
-        RenderContext ctx = new RenderContext(surface.DrawingSurface, context.FrameTime, context.ChunkResolution,
-            surface.Size, context.DocumentSize, context.ProcessingColorSpace);
-        ctx.FullRerender = context.FullRerender;
-        ctx.TargetOutput = context.TargetOutput;
+        RenderContext ctx = context.Clone();
+        ctx.RenderSurface = surface.DrawingSurface;
+        ctx.RenderOutputSize = surface.Size;
 
         float chunkMultiplier = (float)context.ChunkResolution.Multiplier();
 

+ 5 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs

@@ -88,7 +88,8 @@ public class OutlineNode : RenderNode, IRenderInput
 
             var ctx = context.Clone();
             ctx.ChunkResolution = ChunkResolution.Full;
-            ctx.RenderOutputSize = (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
+            bool isAdjusted = context.DocumentSize == context.RenderOutputSize;
+            ctx.RenderOutputSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
 
             Background.Value.Paint(ctx, temp.DrawingSurface);
 
@@ -124,7 +125,10 @@ public class OutlineNode : RenderNode, IRenderInput
 
     public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
+        int saved = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         OnPaint(context, renderOn);
+        renderOn.Canvas.RestoreToCount(saved);
         return true;
     }
 

+ 84 - 27
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs

@@ -11,67 +11,124 @@ using Drawie.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 
 [NodeInfo("ApplyFilter")]
-public class ApplyFilterNode : RenderNode, IRenderInput
+public sealed class ApplyFilterNode : RenderNode, IRenderInput
 {
-    private Paint _paint = new();
+    private readonly Paint _paint = new();
+    private readonly Paint _maskPaint = new()
+    {
+        BlendMode = BlendMode.DstIn,
+        ColorFilter = Filters.MaskFilter
+    };
+
     public InputProperty<Filter?> Filter { get; }
 
     public RenderInputProperty Background { get; }
 
+    public RenderInputProperty Mask { get; }
+    
+    public InputProperty<bool> InvertMask { get; }
+
     public ApplyFilterNode()
     {
         Background = CreateRenderInput("Input", "IMAGE");
         Filter = CreateInput<Filter>("Filter", "FILTER", null);
+        Mask = CreateRenderInput("Mask", "MASK");
+        InvertMask = CreateInput("InvertMask", "INVERT_MASK", false);
         Output.FirstInChain = null;
         AllowHighDpiRendering = true;
     }
 
-
     protected override void Paint(RenderContext context, DrawingSurface surface)
     {
         AllowHighDpiRendering = (Background.Connection.Node as RenderNode)?.AllowHighDpiRendering ?? true;
         base.Paint(context, surface);
     }
 
-    protected override void OnPaint(RenderContext context, DrawingSurface surface)
+    protected override void OnPaint(RenderContext context, DrawingSurface outputSurface)
     {
-        if (_paint == null)
-            return;
+        using var _ = DetermineTargetSurface(context, outputSurface, out var processingSurface);
+
+        DrawWithFilter(context, outputSurface, processingSurface);
+        
+        // If the Mask is null, we already drew to the output surface, otherwise we still need to draw to it (and apply the mask)
+        if (processingSurface != outputSurface)
+        {
+            ApplyWithMask(context, processingSurface, outputSurface);
+        }
+    }
 
+    private void DrawWithFilter(RenderContext context, DrawingSurface outputSurface, DrawingSurface processingSurface)
+    {
         _paint.SetFilters(Filter.Value);
 
         if (!context.ProcessingColorSpace.IsSrgb)
         {
-            var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
+            HandleNonSrgbContext(context, outputSurface, processingSurface);
+            return;
+        }
+
+        var layer = processingSurface.Canvas.SaveLayer(_paint);
+        Background.Value?.Paint(context, processingSurface);
+        processingSurface.Canvas.RestoreToCount(layer);
+    }
 
-            int saved = surface.Canvas.Save();
-            surface.Canvas.SetMatrix(Matrix3X3.Identity);
+    private void HandleNonSrgbContext(RenderContext context, DrawingSurface surface, DrawingSurface targetSurface)
+    {
+        using var intermediate = Texture.ForProcessing(surface, context.ProcessingColorSpace);
 
-            Background.Value?.Paint(context, intermediate.DrawingSurface);
+        Background.Value?.Paint(context, intermediate.DrawingSurface);
 
-            var srgbSurface = Texture.ForProcessing(intermediate.Size, ColorSpace.CreateSrgb());
+        using var srgbSurface = Texture.ForProcessing(intermediate.Size, ColorSpace.CreateSrgb());
 
-            srgbSurface.DrawingSurface.Canvas.SaveLayer(_paint);
-            srgbSurface.DrawingSurface.Canvas.DrawSurface(intermediate.DrawingSurface, 0, 0);
-            srgbSurface.DrawingSurface.Canvas.Restore();
+        srgbSurface.DrawingSurface.Canvas.SaveLayer(_paint);
+        srgbSurface.DrawingSurface.Canvas.DrawSurface(intermediate.DrawingSurface, 0, 0);
+        srgbSurface.DrawingSurface.Canvas.Restore();
 
-            surface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
-            surface.Canvas.RestoreToCount(saved);
-            intermediate.Dispose();
-            srgbSurface.Dispose();
-        }
-        else
-        {
-            int layer = surface.Canvas.SaveLayer(_paint);
-            Background.Value?.Paint(context, surface);
-            surface.Canvas.RestoreToCount(layer);
-        }
+        var saved = targetSurface.Canvas.Save();
+        targetSurface.Canvas.SetMatrix(Matrix3X3.Identity);
+
+        targetSurface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
+        targetSurface.Canvas.RestoreToCount(saved);
+    }
+
+    private Texture? DetermineTargetSurface(RenderContext context, DrawingSurface outputSurface, out DrawingSurface targetSurface)
+    {
+        targetSurface = outputSurface;
+        
+        if (Mask.Value == null)
+            return null;
+        
+        Background.Value?.Paint(context, outputSurface);
+        var texture = Texture.ForProcessing(outputSurface, context.ProcessingColorSpace);
+        targetSurface = texture.DrawingSurface;
+        
+        return texture;
     }
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    private void ApplyWithMask(RenderContext context, DrawingSurface processedSurface, DrawingSurface finalSurface)
     {
-        return PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
+        _maskPaint.BlendMode = !InvertMask.Value ? BlendMode.DstIn : BlendMode.DstOut;
+        var maskLayer = processedSurface.Canvas.SaveLayer(_maskPaint);
+        Mask.Value?.Paint(context, processedSurface);
+        processedSurface.Canvas.RestoreToCount(maskLayer);
+
+        var saved = finalSurface.Canvas.Save();
+        finalSurface.Canvas.SetMatrix(Matrix3X3.Identity);
+
+        finalSurface.Canvas.DrawSurface(processedSurface, 0, 0);
+        finalSurface.Canvas.RestoreToCount(saved);
     }
 
+    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "") =>
+        PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
+
     public override Node CreateCopy() => new ApplyFilterNode();
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        
+        _paint.Dispose();
+        _maskPaint.Dispose();
+    }
 }

+ 15 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Image/MaskNode.cs

@@ -6,20 +6,23 @@ using PixiEditor.ChangeableDocument.Rendering;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
 
 [NodeInfo("Mask")]
-public class MaskNode : RenderNode, IRenderInput
+public sealed class MaskNode : RenderNode, IRenderInput
 {
     public RenderInputProperty Background { get; }
     public RenderInputProperty Mask { get; }
+    public InputProperty<bool> Invert { get; }
 
-    protected Paint maskPaint = new Paint()
+    private readonly Paint maskPaint = new()
     {
-        BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.DstIn, ColorFilter = Nodes.Filters.MaskFilter
+        BlendMode = BlendMode.DstIn,
+        ColorFilter = Filters.MaskFilter
     };
 
     public MaskNode()
     {
         Background = CreateRenderInput("Background", "INPUT");
         Mask = CreateRenderInput("Mask", "MASK");
+        Invert = CreateInput("Invert", "INVERT", false);
         AllowHighDpiRendering = true;
         Output.FirstInChain = null;
     }
@@ -38,14 +41,22 @@ public class MaskNode : RenderNode, IRenderInput
             return;
         }
 
+        maskPaint.BlendMode = !Invert.Value ? BlendMode.DstIn : BlendMode.DstOut;
+
         int layer = surface.Canvas.SaveLayer(maskPaint);
         Mask.Value.Paint(context, surface);
         surface.Canvas.RestoreToCount(layer);
     }
 
-
     public override Node CreateCopy()
     {
         return new MaskNode();
     }
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        
+        maskPaint.Dispose();
+    }
 }

+ 29 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -5,6 +5,7 @@ using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -130,6 +131,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
         var sceneSize = GetSceneSize(ctx.FrameTime);
         VecD topLeft = sceneSize / 2f;
+
         if (renderedSurfaceFrame == null || ctx.FullRerender || ctx.FrameTime.Frame != renderedSurfaceFrame)
         {
             GetLayerImageAtFrame(ctx.FrameTime.Frame).DrawMostUpToDateRegionOn(
@@ -139,7 +141,18 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         }
         else
         {
-            workingSurface.Canvas.DrawSurface(fullResrenderedSurface.DrawingSurface, -topLeft, paint);
+            if (ctx.DesiredSamplingOptions == SamplingOptions.Default)
+            {
+                workingSurface.Canvas.DrawSurface(
+                    fullResrenderedSurface.DrawingSurface, -(float)topLeft.X, -(float)topLeft.Y, paint);
+            }
+            else
+            {
+                using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
+                workingSurface.Canvas.DrawImage(snapshot, -(float)topLeft.X, -(float)topLeft.Y,
+                    ctx.DesiredSamplingOptions,
+                    paint);
+            }
         }
 
         workingSurface.Canvas.RestoreToCount(saved);
@@ -242,14 +255,27 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
         if (renderedSurfaceFrame == cacheFrame)
         {
-            renderOnto.Canvas.DrawSurface(fullResrenderedSurface.DrawingSurface, VecI.Zero, blendPaint);
+            int saved = renderOnto.Canvas.Save();
+            renderOnto.Canvas.Scale((float)context.ChunkResolution.Multiplier());
+            if (context.DesiredSamplingOptions == SamplingOptions.Default)
+            {
+                renderOnto.Canvas.DrawSurface(
+                    fullResrenderedSurface.DrawingSurface, 0, 0, blendPaint);
+            }
+            else
+            {
+                using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
+                renderOnto.Canvas.DrawImage(snapshot, 0, 0, context.DesiredSamplingOptions, blendPaint);
+            }
+
+            renderOnto.Canvas.RestoreToCount(saved);
         }
         else
         {
             img.DrawMostUpToDateRegionOn(
                 new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
                 context.ChunkResolution,
-                renderOnto, VecI.Zero, blendPaint);
+                renderOnto, VecI.Zero, blendPaint, context.DesiredSamplingOptions);
         }
 
         return true;

+ 14 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs

@@ -62,7 +62,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
                 DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters, targetPaint);
 
                 blendPaint.SetFilters(null);
-                DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution);
+                DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution,
+                    context.DesiredSamplingOptions);
             }
 
             return;
@@ -130,7 +131,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
             blendPaint.SetFilters(null);
         }
 
-        DrawWithResolution(outputWorkingSurface.DrawingSurface, renderOnto, adjustedResolution);
+        DrawWithResolution(outputWorkingSurface.DrawingSurface, renderOnto, adjustedResolution,
+            context.DesiredSamplingOptions);
 
         renderOnto.Canvas.RestoreToCount(saved);
         outputWorkingSurface.DrawingSurface.Canvas.Restore();
@@ -149,13 +151,21 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
         workingSurface.Canvas.RestoreToCount(scaled);
     }
 
-    private void DrawWithResolution(DrawingSurface source, DrawingSurface target, ChunkResolution resolution)
+    private void DrawWithResolution(DrawingSurface source, DrawingSurface target, ChunkResolution resolution, SamplingOptions sampling)
     {
         int scaled = target.Canvas.Save();
         float multiplier = (float)resolution.InvertedMultiplier();
         target.Canvas.Scale(multiplier, multiplier);
 
-        target.Canvas.DrawSurface(source, 0, 0, blendPaint);
+        if (sampling == SamplingOptions.Default)
+        {
+            target.Canvas.DrawSurface(source, 0, 0, blendPaint);
+        }
+        else
+        {
+            using var snapshot = source.Snapshot();
+            target.Canvas.DrawImage(snapshot, 0, 0, sampling, blendPaint);
+        }
 
         target.Canvas.RestoreToCount(scaled);
     }

+ 163 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NoiseNode.cs

@@ -18,6 +18,11 @@ public class NoiseNode : RenderNode
     private NoiseType previousNoiseType = Nodes.NoiseType.FractalPerlin;
     private int previousOctaves = -1;
     private VecD previousOffset = new VecD(0d, 0d);
+    private VoronoiFeature previousVoronoiFeature = Nodes.VoronoiFeature.F1;
+    private double previousRandomness = double.NaN;
+    private double previousAngleOffset = double.NaN;
+    
+    private Shader voronoiShader;
 
     private Paint paint = new();
 
@@ -25,7 +30,7 @@ public class NoiseNode : RenderNode
         ColorMatrix.MapAlphaToRedGreenBlue + ColorMatrix.OpaqueAlphaOffset);
 
     public InputProperty<NoiseType> NoiseType { get; }
-
+    
     public InputProperty<VecD> Offset { get; }
     
     public InputProperty<double> Scale { get; }
@@ -33,6 +38,12 @@ public class NoiseNode : RenderNode
     public InputProperty<int> Octaves { get; }
 
     public InputProperty<double> Seed { get; }
+    
+    public InputProperty<VoronoiFeature> VoronoiFeature { get; }
+    
+    public InputProperty<double> Randomness { get; }
+    
+    public InputProperty<double> AngleOffset { get; }
 
     public NoiseNode()
     {
@@ -45,6 +56,13 @@ public class NoiseNode : RenderNode
             .WithRules(validator => validator.Min(1));
 
         Seed = CreateInput(nameof(Seed), "SEED", 0d);
+        
+        VoronoiFeature = CreateInput(nameof(VoronoiFeature), "VORONOI_FEATURE", Nodes.VoronoiFeature.F1);
+        
+        Randomness = CreateInput(nameof(Randomness), "RANDOMNESS", 1d)
+            .WithRules(v => v.Min(0d).Max(1d));
+        
+        AngleOffset = CreateInput(nameof(AngleOffset), "ANGLE_OFFSET", 0d);
     }
 
     protected override void OnPaint(RenderContext context, DrawingSurface target)
@@ -54,6 +72,9 @@ public class NoiseNode : RenderNode
             || previousOctaves != Octaves.Value
             || previousNoiseType != NoiseType.Value
             || previousOffset != Offset.Value
+            || previousVoronoiFeature != VoronoiFeature.Value
+            || Math.Abs(previousRandomness - Randomness.Value) > 0.000001
+            || Math.Abs(previousAngleOffset - AngleOffset.Value) > 0.000001
             || double.IsNaN(previousScale))
         {
             if (Scale.Value < 0.000001)
@@ -67,6 +88,10 @@ public class NoiseNode : RenderNode
                 return;
             }
 
+            if (paint.Shader != voronoiShader)
+            {
+                paint?.Shader?.Dispose();
+            }
             paint.Shader = shader;
 
             // Define a grayscale color filter to apply to the image
@@ -76,6 +101,9 @@ public class NoiseNode : RenderNode
             previousSeed = Seed.Value;
             previousOctaves = Octaves.Value;
             previousNoiseType = NoiseType.Value;
+            previousVoronoiFeature = VoronoiFeature.Value;
+            previousRandomness = Randomness.Value;
+            previousAngleOffset = AngleOffset.Value;
         }
 
         RenderNoise(target);
@@ -101,7 +129,11 @@ public class NoiseNode : RenderNode
         {
             return false;
         }
-        
+
+        if (paint.Shader != voronoiShader)
+        {
+            paint?.Shader?.Dispose();
+        }
         paint.Shader = shader;
         paint.ColorFilter = grayscaleFilter;
         
@@ -122,17 +154,145 @@ public class NoiseNode : RenderNode
                 (float)(1d / Scale.Value),
                 (float)(1d / Scale.Value),
                 octaves, (float)Seed.Value),
+            Nodes.NoiseType.Voronoi => GetVoronoiShader((float)(1d / (Scale.Value)), octaves, (float)Seed.Value, (int)VoronoiFeature.Value, (float)Randomness.Value, (float)AngleOffset.Value),
             _ => null
         };
 
         return shader;
     }
 
+    private Shader GetVoronoiShader(float frequency, int octaves, float seed, int feature, float randomness, float angleOffset)
+    {
+        string voronoiShaderCode = """
+                                   uniform float iSeed;
+                                   uniform float iFrequency;
+                                   uniform int iOctaves;
+                                   uniform float iRandomness;
+                                   uniform int iFeature;
+                                   uniform float iAngleOffset;
+                                   
+                                   const int MAX_OCTAVES = 8;
+                                   const float LARGE_NUMBER = 1e9;
+                                   const float FEATURE_SEED_SCALE = 10.0;
+                                   const float PI = 3.14159265;
+                                   
+                                   float hashPoint(float2 p, float seed) {
+                                       p = fract(p * float2(0.3183099, 0.3678794) + seed);
+                                       p += dot(p, p.yx + 19.19);
+                                       return fract(p.x * p.y);
+                                   }
+                                   
+                                   float2 getFeaturePoint(float2 cell, float seed, float randomness, float angleOffset) {
+                                       float2 randomCellOffset = float2(
+                                           hashPoint(cell, seed),
+                                           hashPoint(cell, seed + 17.0)
+                                       );
+                                       
+                                       float2 featurePoint = mix(float2(0.5, 0.5), randomCellOffset, randomness);
+                                       
+                                       float angle = hashPoint(cell, seed + 53.0) * PI * 2.0;
+                                       angle += angleOffset;
+                                       
+                                       float2 dir = float2(cos(angle), sin(angle));
+                                       float offsetAmount = 0.15;
+                                       featurePoint += dir * offsetAmount * randomness;
+                                       
+                                       featurePoint = clamp(featurePoint, 0.0, 1.0);
+                                       
+                                       return featurePoint;
+                                   }
+                                   
+                                   float2 getVoronoiDistances(float2 pos, float seed) {
+                                       float2 cell = floor(pos);
+                                       float minDist = LARGE_NUMBER;
+                                       float secondMinDist = LARGE_NUMBER;
+                                   
+                                       for (int y = -1; y <= 1; y++) {
+                                           for (int x = -1; x <= 1; x++) {
+                                               float2 neighborCell = cell + float2(float(x), float(y));
+                                               float2 featurePoint = getFeaturePoint(neighborCell, seed, iRandomness, iAngleOffset);
+                                               float2 delta = pos - (neighborCell + featurePoint);
+                                               float dist = length(delta);
+                                               
+                                               if (dist < minDist) {
+                                                   secondMinDist = minDist;
+                                                   minDist = dist;
+                                               } else if (dist < secondMinDist) {
+                                                   secondMinDist = dist;
+                                               }
+                                           }
+                                       }
+                                       return float2(minDist, secondMinDist);
+                                   }
+                                   
+                                   half4 main(float2 uv) {
+                                       float noiseSum = 0.0;
+                                       float amplitude = 1.0;
+                                       float amplitudeSum = 0.0;
+                                   
+                                       for (int octave = 0; octave < MAX_OCTAVES; octave++) {
+                                           if (octave >= iOctaves) break;
+                                   
+                                           float freq = iFrequency * exp2(float(octave));
+                                           float2 samplePos = uv * freq;
+                                           
+                                           float dist = 0.0;
+                                           float2 distances = getVoronoiDistances(samplePos, iSeed + float(octave) * FEATURE_SEED_SCALE);
+                                           float f1 = distances.x;
+                                           float f2 = distances.y;
+                                           
+                                           if (iFeature == 0) {
+                                               dist = f1;
+                                           }
+                                           else if (iFeature == 1) {
+                                               dist = f2;
+                                           }
+                                           else if (iFeature == 2) {
+                                               dist = f2 - f1;
+                                           }
+                                   
+                                           noiseSum += dist * amplitude;
+                                           amplitudeSum += amplitude;
+                                           amplitude *= 0.5;
+                                       }
+                                   
+                                       return half4(noiseSum / amplitudeSum);
+                                   }
+                                   """;
+        
+        Uniforms uniforms = new Uniforms();
+        uniforms.Add("iSeed", new Uniform("iSeed", seed));
+        uniforms.Add("iFrequency", new Uniform("iFrequency", frequency));
+        uniforms.Add("iOctaves", new Uniform("iOctaves", octaves));
+        uniforms.Add("iRandomness", new Uniform("iRandomness", randomness));
+        uniforms.Add("iFeature", new Uniform("iFeature", feature));
+        uniforms.Add("iAngleOffset", new Uniform("iAngleOffset", angleOffset));
+
+        if (voronoiShader == null)
+        {
+            voronoiShader = Shader.Create(voronoiShaderCode, uniforms, out _);
+        }
+        else
+        {
+            voronoiShader = voronoiShader.WithUpdatedUniforms(uniforms);
+        }
+        
+        return voronoiShader;
+    }
+
     public override Node CreateCopy() => new NoiseNode();
 }
 
 public enum NoiseType
 {
     TurbulencePerlin,
-    FractalPerlin
+    FractalPerlin,
+    Voronoi
+}
+
+public enum VoronoiFeature
+{
+    F1 = 0, // Distance to the closest feature point
+    F2 = 1, // Distance to the second-closest feature point
+    F2MinusF1 = 2
 }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -56,6 +56,7 @@ public class OutputNode : Node, IRenderInput, IPreviewRenderable
         }
 
         int saved = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Input.Value.Paint(context, renderOn);
 
         renderOn.Canvas.RestoreToCount(saved);

+ 11 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs

@@ -66,7 +66,15 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
                 surface.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
             }
 
-            surface.Canvas.DrawSurface(target, 0, 0);
+            if (context.DesiredSamplingOptions != SamplingOptions.Default)
+            {
+                using var snapshot = target.Snapshot();
+                surface.Canvas.DrawImage(snapshot, 0, 0, context.DesiredSamplingOptions);
+            }
+            else
+            {
+                surface.Canvas.DrawSurface(target, 0, 0);
+            }
 
             if (RendersInAbsoluteCoordinates)
             {
@@ -85,7 +93,9 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
     public virtual bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
     {
+        int saved = renderOn.Canvas.Save();
         OnPaint(context, renderOn);
+        renderOn.Canvas.RestoreToCount(saved);
         return true;
     }
 

+ 10 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs

@@ -104,7 +104,8 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         Uniforms uniforms;
         uniforms = new Uniforms();
 
-        VecI finalSize = (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
+        bool isAdjusted = context.RenderOutputSize == context.DocumentSize;
+        VecI finalSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
 
         uniforms.Add("iResolution", new Uniform("iResolution", (VecD)finalSize));
         uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
@@ -123,8 +124,11 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         int saved = texture.DrawingSurface.Canvas.Save();
         //texture.DrawingSurface.Canvas.Scale((float)context.ChunkResolution.Multiplier(), (float)context.ChunkResolution.Multiplier());
 
-        var ctx = new RenderContext(texture.DrawingSurface, context.FrameTime, ChunkResolution.Full, finalSize,
-            context.DocumentSize, context.ProcessingColorSpace, context.Opacity);
+        var ctx = context.Clone();
+        ctx.RenderSurface = texture.DrawingSurface;
+        ctx.RenderOutputSize = finalSize;
+        ctx.ChunkResolution = ChunkResolution.Full;
+
         Background.Value.Paint(ctx, texture.DrawingSurface);
         texture.DrawingSurface.Canvas.RestoreToCount(saved);
 
@@ -156,8 +160,10 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
 
         if (context.ChunkResolution != ChunkResolution.Full)
         {
+            bool isAdjusted = context.RenderOutputSize == context.DocumentSize;
+            VecI finalSize = isAdjusted ? context.RenderOutputSize : (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier());
             var intermediateSurface = RequestTexture(51,
-                (VecI)(context.RenderOutputSize * context.ChunkResolution.InvertedMultiplier()),
+                finalSize,
                 ColorSpace.Value == ColorSpaceType.Inherit
                     ? context.ProcessingColorSpace
                     : ColorSpace.Value == ColorSpaceType.Srgb

+ 32 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/LineNode.cs

@@ -0,0 +1,32 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.ChangeableDocument.Rendering;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+
+[NodeInfo("Line")]
+public class LineNode : ShapeNode<LineVectorData>
+{
+    public InputProperty<VecD> Start { get; }
+    public InputProperty<VecD> End { get; }
+    public InputProperty<Paintable> StrokeColor { get; }
+    public InputProperty<double> StrokeWidth { get; }
+
+    public LineNode()
+    {
+        Start = CreateInput<VecD>("LineStart", "LINE_START", VecD.Zero);
+        End = CreateInput<VecD>("LineEnd", "LINE_END", new VecD(32, 32));
+        StrokeColor = CreateInput<Paintable>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
+        StrokeWidth = CreateInput<double>("StrokeWidth", "STROKE_WIDTH", 1);
+    }
+
+    protected override LineVectorData? GetShapeData(RenderContext context)
+    {
+        return new LineVectorData(Start.Value, End.Value)
+            { Stroke = StrokeColor.Value, StrokeWidth = (float)StrokeWidth.Value };
+    }
+
+    public override Node CreateCopy() => new LineNode();
+}

+ 37 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RectangleNode.cs

@@ -0,0 +1,37 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.ChangeableDocument.Rendering;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+
+[NodeInfo("Rectangle")]
+public class RectangleNode : ShapeNode<RectangleVectorData>
+{
+    public InputProperty<VecD> Center { get; }
+    public InputProperty<VecD> Size { get; }
+    public InputProperty<double> CornerRadius { get; }
+    public InputProperty<Paintable> StrokeColor { get; }
+    public InputProperty<Paintable> FillColor { get; }
+    public InputProperty<double> StrokeWidth { get; }
+
+    public RectangleNode()
+    {
+        Center = CreateInput<VecD>("Position", "POSITION", VecI.Zero);
+        Size = CreateInput<VecD>("Size", "SIZE", new VecD(32, 32)).WithRules(
+            v => v.Min(new VecD(0)));
+        CornerRadius = CreateInput<double>("CornerRadius", "RADIUS", 0);
+        StrokeColor = CreateInput<Paintable>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
+        FillColor = CreateInput<Paintable>("FillColor", "FILL_COLOR", new Color(0, 0, 0, 255));
+        StrokeWidth = CreateInput<double>("StrokeWidth", "STROKE_WIDTH", 1);
+    }
+
+    protected override RectangleVectorData? GetShapeData(RenderContext context)
+    {
+        return new RectangleVectorData(Center.Value, Size.Value)
+            { CornerRadius = CornerRadius.Value, Stroke = StrokeColor.Value, FillPaintable = FillColor.Value, StrokeWidth = (float)StrokeWidth.Value };
+    }
+
+    public override Node CreateCopy() => new RectangleNode();
+}

+ 7 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs

@@ -184,8 +184,7 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
             context.FrameTime, context.ChunkResolution, context.RenderOutputSize, context.DocumentSize, renderTarget == context.RenderSurface,
-            context.ProcessingColorSpace,
-            context.Opacity);
+            context.ProcessingColorSpace, context.DesiredSamplingOptions, context.Opacity);
         renderObjectContext.FullRerender = context.FullRerender;
         return renderObjectContext;
     }
@@ -247,7 +246,12 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
         VecI targetSize = img.LatestSize;
 
-        var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
+        if (targetSize.X <= 0 || targetSize.Y <= 0)
+        {
+            return;
+        }
+
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
         renderSurface = RequestTexture(textureId, targetSize, processingColorSpace, false);
 
         int saved = renderSurface.DrawingSurface.Canvas.Save();
@@ -265,7 +269,6 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         }
 
         renderSurface.DrawingSurface.Canvas.RestoreToCount(saved);
-        ctx?.Dispose();
     }
 
     protected void ApplyRasterClip(DrawingSurface toClip, DrawingSurface clipSource)

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TextureCache.cs

@@ -14,7 +14,7 @@ public class TextureCache : IDisposable
     {
         if (_managedTextures.TryGetValue(id, out var texture))
         {
-            if (texture.Size != size || texture.IsDisposed || texture.ColorSpace != processingCs)
+            if (texture.Size != size || texture.IsDisposed || !texture.ColorSpace.Equals(processingCs))
             {
                 texture.Dispose();
                 texture = new Texture(CreateImageInfo(size, processingCs));

+ 8 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs

@@ -132,7 +132,11 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
             return false;
         }
 
+
+        int savedCount = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Rasterize(renderOn, paint);
+        renderOn.Canvas.RestoreToCount(savedCount);
 
         return true;
     }
@@ -188,8 +192,9 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
     {
         int layer;
         // TODO: This can be further optimized by passing opacity, blend mode and filters directly to the rasterization method
-        if (paint != null && (paint.Color.A < 255 || paint.ColorFilter != null || paint.ImageFilter != null || paint.Shader != null ||
-            paint.BlendMode != Drawie.Backend.Core.Surfaces.BlendMode.SrcOver))
+        if (paint != null && (paint.Color.A < 255 || paint.ColorFilter != null || paint.ImageFilter != null ||
+                              paint.Shader != null ||
+                              paint.BlendMode != Drawie.Backend.Core.Surfaces.BlendMode.SrcOver))
         {
             layer = surface.Canvas.SaveLayer(paint);
         }
@@ -197,7 +202,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
         {
             layer = surface.Canvas.Save();
         }
-        
+
         RenderableShapeData?.RasterizeTransformed(surface.Canvas);
 
         surface.Canvas.RestoreToCount(layer);

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Surfaces;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Rendering;
@@ -33,7 +34,7 @@ internal class EvaluateGraph_Change : Change
         using Texture renderTexture = Texture.ForProcessing(target.Size, target.ProcessingColorSpace);
         RenderContext context =
             new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, target.Size, target.Size,
-                target.ProcessingColorSpace) { FullRerender = true };
+                target.ProcessingColorSpace, SamplingOptions.Default) { FullRerender = true };
         foreach (var nodeToEvaluate in queue)
         {
             nodeToEvaluate.Execute(context);

+ 21 - 6
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -21,6 +21,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
 {
     private Queue<RenderRequest> renderRequests = new();
     private Texture renderTexture;
+    private int lastExecutedGraphFrame = -1;
 
     public DocumentRenderer(IReadOnlyDocument document)
     {
@@ -68,7 +69,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
         RenderContext context = new(renderTexture.DrawingSurface, frame, resolution, Document.Size, Document.Size,
-            Document.ProcessingColorSpace);
+            Document.ProcessingColorSpace, SamplingOptions.Default);
         context.FullRerender = true;
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
         try
@@ -114,7 +115,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
         RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size, Document.Size,
-            Document.ProcessingColorSpace);
+            Document.ProcessingColorSpace, SamplingOptions.Default);
         context.FullRerender = true;
 
         node.RenderForOutput(context, toRenderOn, null);
@@ -134,7 +135,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
 
         renderRequests.Enqueue(request);
-        ExecuteRenderRequests();
+        ExecuteRenderRequests(context.FrameTime);
 
         return await tcs.Task;
     }
@@ -201,8 +202,12 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         IsBusy = true;
 
         renderOn.Canvas.Clear();
+        int savedCount = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         context.RenderSurface = renderOn;
         Document.NodeGraph.Execute(context);
+        lastExecutedGraphFrame = context.FrameTime.Frame;
+        renderOn.Canvas.RestoreToCount(savedCount);
 
         IsBusy = false;
 
@@ -236,8 +241,9 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
             : Document.NodeGraph;
 
         RenderContext context =
-            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, SolveRenderOutputSize(customOutput, graph, Document.Size),
-                Document.Size, Document.ProcessingColorSpace) { FullRerender = true };
+            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full,
+                SolveRenderOutputSize(customOutput, graph, Document.Size),
+                Document.Size, Document.ProcessingColorSpace, SamplingOptions.Default) { FullRerender = true };
 
         if (hasCustomOutput)
         {
@@ -264,19 +270,28 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         renderTexture.DrawingSurface.Canvas.Restore();
         toRenderOn.Canvas.Restore();
 
+        lastExecutedGraphFrame = frameTime.Frame;
+
         IsBusy = false;
     }
 
-    private void ExecuteRenderRequests()
+    private void ExecuteRenderRequests(KeyFrameTime frameTime)
     {
         if (isExecuting) return;
 
         isExecuting = true;
         using var ctx = DrawingBackendApi.Current?.RenderingDispatcher.EnsureContext();
+
         while (renderRequests.Count > 0)
         {
             RenderRequest request = renderRequests.Dequeue();
 
+            if (frameTime.Frame != lastExecutedGraphFrame && request.PreviewRenderable != this)
+            {
+                using Texture executeSurface = Texture.ForDisplay(new VecI(1));
+                RenderDocument(executeSurface.DrawingSurface, frameTime, VecI.One);
+            }
+
             try
             {
                 bool result = true;

+ 5 - 3
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -13,6 +13,7 @@ public class RenderContext
 
     public KeyFrameTime FrameTime { get; }
     public ChunkResolution ChunkResolution { get; set; }
+    public SamplingOptions DesiredSamplingOptions { get; set; } = SamplingOptions.Default;
     public VecI RenderOutputSize { get; set; }
 
     public VecI DocumentSize { get; set; }
@@ -24,7 +25,7 @@ public class RenderContext
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
-        VecI renderOutputSize, VecI documentSize, ColorSpace processingColorSpace, double opacity = 1)
+        VecI renderOutputSize, VecI documentSize, ColorSpace processingColorSpace, SamplingOptions desiredSampling, double opacity = 1)
     {
         RenderSurface = renderSurface;
         FrameTime = frameTime;
@@ -33,6 +34,7 @@ public class RenderContext
         Opacity = opacity;
         ProcessingColorSpace = processingColorSpace;
         DocumentSize = documentSize;
+        DesiredSamplingOptions = desiredSampling;
     }
 
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
@@ -63,10 +65,10 @@ public class RenderContext
 
     public RenderContext Clone()
     {
-        return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, Opacity)
+        return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, DesiredSamplingOptions, Opacity)
         {
             FullRerender = FullRerender,
-            TargetOutput = TargetOutput
+            TargetOutput = TargetOutput,
         };
     }
 }

+ 5 - 0
src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs

@@ -50,4 +50,9 @@ public static class PreferencesConstants
     public const string SecondaryBackgroundColorDefault = "#353535";
     public const string SecondaryBackgroundColor = "SecondaryBackgroundColor";
 
+    public const string MaxBilinearSampleSize = "MaxBilinearSampleSize";
+    public const int MaxBilinearSampleSizeDefault = 4096;
+
+    public const string DisablePreviews = "DisablePreviews";
+    public const bool DisablePreviewsDefault = false;
 }

+ 44 - 17
src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs

@@ -3,10 +3,11 @@
 public static class PixiEditorSettings
 {
     private const string PixiEditor = "PixiEditor";
-    
+
     public static class Palettes
     {
-        public static LocalSetting<IEnumerable<string>> FavouritePalettes { get; } = LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor);
+        public static LocalSetting<IEnumerable<string>> FavouritePalettes { get; } =
+            LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor);
     }
 
     public static class Update
@@ -22,16 +23,20 @@ public static class PixiEditorSettings
 
         public static LocalSetting<string> PoEditorApiKey { get; } = new($"{PixiEditor}:POEditor_API_Key");
     }
-    
+
     public static class Tools
     {
         public static SyncedSetting<bool> EnableSharedToolbar { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
-        public static SyncedSetting<RightClickMode> RightClickMode { get; } = SyncedSetting.NonOwned<RightClickMode>(PixiEditor);
-        
+        public static SyncedSetting<bool> SelectionTintingEnabled { get; } = SyncedSetting.NonOwned(PixiEditor, true);
+
+        public static SyncedSetting<RightClickMode> RightClickMode { get; } =
+            SyncedSetting.NonOwned<RightClickMode>(PixiEditor);
+
         public static SyncedSetting<bool> IsPenModeEnabled { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
-        public static SyncedSetting<string> PrimaryToolset { get; } = SyncedSetting.NonOwned<string>(PixiEditor, "PAINT_TOOLSET");
+        public static SyncedSetting<string> PrimaryToolset { get; } =
+            SyncedSetting.NonOwned<string>(PixiEditor, "PAINT_TOOLSET");
     }
 
     public static class File
@@ -39,12 +44,13 @@ public static class PixiEditorSettings
         public static SyncedSetting<int> DefaultNewFileWidth { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
 
         public static SyncedSetting<int> DefaultNewFileHeight { get; } = SyncedSetting.NonOwned(PixiEditor, 64);
-        
-        public static LocalSetting<IEnumerable<string>> RecentlyOpened { get; } = LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor, []);
-    
+
+        public static LocalSetting<IEnumerable<string>> RecentlyOpened { get; } =
+            LocalSetting.NonOwned<IEnumerable<string>>(PixiEditor, []);
+
         public static SyncedSetting<int> MaxOpenedRecently { get; } = SyncedSetting.NonOwned(PixiEditor, 8);
     }
-    
+
     public static class StartupWindow
     {
         public static SyncedSetting<bool> ShowStartupWindow { get; } = SyncedSetting.NonOwned(PixiEditor, true);
@@ -53,9 +59,10 @@ public static class PixiEditorSettings
 
         public static SyncedSetting<bool> NewsPanelCollapsed { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
-        public static SyncedSetting<IEnumerable<int>> LastCheckedNewsIds { get; } = SyncedSetting.NonOwned<IEnumerable<int>>(PixiEditor);
+        public static SyncedSetting<IEnumerable<int>> LastCheckedNewsIds { get; } =
+            SyncedSetting.NonOwned<IEnumerable<int>>(PixiEditor);
     }
-    
+
     public static class Discord
     {
         public static SyncedSetting<bool> EnableRichPresence { get; } = SyncedSetting.NonOwned(PixiEditor, true);
@@ -74,12 +81,32 @@ public static class PixiEditorSettings
 
     public static class Scene
     {
-        public static SyncedSetting<bool> AutoScaleBackground { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.AutoScaleBackgroundDefault, PreferencesConstants.AutoScaleBackground);
-        public static SyncedSetting<double> CustomBackgroundScaleX { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleX);
-        public static SyncedSetting<double> CustomBackgroundScaleY { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleY);
+        public static SyncedSetting<bool> AutoScaleBackground { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.AutoScaleBackgroundDefault, PreferencesConstants.AutoScaleBackground);
+
+        public static SyncedSetting<double> CustomBackgroundScaleX { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleX);
+
+        public static SyncedSetting<double> CustomBackgroundScaleY { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.CustomBackgroundScaleDefault, PreferencesConstants.CustomBackgroundScaleY);
 
-        public static SyncedSetting<string> PrimaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.PrimaryBackgroundColorDefault, PreferencesConstants.PrimaryBackgroundColor);
-        public static SyncedSetting<string> SecondaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor, PreferencesConstants.SecondaryBackgroundColorDefault, PreferencesConstants.SecondaryBackgroundColor);
+        public static SyncedSetting<string> PrimaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.PrimaryBackgroundColorDefault, PreferencesConstants.PrimaryBackgroundColor);
 
+        public static SyncedSetting<string> SecondaryBackgroundColor { get; } = SyncedSetting.NonOwned(PixiEditor,
+            PreferencesConstants.SecondaryBackgroundColorDefault, PreferencesConstants.SecondaryBackgroundColor);
+    }
+
+    public static class Performance
+    {
+        public static SyncedSetting<int> MaxBilinearSampleSize { get; } = SyncedSetting.NonOwned(
+            PixiEditor,
+            PreferencesConstants.MaxBilinearSampleSizeDefault,
+            PreferencesConstants.MaxBilinearSampleSize);
+
+        public static SyncedSetting<bool> DisablePreviews { get; } = SyncedSetting.NonOwned(
+            PixiEditor,
+            PreferencesConstants.DisablePreviewsDefault,
+            PreferencesConstants.DisablePreviews);
     }
 }

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


+ 1 - 0
src/PixiEditor.IdentityProvider.PixiAuth/PixiAuthIdentityProvider.cs

@@ -129,6 +129,7 @@ public class PixiAuthIdentityProvider : IIdentityProvider
             Error(e.Message, e.TimeLeft);
             LoginTimeout?.Invoke(e.TimeLeft);
         }
+        // Can produce SESSION_NOT_FOUND or USER_NOT_FOUND as a message, this comment ensure it's catched by the localization key checker
         catch (PixiAuthException authException)
         {
             Error(authException.Message);

+ 4 - 0
src/PixiEditor.UI.Common/Accents/Base.axaml

@@ -60,6 +60,8 @@
             <Color x:Key="IntSocketColor">#4C64B1</Color>
             <Color x:Key="StringSocketColor">#C9E4C6</Color>
             <Color x:Key="EllipseDataSocketColor">#a473a5</Color>
+            <Color x:Key="LineDataSocketColor">#816382</Color>
+            <Color x:Key="RectangleDataSocketColor">#825e8a</Color>
             <Color x:Key="PointsDataSocketColor">#e1d0e1</Color>
             <Color x:Key="TextDataSocketColor">#f2f2f2</Color>
             <Color x:Key="Matrix3X3SocketColor">#ffea4f</Color>
@@ -174,6 +176,8 @@
             <ConicGradientBrush x:Key="ShapeVectorDataSocketBrush"
                                 GradientStops="{StaticResource ShapeDataSocketGradient}" />
             <SolidColorBrush x:Key="EllipseVectorDataSocketBrush" Color="{StaticResource EllipseDataSocketColor}" />
+            <SolidColorBrush x:Key="LineVectorDataSocketBrush" Color="{StaticResource LineDataSocketColor}" />
+            <SolidColorBrush x:Key="RectangleVectorDataSocketBrush" Color="{StaticResource RectangleDataSocketColor}" />
             <SolidColorBrush x:Key="PointsVectorDataSocketBrush" Color="{StaticResource PointsDataSocketColor}" />
             <SolidColorBrush x:Key="TextVectorDataSocketBrush" Color="{StaticResource TextDataSocketColor}" />
 

+ 0 - 1
src/PixiEditor.sln

@@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildConfiguration", "Build
 	ProjectSection(SolutionItems) = preProject
 		Custom.ruleset = Custom.ruleset
 		Directory.Build.props = Directory.Build.props
-		stylecop.json = stylecop.json
 	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLib", "ChunkyImageLib\ChunkyImageLib.csproj", "{6A9DA760-1E47-414C-B8E8-3B4927F18131}"

+ 124 - 124
src/PixiEditor/Data/Localization/Languages/ar.json

@@ -8,16 +8,16 @@
   "DISCORD": "ديسكورد",
   "KEY_BINDINGS": "ارتباطات المفاتيح",
   "MISC": "متنوع",
-  "SHOW_STARTUP_WINDOW": "عرض نافذة بدء التشغيل",
+  "SHOW_STARTUP_WINDOW": "عرض نافذة بدئ التشغيل",
   "RECENT_FILE_LENGTH": "طول قائمة الملفات الاخيرة",
   "RECENT_FILE_LENGTH_TOOLTIP": "كم عدد المستندات التي يتم اضهارها في ملف > الملفات الاخيرة. الافتراضي: 8",
   "DEFAULT_NEW_SIZE": "الحجم الافتراضي للملف الجديد",
   "WIDTH": "العرض",
   "HEIGHT": "الطول",
-  "TOOLS": "ألادوات",
+  "TOOLS": "الأدوات",
   "ENABLE_SHARED_TOOLBAR": "تمكين شريط الادوات المشترك",
   "AUTOMATIC_UPDATES": "التحديثات التلقائية",
-  "CHECK_FOR_UPDATES": "تحقق من التحديثات عند بدء التشغيل",
+  "CHECK_FOR_UPDATES": "تحقق من التحديثات عند بدئ التشغيل",
   "UPDATE_STREAM": "مصدر التحديث",
   "UPDATE_CHANNEL_HELP_TOOLTIP": "لا يمكن تغيير قنوات التحديث إلا في إصدار مستقل (يتم تنزيله من https://pixieditor.net).\nتتعامل إصدارات Steam و Microsoft Store مع التحديثات بشكل منفصل.",
   "DEBUG": "معالجة",
@@ -25,14 +25,14 @@
   "OPEN_CRASH_REPORTS_DIR": "افتح دليل تقارير الاعطال",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "ENABLED": "مفعل",
-  "SHOW_IMAGE_NAME": "اضهار اسم الصورة",
-  "SHOW_IMAGE_SIZE": "اضهار حجم الصورة",
-  "SHOW_LAYER_COUNT": "اضهار عدد الطبقات",
+  "SHOW_IMAGE_NAME": "إظهار اسم الصورة",
+  "SHOW_IMAGE_SIZE": "إظهار حجم الصورة",
+  "SHOW_LAYER_COUNT": "إظهار عدد الطبقات",
   "FILE": "ملف",
   "RECENT": "مؤخرًا",
   "OPEN": "فتح",
-  "SAVE_PIXI": "حفض ( .pixi )",
-  "SAVE_AS_PIXI": "حفض جديد ك ( .pixi )",
+  "SAVE_PIXI": "حفظ ( .pixi )",
+  "SAVE_AS_PIXI": "حفظ جديد كـ( .pixi )",
   "EXPORT_IMG": "تصدير (png, .jpg., الخ.)",
   "EDIT": "تعديل",
   "EXIT": "خروج",
@@ -85,7 +85,7 @@
   "PATH_DOES_NOT_EXIST": "{0} غير موجود.",
   "LOCATION_DOES_NOT_EXIST": "الموقع غير موجود.",
   "FILE_NOT_FOUND": "لم يتم العثور على الملف.",
-  "ARE_YOU_SURE": "هل انت متاكد؟",
+  "ARE_YOU_SURE": "هل انت متأكد؟",
   "ARE_YOU_SURE_PATH_FULL_PATH": "هل أنت متأكد أنك تريد حذف {0}؟\nستفقد هذه البيانات لجميع التركيبات.\n(مسار كامل: {1})",
   "FAILED_TO_OPEN_FILE": "فشل في فتح الملف",
   "OLD_FILE_FORMAT": "تنسيق الملف القديم",
@@ -93,14 +93,14 @@
   "NOTHING_FOUND": "لم يتم العثور على شيء",
   "EXPORT": "تصدير",
   "EXPORT_IMAGE": "تصدير الصورة",
-  "IMPORT": "اضافة",
-  "SHORTCUT_TEMPLATES": "اختصارات القوالب",
+  "IMPORT": "إضافة",
+  "SHORTCUT_TEMPLATES": "إختصارات القوالب",
   "RESET_ALL": "إعادة ضبط الكل",
   "LAYER": "طبقة",
   "LAYER_DELETE_SELECTED": "حذف الطبقة/المجلد النشط",
   "LAYER_DELETE_SELECTED_DESCRIPTIVE": "حذف الطبقة أو المجلد النشط",
-  "LAYER_DELETE_ALL_SELECTED": "احذف جميع الطبقات / المجلدات المحددة",
-  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "احذف كل الطبقات و / أو المجلدات المحددة",
+  "LAYER_DELETE_ALL_SELECTED": "حذف جميع الطبقات / المجلدات المحددة",
+  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "حذف كل الطبقات و / أو المجلدات المحددة",
   "DELETE_SELECTED_PIXELS": "حذف البكسلات المحددة",
   "NEW_FOLDER": "مجلد جديد",
   "CREATE_NEW_FOLDER": "انشاء مجلد جديد",
@@ -108,34 +108,34 @@
   "CREATE_NEW_LAYER": "انشاء طبقة جديدة",
   "NEW_IMAGE": "صورة جديدة",
   "CREATE_NEW_IMAGE": "انشاء صورة جديدة",
-  "SAVE": "حفض",
-  "SAVE_AS": "حفض جديد...",
+  "SAVE": "حفظ",
+  "SAVE_AS": "حفظ جديد...",
   "IMAGE": "صورة",
-  "SAVE_IMAGE": "حفض الصورة",
-  "SAVE_IMAGE_AS": "حفض الصورة كجديدة",
+  "SAVE_IMAGE": "حفظ الصورة",
+  "SAVE_IMAGE_AS": "حفظ الصورة كجديدة",
   "DUPLICATE": "تكرار",
   "DUPLICATE_SELECTED_LAYER": "تكرار الطبقة المجددة",
-  "CREATE_MASK": "انشاء قناع",
-  "DELETE_MASK": "حذف قناع",
+  "CREATE_MASK": "إنشاء قناع",
+  "DELETE_MASK": "حذف القناع",
   "TOGGLE_MASK": "تبديل القناع",
   "APPLY_MASK": "تطبيق القناع",
   "TOGGLE_VISIBILITY": "تبديل الرؤية",
-  "MOVE_MEMBER_UP": "حرك العضو لأعلى",
-  "MOVE_MEMBER_UP_DESCRIPTIVE": "انقل الطبقة أو المجلد المحدد لأعلى",
-  "MOVE_MEMBER_DOWN": "حرك العضو لأسفل",
-  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "انقل الطبقة أو المجلد المحدد لأسفل",
+  "MOVE_MEMBER_UP": "تحريك العضو لأعلى",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "نقل الطبقة أو المجلد المحدد لأعلى",
+  "MOVE_MEMBER_DOWN": "تحريك العضو لأسفل",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "نقل الطبقة أو المجلد المحدد لأسفل",
   "MERGE_ALL_SELECTED_LAYERS": "دمج كل الطبقات المحددة",
-  "MERGE_WITH_ABOVE": "دمج الطبقة المحددة أعلاه",
+  "MERGE_WITH_ABOVE": "دمج الطبقة المحددة مع أعلاها",
   "MERGE_WITH_ABOVE_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة التي فوقها",
-  "MERGE_WITH_BELOW": "دمج الطبقة المحددة أدناه",
+  "MERGE_WITH_BELOW": "دمج الطبقة المحددة مع أدناها",
   "MERGE_WITH_BELOW_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة الموجودة تحتها",
-  "ADD_REFERENCE_LAYER": "أضف طبقة مرجعية",
-  "DELETE_REFERENCE_LAYER": "احذف الطبقة المرجعية",
+  "ADD_REFERENCE_LAYER": "إضافة طبقة مرجعية",
+  "DELETE_REFERENCE_LAYER": "حذف الطبقة المرجعية",
   "TRANSFORM_REFERENCE_LAYER": "تحويل الطبقة المرجعية",
   "TOGGLE_REFERENCE_LAYER_POS": "تبديل موضع الطبقة المرجعية",
   "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "تبديل الطبقة المرجعية بين الأعلى أو الأكثر أدناه",
   "RESET_REFERENCE_LAYER_POS": "إعادة تعيين موضع الطبقة المرجعية",
-  "CLIP_CANVAS": "مقطع الصورة",
+  "CLIP_CANVAS": "حف الصورة",
   "FLIP_IMG_VERTICALLY": "قلب الصورة عموديًا",
   "FLIP_IMG_HORIZONTALLY": "قلب الصورة أفقيًا",
   "FLIP_LAYERS_VERTICALLY": "قلب الطبقات المحددة عموديًا",
@@ -149,40 +149,40 @@
   "TOGGLE_VERT_SYMMETRY_AXIS": "تبديل محور التناظر العمودي",
   "TOGGLE_HOR_SYMMETRY_AXIS": "تبديل محور التناظر الأفقي",
   "RESIZE_DOCUMENT": "تغيير حجم العنصر",
-  "RESIZE_CANVAS": "تغيير حجم الصورة",
+  "RESIZE_CANVAS": "تغيير حجم اللوحة",
   "CENTER_CONTENT": "توسيط المحتوى",
   "CUT": "قص",
   "CUT_DESCRIPTIVE": "قص المنطقة/الطبقات المحددة",
   "PASTE": "لصق",
   "PASTE_DESCRIPTIVE": "لصق محتويات الحافظة",
   "PASTE_AS_NEW_LAYER": "لصق كطبقة جديدة",
-  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "لصق من الحافضةكطبقة جديدة",
+  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "لصق من الحافظة كطبقة جديدة",
   "PASTE_REFERENCE_LAYER": "لصق الطبقة المرجعية",
   "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "لصق محتويات الحافظة كطبقة مرجعية",
   "PASTE_COLOR": "لصق اللون",
   "PASTE_COLOR_DESCRIPTIVE": "لصق اللون من الحافظة",
-  "PASTE_COLOR_SECONDARY": "لصق اللون على أنه ثانوي",
-  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "الصق اللون من الحافظة كلون ثانوي",
+  "PASTE_COLOR_SECONDARY": "لصق اللون كلون ثانوي",
+  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "لصق اللون من الحافظة كلون ثانوي",
   "CLIPBOARD": "الحافظة",
   "COPY": "نسخ",
-  "COPY_DESCRIPTIVE": "نسخ الي الحافضة",
+  "COPY_DESCRIPTIVE": "نسخ الي الحافظة",
   "COPY_COLOR_HEX": "نسخ اللون الاساسي (HEX)",
-  "COPY_COLOR_HEX_DESCRIPTIVE": "نسخ اللون الاساسي ك HEX",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "نسخ اللون الاساسي كـ HEX",
   "COPY_COLOR_RGB": "نسخ اللون الاساسي (RGB)",
-  "COPY_COLOR_RGB_DESCRIPTIVE": "نسخ اللون الاساسي ك RGB",
+  "COPY_COLOR_RGB_DESCRIPTIVE": "نسخ اللون الاساسي كـ RGB",
   "COPY_COLOR_SECONDARY_HEX": "نسخ اللون الثانوي (HEX)",
-  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "نسخ اللون الثانوي ك HEX",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "نسخ اللون الثانوي كـ HEX",
   "COPY_COLOR_SECONDARY_RGB": "نسخ اللون الثانوي (RGB)",
-  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "نسخ اللون الثانوي ك RGB",
-  "PALETTE_COLORS": "لوحة الالوان",
-  "REPLACE_SECONDARY_BY_PRIMARY": "استبدل اللون الثانوي باللون الأساسي",
-  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "استبدل اللون الثانوي باللون الأساسي",
-  "REPLACE_PRIMARY_BY_SECONDARY": "استبدل اللون الأساسي باللون الثانوي",
-  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "استبدل اللون الأساسي باللون الثانوي",
-  "OPEN_PALETTE_BROWSER": "افتح متصفح لوحة الألوان",
+  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "نسخ اللون الثانوي كـ RGB",
+  "PALETTE_COLORS": "لوحة الألوان",
+  "REPLACE_SECONDARY_BY_PRIMARY": "استبدال اللون الأساسي بالثانوي",
+  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "استبدال اللون الأساسي باللون الثانوي",
+  "REPLACE_PRIMARY_BY_SECONDARY": "استبدال اللون الثانوي بالأساسي",
+  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "استبدال اللون الثانوي باللون الأساسي",
+  "OPEN_PALETTE_BROWSER": "فتح متصفح لوحة الألوان",
   "OVERWRITE_PALETTE_CONSENT": "اللوحة '{0}' موجودة بالفعل ، هل تريد استبدالها؟",
   "PALETTE_EXISTS": "لوح الألوان موجود بالفعل",
-  "REPLACE_PALETTE_CONSENT": "هل تريد استبدال اللوحة الحالية باللوحة المحددة؟",
+  "REPLACE_PALETTE_CONSENT": "هل تريد استبدال اللوحة المحددة باللوحة الحالية؟",
   "REPLACE_PALETTE": "استبدال اللوحة الحالية",
   "SELECT_COLOR_1": "حدد اللون 1",
   "SELECT_COLOR_2": "حدد اللون 2",
@@ -194,7 +194,7 @@
   "SELECT_COLOR_8": "حدد اللون 8",
   "SELECT_COLOR_9": "حدد اللون 9",
   "SELECT_COLOR_10": "حدد اللون 10",
-  "SELECT_TOOL": "حدد الاداة {0}",
+  "SELECT_TOOL": "حدد الأداة {0}",
   "SELECT_COLOR_1_DESCRIPTIVE": "حدد اللون الأول في اللوحة",
   "SELECT_COLOR_2_DESCRIPTIVE": "حدد اللون الثاني في اللوحة",
   "SELECT_COLOR_3_DESCRIPTIVE": "حدد اللون الثالث في اللوحة",
@@ -208,23 +208,23 @@
   "SWAP_COLORS": "تبديل الألوان",
   "SWAP_COLORS_DESCRIPTIVE": "تبديل الألوان الأساسية والثانوية",
   "SEARCH": "بحث",
-  "COMMAND_SEARCH": "اوامر البحث",
-  "OPEN_COMMAND_SEARCH": "فتح نافذة اوامر البحث",
+  "COMMAND_SEARCH": "أوامر البحث",
+  "OPEN_COMMAND_SEARCH": "فتح نافذة أوامر البحث",
   "SELECT": "حدد",
-  "DESELECT": "الغاء التحديد",
+  "DESELECT": "إلغاء التحديد",
   "INVERT": "عكس",
-  "SELECTION": "اختيار",
-  "SELECT_ALL": "حدد الكل",
-  "SELECT_ALL_DESCRIPTIVE": "حدد كل شيء",
+  "SELECTION": "تحديد",
+  "SELECT_ALL": "تحديد الكل",
+  "SELECT_ALL_DESCRIPTIVE": "تحديد كل شيء",
   "CLEAR_SELECTION": "حذف المحدد",
-  "INVERT_SELECTION": "اقلب المحدد",
-  "INVERT_SELECTION_DESCRIPTIVE": "اقلب المنطقة المحددة",
+  "INVERT_SELECTION": "عكس المحدد",
+  "INVERT_SELECTION_DESCRIPTIVE": "عكس المنطقة المحددة",
   "TRANSFORM_SELECTED_AREA": "تحويل المنطقة المحددة",
   "NUDGE_SELECTED_LEFT": "دفع العنصر المحدد لليسار",
   "NUDGE_SELECTED_RIGHT": "دفع العنصر المحدد لليمين",
   "NUDGE_SELECTED_UP": "دفع العنصر المحدد لأعلى",
   "NUDGE_SELECTED_DOWN": "دفع العنصر المحدد لأسفل",
-  "MASK_FROM_SELECTION": "قناع جديد من الاختيار",
+  "MASK_FROM_SELECTION": "قناع جديد من المحدد",
   "MASK_FROM_SELECTION_DESCRIPTIVE": "التحديد لقناع جديد",
   "ADD_SELECTION_TO_MASK": "إضافة التحديد إلى القناع",
   "SUBTRACT_SELECTION_FROM_MASK": "قطع التحديد من القناع",
@@ -232,8 +232,8 @@
   "SELECTION_TO_MASK": "التحديد للقناع",
   "TO_NEW_MASK": "إلى قناع جديد",
   "ADD_TO_MASK": "أضف إلى القناع",
-  "SUBTRACT_FROM_MASK": "قص من القناع",
-  "INTERSECT_WITH_MASK": "تتقاطع مع القناع",
+  "SUBTRACT_FROM_MASK": "إنقاص من القناع",
+  "INTERSECT_WITH_MASK": "تقاطع مع القناع",
   "STYLUS": "قلم",
   "TOGGLE_PEN_MODE": "تبديل وضع القلم",
   "UNDO": "تراجع",
@@ -247,12 +247,12 @@
   "NEW_WINDOW_FOR_IMG": "نافذة جديدة للصورة الحالية",
   "CENTER_ACTIVE_VIEWPORT": "توسيط إطار العرض النشط",
   "FLIP_VIEWPORT_HORIZONTALLY": "قلب إطار العرض أفقيًا",
-  "FLIP_VIEWPORT_VERTICALLY": "قلب إطار العرض رأسيًا",
+  "FLIP_VIEWPORT_VERTICALLY": "قلب إطار العرض عموديًا",
   "SETTINGS": "الاعدادات",
   "OPEN_SETTINGS": "فتح الاعدادات",
   "OPEN_SETTINGS_DESCRIPTIVE": "فتح نافذة الاعدادات",
   "OPEN_STARTUP_WINDOW": "فتح نافذة التشغيل",
-  "OPEN_SHORTCUT_WINDOW": "افتح نافذة الاختصارات",
+  "OPEN_SHORTCUT_WINDOW": "فتح نافذة الاختصارات",
   "OPEN_ABOUT_WINDOW": "فتح نافذة المعلومات",
   "ERROR": "خطأ",
   "INTERNAL_ERROR": "خطأ داخلي",
@@ -266,51 +266,51 @@
   "DONATE": "تبرع",
   "YES": "نعم",
   "NO": "لا",
-  "CANCEL": "الغاء",
+  "CANCEL": "إلغاء",
   "UNNAMED": "بدون اسم",
-  "OPEN_COMMAND_DEBUG_WINDOW": "افتح نافذة تصحيح أخطاء الأمر",
+  "OPEN_COMMAND_DEBUG_WINDOW": "افتح نافذة معالجة أخطاء الأمر",
   "DELETE": "حذف",
   "USER_PREFS": "تفضيلات المستخدم (Roaming)",
-  "SHORTCUT_FILE": "اختصار الملف (Roaming)",
+  "SHORTCUT_FILE": "ملف الاختصارات (Roaming)",
   "EDITOR_DATA": "بيانات البرنامج (Local)",
   "MOVE_VIEWPORT_TOOLTIP": "ينقل إطار العرض. ({0})",
-  "MOVE_VIEWPORT_ACTION_DISPLAY": "انقر وانتقل لتحريك منفذ العرض",
+  "MOVE_VIEWPORT_ACTION_DISPLAY": "انقر وانتقل لتحريك إطار العرض",
   "MOVE_TOOL_TOOLTIP": "ينقل وحدات البكسل المحددة ({0}). اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "MOVE_TOOL_ACTION_DISPLAY": "استمر في الضغط على الماوس لتحريك وحدات البكسل المحددة. اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "PEN_TOOL_TOOLTIP": "قلم. ({0})",
   "PEN_TOOL_ACTION_DISPLAY": "اضغط وحرك للرسم.",
   "PIXEL_PERFECT_SETTING": "بكسل مثالي",
-  "RECTANGLE_TOOL_TOOLTIP": "رسم مستطيل على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم مربع.",
+  "RECTANGLE_TOOL_TOOLTIP": "رسم مستطيل على اللوحة ({0}). اضغط مع الاستمرار على Shift لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم مستطيل. اضغط مع الاستمرار على Shift لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك لرسم مربع.",
   "KEEP_ORIGINAL_IMAGE_SETTING": "احتفظ بالصورة الأصلية",
-  "ROTATE_VIEWPORT_TOOLTIP": "يدور منفذ العرض. ({0})",
+  "ROTATE_VIEWPORT_TOOLTIP": "يدير إطار العرض. ({0})",
   "ROTATE_VIEWPORT_ACTION_DISPLAY": "انقر وحرك لتدوير منفذ العرض",
   "SELECT_TOOL_TOOLTIP": "يختار المنطقة. ({0})",
-  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد منطقة. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl لقصها منه.",
+  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد منطقة. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl لإنقاصها منه.",
   "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "انقر وانتقل للإضافة إلى التحديد الحالي.",
-  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "انقر وانتقل للقص من التحديد الحالي.",
+  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "انقر وانتقل للإنقاص من التحديد الحالي.",
   "ZOOM_TOOL_TOOLTIP": "تكبير / تصغير منفذ العرض ({0}). انقر للتكبير ، او مع الاستمرار في الضغط على مفتاح alt وانقر للتصغير.",
   "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك للتكبير. انقر للتكبير ، واضغط باستمرار على ctrl وانقر للتصغير.",
   "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك للتكبير. انقر للتصغير ، وحرر مفتاح التحكم وانقر للتكبير.",
   "BRIGHTNESS_TOOL_TOOLTIP": "جعل وحدات البكسل أفتح أو أغمق ({0}). اضغط باستمرار على مفتاح Ctrl لجعل البكسل أغمق.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "ارسم على وحدات البكسل لجعلها أكثر إشراقًا. اضغط باستمرار على Ctrl للتغميق.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "ارسم بالبكسل لجعلها أكثر قتامة. اترك Ctrl لتفتيح.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "ارسم على وحدات البكسل لجعلها أفتح. اضغط باستمرار على Ctrl للتغميق.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "ارسم بالبكسل لجعلها أغمق. اترك Ctrl لتفتيح.",
   "COLOR_PICKER_TOOLTIP": "يختار اللون الأساسي من الصورة. ({0})",
-  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "انقر لاختيار الألوان. اضغط باستمرار على Ctrl لإخفاء الصورة. اضغط مع الاستمرار على Shift لإخفاء الطبقة المرجعية",
+  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "انقر لاختيار الألوان. اضغط باستمرار على Ctrl لإخفاء اللوحة. اضغط مع الاستمرار على Shift لإخفاء الطبقة المرجعية",
   "ELLIPSE_TOOL_TOOLTIP": "رسم قطع ناقص على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وحرك الماوس لرسم شكل بيضاوي. اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم دائرة.",
   "ERASER_TOOL_TOOLTIP": "يمحو اللون من البكسل. ({0})",
   "ERASER_TOOL_ACTION_DISPLAY": "انقر وحرك للمسح.",
   "FLOOD_FILL_TOOL_TOOLTIP": "املأ المنطقة باللون. ({0})",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "اضغط على منطقة لملئها. اضغط باستمرار على مفتاح Ctrl للتطبيق على جميع الطبقات.",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "اضغط على منطقة لملئها. حرر Ctrl للنظر في الطبقات الحالية فقط.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "اضغط على منطقة لملئها. اضغط باستمرار على مفتاح Ctrl لاعتبار جميع الطبقات.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "اضغط على منطقة لملئها. حرر Ctrl لاعتبار الطبقات الحالية فقط.",
   "LASSO_TOOL_TOOLTIP": "لاسو. ({0})",
-  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد وحدات البكسل داخل لاسو. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl للحذف منه.",
+  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد وحدات البكسل داخل لاسو. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl للإنقاص منه.",
   "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "انقر وتحرك لإضافة وحدات بكسل داخل لاسو إلى التحديد.",
   "LASSO_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك لحذف وحدات البكسل داخل لاسو من التحديد.",
-  "LINE_TOOL_TOOLTIP": "رسم خط على الصورة ({0}). اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
+  "LINE_TOOL_TOOLTIP": "رسم خط على اللوحة ({0}). اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم خط. اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم خط مع تمكين الالتقاط.",
   "MAGIC_WAND_TOOL_TOOLTIP": "العصا السحرية ({0}). ملء المحدد",
@@ -320,42 +320,42 @@
   "COLOR_PICKER_TOOL": "أداة انتقاء اللون",
   "ELLIPSE_TOOL": "القطع الناقص",
   "ERASER_TOOL": "ممحاة",
-  "FLOOD_FILL_TOOL": "ملء الفيضانات",
+  "FLOOD_FILL_TOOL": "ملؤ الفيضانات",
   "LASSO_TOOL": "لاسو",
   "LINE_TOOL": "خط",
   "MAGIC_WAND_TOOL": "العصا السحرية",
   "MOVE_TOOL": "تحريك",
-  "MOVE_VIEWPORT_TOOL": "تحريك منفذ العرض",
+  "MOVE_VIEWPORT_TOOL": "تحريك إطار العرض",
   "RECTANGLE_TOOL": "مستطيل",
-  "ROTATE_VIEWPORT_TOOL": "تدوير منفذ العرض",
+  "ROTATE_VIEWPORT_TOOL": "تدوير إطار العرض",
   "SELECT_TOOL_NAME": "حدد",
   "ZOOM_TOOL": "تكبير",
   "SHAPE_LABEL": "شكل",
   "MODE_LABEL": "الوضع",
   "SCOPE_LABEL": "نطاق",
-  "FILL_SHAPE_LABEL": "ملء الشكل",
-  "FILL_COLOR_LABEL": "ملء اللون",
+  "FILL_SHAPE_LABEL": "ملؤ الشكل",
+  "FILL_COLOR_LABEL": "ملؤ اللون",
   "TOOL_SIZE_LABEL": "حجم الأداة",
   "STRENGTH_LABEL": "قوة",
   "NEW": "جديد",
-  "ADD": "اضافة",
-  "SUBTRACT": "قص",
-  "INTERSECT": "تتقاطع",
+  "ADD": "إضافة",
+  "SUBTRACT": "إنقاص",
+  "INTERSECT": "تقاطع",
   "RECTANGLE": "مستطيل",
   "CIRCLE": "دائرة",
   "ABOUT": "حول البرنامج",
   "MINIMIZE": "تصغير",
-  "RESTORE": "اعادة",
+  "RESTORE": "إعادة",
   "MAXIMIZE": "تكبير",
-  "CLOSE": "اغلق",
-  "EXPORT_SIZE_HINT": "إذا كنت ترغب في مشاركة الصورة ، فجرّب {0}٪ للحصول على أفضل وضوح",
-  "CREATE": "انشاء",
+  "CLOSE": "إغلاق",
+  "EXPORT_SIZE_HINT": "إن كنت ترغب في مشاركة الصورة ، فجرّب {0}٪ للحصول على أفضل وضوح",
+  "CREATE": "إنشاء",
   "BASE_LAYER_NAME": "الطبقة الرئيسية",
   "ENABLE_MASK": "تمكين القناع",
   "SELECTED_AREA_EMPTY": "المنطقة المحددة فارغة",
   "NOTHING_TO_COPY": "لا يوجد شي لنسخه",
   "REFERENCE_LAYER_PATH": "مسار الطبقة المرجعية",
-  "FLIP": "توجيه",
+  "FLIP": "قلب",
   "ROTATION": "دوران",
   "ROT_IMG_90_D": "تدوير الصورة 90 درجة",
   "ROT_IMG_180_D": "تدوير الصورة 180 درجة",
@@ -366,63 +366,63 @@
   "UNNAMED_PALETTE": "لوح غير مسمى",
   "CLICK_SELECT_PRIMARY": "انقر لتحديد اللون الرئيسي.",
   "PEN_MODE": "وضع القلم",
-  "VIEW": "منظر",
+  "VIEW": "إظهار",
   "HORIZONTAL_LINE_SYMMETRY": "تناظر الخط الأفقي",
   "VERTICAL_LINE_SYMMETRY": "تناظر الخط العمودي",
   "COLOR_PICKER_TITLE": "أداة اختيار اللون",
-  "COLOR_SLIDERS_TITLE": "لوحة الالوان",
+  "COLOR_SLIDERS_TITLE": "أشرطة الألوان",
   "PALETTE_TITLE": "اللوحة",
   "SWATCHES_TITLE": "حوامل",
   "LAYERS_TITLE": "الطبقات",
   "NORMAL_BLEND_MODE": "عادي",
-  "DARKEN_BLEND_MODE": "أغمق",
+  "DARKEN_BLEND_MODE": "تغميق",
   "MULTIPLY_BLEND_MODE": "تضاعف",
   "COLOR_BURN_BLEND_MODE": "احتراق الالوان",
-  "LIGHTEN_BLEND_MODE": "فاتح",
+  "LIGHTEN_BLEND_MODE": "تفتيح",
   "SCREEN_BLEND_MODE": "شاشة",
   "COLOR_DODGE_BLEND_MODE": "انقاص كثافة اللون",
   "OVERLAY_BLEND_MODE": "تراكب",
   "SOFT_LIGHT_BLEND_MODE": "ضوء خافت",
-  "HARD_LIGHT_BLEND_MODE": "ضوء غامق",
-  "DIFFERENCE_BLEND_MODE": "اختلاف",
+  "HARD_LIGHT_BLEND_MODE": "ضوء شديد",
+  "DIFFERENCE_BLEND_MODE": "الفرق",
   "EXCLUSION_BLEND_MODE": "استبعاد",
-  "HUE_BLEND_MODE": "مسحة",
+  "HUE_BLEND_MODE": "المسحة",
   "SATURATION_BLEND_MODE": "التشبع",
-  "LUMINOSITY_BLEND_MODE": "لمعان",
+  "LUMINOSITY_BLEND_MODE": "اللمعان",
   "COLOR_BLEND_MODE": "اللون",
   "NOT_SUPPORTED_BLEND_MODE": "غير مدعوم",
-  "RESTART": "اعد تشغيل",
+  "RESTART": "إعادة التشغيل",
   "SORT_BY": "ترتيب حسب",
   "NAME": "الاسم",
-  "COLORS": "الالوان",
+  "COLORS": "الألوان",
   "DEFAULT": "الافتراضي",
-  "ALPHABETICAL": "مرتب حسب الحروف الأبجدية",
+  "ALPHABETICAL": "أبجدي",
   "COLOR_COUNT": "عدد الألوان",
-  "ANY": "اي",
+  "ANY": "أي",
   "MAX": "الاقصى",
-  "MIN": "الاقل",
+  "MIN": "الأدنى",
   "EXACT": "بالضبط",
   "ASCENDING": "تصاعدي",
   "DESCENDING": "تنازلي",
-  "NAME_IS_TOO_LONG": "الاسم طويل جدا",
+  "NAME_IS_TOO_LONG": "الاسم طويل جدًا",
   "STOP_IT_TEXT1": "هذا يكفي. رتب أسماء ملفاتك.",
   "STOP_IT_TEXT2": "هل يمكنك التوقف عن نسخ هذه الأسماء من فضلك؟",
-  "REPLACER_TOOLTIP": "انقر بزر الماوس الأيمن على لوحة الألوان واختر 'استبدال' أو أسقطها هنا.",
+  "REPLACER_TOOLTIP": "انقر زر الماوس الأيمن على لوحة الألوان واختر 'استبدال' أو ألقها هنا.",
   "CLICK_TO_CHOOSE_COLOR": "انقر لاختيار اللون",
   "REPLACE_COLOR": "استبدل اللون",
-  "PALETTE_COLOR_TOOLTIP": "انقر لتحديد اللون الرئيسي. قم بالسحب والإفلات على لون لوح آخر لتبديلها.",
+  "PALETTE_COLOR_TOOLTIP": "انقر لتحديده كلون رئيسي. قم بالسحب والإفلات على لون لوح آخر لتبديلها.",
   "ADD_FROM_SWATCHES": "أضف من العينات",
   "ADD_COLOR_TO_PALETTE": "أضف اللون إلى لوح الألوان",
   "USE_IN_CURRENT_IMAGE": "استخدم في الصورة الحالية",
-  "ADD_TO_FAVORITES": "اضافة الى المفضلة",
+  "ADD_TO_FAVORITES": "إضافة الى المفضلة",
   "BROWSE_PALETTES": "تصفح اللوحات",
   "LOAD_PALETTE": "تحميل لوحة",
-  "SAVE_PALETTE": "حفض اللوحة",
+  "SAVE_PALETTE": "حفظ اللوحة",
   "FAVORITES": "المفضلة",
   "ADD_FROM_CURRENT_PALETTE": "أضف من اللوحة الحالية",
   "OPEN_PALETTES_DIR_TOOLTIP": "افتح دليل اللوحات في المستكشف",
   "BROWSE_ON_LOSPEC_TOOLTIP": "تصفح اللوحات على Lospec",
-  "IMPORT_FROM_FILE_TOOLTIP": "اضافة من ملف",
+  "IMPORT_FROM_FILE_TOOLTIP": "استيراد من ملف",
   "TOP_LEFT": "أعلى اليسار",
   "TOP_CENTER": "اعلى الوسط",
   "TOP_RIGHT": "اعلى اليمين",
@@ -433,29 +433,29 @@
   "BOTTOM_CENTER": "اسفل الوسط",
   "BOTTOM_RIGHT": "أسفل اليمين",
   "CLIP_TO_BELOW": "مقطع للطبقة أدناه",
-  "MOVE_UPWARDS": "تحرك لأعلى",
+  "MOVE_UPWARDS": "تحريك لأعلى",
   "MOVE_DOWNWARDS": "تحريك للاسفل",
   "MERGE_SELECTED": "دمج المحدد",
   "LOCK_TRANSPARENCY": "قفل الشفافية",
   "COULD_NOT_LOAD_PALETTE": "تعذر إحضار اللوحات",
   "NO_PALETTES_FOUND": "لم يتم العثور على لوحات.",
   "LOSPEC_LINK_TEXT": "سمعت أنه يمكنك العثور على بعضها هنا: lospec.com/palette-list",
-  "PALETTE_BROWSER": "متصفح لوح الألوان",
-  "DELETE_PALETTE_CONFIRMATION": "هل أنت متأكد أنك تريد حذف هذه اللوحة؟ هذا لا يمكن التراجع عنها.",
-  "SHORTCUTS_IMPORTED": "تم اضافة الاختصارات من {0} بنجاح.",
-  "SHORTCUT_PROVIDER_DETECTED": "لقد اكتشفنا أنك قمت بتثبيت {0}. هل تريد اضافة الاختصارات منه؟",
-  "IMPORT_FROM_INSTALLATION": "اضافة من التثبيت",
-  "IMPORT_INSTALLATION_OPTION1": "اضافة من التنزيلات",
-  "IMPORT_INSTALLATION_OPTION2": "استخدام الافتراضي",
-  "IMPORT_FROM_TEMPLATE": "اضافة من قالب",
+  "PALETTE_BROWSER": "متصفح ألواح الألوان",
+  "DELETE_PALETTE_CONFIRMATION": "هل أنت متأكد أنك تريد حذف هذه اللوحة؟ لا يمكن التراجع عن هذا الفعل.",
+  "SHORTCUTS_IMPORTED": "تم استيراد الاختصارات من {0} بنجاح.",
+  "SHORTCUT_PROVIDER_DETECTED": "لقد وجدنا أنك قمت بتثبيت {0}. هل تريد إضافة الاختصارات منها؟",
+  "IMPORT_FROM_INSTALLATION": "إضافة من التثبيت",
+  "IMPORT_INSTALLATION_OPTION1": "إضافة من التثبيت",
+  "IMPORT_INSTALLATION_OPTION2": "استخدام الافتراضيات",
+  "IMPORT_FROM_TEMPLATE": "استيراد من قالب",
   "SHORTCUTS_IMPORTED_SUCCESS": "تم اضافة الاختصارات بنجاح.",
-  "WARNING_RESET_SHORTCUTS_DEFAULT": "هل أنت متأكد من أنك تريد إعادة تعيين جميع الاختصارات إلى قيمتها الافتراضية؟",
+  "WARNING_RESET_SHORTCUTS_DEFAULT": "هل أنت متأكد من أنك تريد إعادة تعيين جميع الاختصارات إلى قيمها الافتراضية؟",
   "SUCCESS": "نجاح",
   "WARNING": "تحذير",
   "ERROR_IMPORTING_IMAGE": "حدث خطأ أثناء استيراد الصورة.",
   "SHORTCUTS_CORRUPTED_TITLE": "ملف الاختصارات تالف",
-  "SHORTCUTS_CORRUPTED": "تعرض ملف الاختصارات للتلف ، اعادة التعيين إلى الوضع الافتراضي.",
-  "FAILED_DOWNLOAD_PALETTE": "فشل تحميل لوح الألوان",
+  "SHORTCUTS_CORRUPTED": "كان ملف الاختصارات مصابًا بالتلف، اعادة التعيين إلى الوضع الافتراضي.",
+  "FAILED_DOWNLOAD_PALETTE": "فشل تنزيل لوح الألوان",
   "FILE_INCORRECT_FORMAT": "لم يكن الملف بتنسيق صحيح",
   "INVALID_FILE": "ملف غير صالح",
   "SHORTCUTS_FILE_INCORRECT_FORMAT": "تنسيق ملف الاختصارات ليس صحيحًا",
@@ -465,14 +465,14 @@
   "SWAP": "تبديل",
   "SHORTCUT_ALREADY_ASSIGNED_SWAP": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الحالي أم تبديل الاختصارين؟",
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الموجود؟",
-  "UNSAVED_CHANGES": "تغييرات غير محفوضة",
+  "UNSAVED_CHANGES": "تغييرات غير محفوظة",
   "DOCUMENT_MODIFIED_SAVE": "تم تعديل العنصر. هل تريد ان تحفظ التغييرات؟",
   "SESSION_UNSAVED_DATA": "{0} ببيانات غير محفوظة. هل أنت متأكد؟",
   "PROJECT_MAINTAINERS": "مشرفو المشروع",
   "OTHER_AWESOME_CONTRIBUTORS": "وغيرهم من المساهمين الرائعين",
-  "HELP": "مساعد",
-  "STOP_IT_TEXT3": "لا ، حقًا ، توقف عن ذلك.",
-  "STOP_IT_TEXT4": "أليس لديك أي شيء أفضل لتفعله؟",
+  "HELP": "مساعدة",
+  "STOP_IT_TEXT3": "لا، حقًا ، توقف عن ذلك.",
+  "STOP_IT_TEXT4": "أليس لديك شيء أفضل تفعله؟",
   "LINEAR_DODGE_BLEND_MODE": "مراوغة خطية (إضافة)",
   "PRESS_ANY_KEY": "اضغط اي مفتاح",
   "NONE_SHORTCUT": "غير محدد",
@@ -481,15 +481,15 @@
   "PUT_REFERENCE_LAYER_BELOW": "ضع طبقة مرجعية أدناه",
   "TOGGLE_VERTICAL_SYMMETRY": "تبديل التناظر العمودي",
   "TOGGLE_HORIZONTAL_SYMMETRY": "تبديل التناظر الأفقي",
-  "RESET_VIEWPORT": "إعادة تعيين منفذ العرض",
-  "VIEWPORT_SETTINGS": "إعدادات منفذ العرض",
+  "RESET_VIEWPORT": "إعادة تعيين إطار العرض",
+  "VIEWPORT_SETTINGS": "إعدادات إطار العرض",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "انقر بالماوس مع الاستمرار لتحريك وحدات البكسل في الطبقات المحددة.",
   "CTRL_KEY": "Ctrl",
   "SHIFT_KEY": "Shift",
   "ALT_KEY": "Alt",
   "RENAME": "إعادة تسمية",
   "PIXEL_UNIT": "بكسل",
-  "OPEN_LOCALIZATION_DEBUG_WINDOW": "افتح نافذة تصحيح أخطاء الترجمة",
+  "OPEN_LOCALIZATION_DEBUG_WINDOW": "افتح نافذة معالجة أخطاء الترجمة",
   "FORCE_OTHER_FLOW_DIRECTION": "فرض اتجاه تدفق آخر",
   "API_KEY": "مفتاح API",
   "LOCALIZATION_VIEW_TYPE": "نوع عرض الترجمة",

+ 1133 - 1129
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1,1131 +1,1135 @@
 {
-  "RECENT_FILES": "Recent Files",
-  "OPEN_FILE": "Open file",
-  "NEW_FILE": "New",
-  "RECENT_EMPTY_TEXT": "So much empty space",
-  "LANGUAGE": "Language",
-  "GENERAL": "General",
-  "DISCORD": "Discord",
-  "KEY_BINDINGS": "Key Bindings",
-  "MISC": "Misc",
-  "SHOW_STARTUP_WINDOW": "Show Startup Window",
-  "RECENT_FILE_LENGTH": "Recent file list length",
-  "RECENT_FILE_LENGTH_TOOLTIP": "How many documents are shown under File > Recent. Default: 8",
-  "DEFAULT_NEW_SIZE": "Default new file size",
-  "WIDTH": "Width",
-  "HEIGHT": "Height",
-  "TOOLS": "Tools",
-  "ENABLE_SHARED_TOOLBAR": "Enable shared toolbar",
-  "AUTOMATIC_UPDATES": "Automatic Updates",
-  "CHECK_FOR_UPDATES": "Check updates on startup",
-  "UPDATE_STREAM": "Update stream",
-  "UPDATE_CHANNEL_HELP_TOOLTIP": "Update channels can only be changed in standalone version (downloaded from https://pixieditor.net).\nSteam and Microsoft Store versions handle updates separately.",
-  "DEBUG": "Debug",
-  "ENABLE_DEBUG_MODE": "Enable Debug mode",
-  "OPEN_CRASH_REPORTS_DIR": "Open crash reports directory",
-  "DISCORD_RICH_PRESENCE": "Rich Presence",
-  "ENABLED": "Enabled",
-  "SHOW_IMAGE_NAME": "Show image name",
-  "SHOW_IMAGE_SIZE": "Show image size",
-  "SHOW_LAYER_COUNT": "Show layer count",
-  "FILE": "File",
-  "RECENT": "Recent",
-  "OPEN": "Open",
-  "SAVE_PIXI": "Save (.pixi)",
-  "SAVE_AS_PIXI": "Save as... (.pixi)",
-  "EXPORT_IMG": "Export (.png, .jpg, etc.)",
-  "EDIT": "Edit",
-  "EXIT": "Exit",
-  "PERCENTAGE": "Percentage",
-  "ABSOLUTE": "Absolute",
-  "PRESERVE_ASPECT_RATIO": "Preserve aspect ratio",
-  "ANCHOR_POINT": "Anchor point",
-  "RESIZE_IMAGE": "Resize image",
-  "RESIZE": "Resize",
-  "DOCUMENTATION": "Documentation",
-  "WEBSITE": "Website",
-  "OPEN_WEBSITE": "Open website",
-  "REPOSITORY": "Repository",
-  "OPEN_REPOSITORY": "Open repository",
-  "OPEN_DOCUMENTATION": "Open documentation",
-  "LICENSE": "License",
-  "OPEN_LICENSE": "Open license",
-  "THIRD_PARTY_LICENSES": "Third party licenses",
-  "OPEN_THIRD_PARTY_LICENSES": "Open third party licenses",
-  "APPLY_TRANSFORM": "Apply transform",
-  "INCREASE_TOOL_SIZE": "Increase tool size",
-  "DECREASE_TOOL_SIZE": "Decrease tool size",
-  "UPDATE_READY": "Update is ready to be installed. Do you want to install it now?",
-  "NEW_UPDATE": "New update",
-  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Couldn't update without admin privileges. Please run PixiEditor as administrator.",
-  "INSUFFICIENT_PERMISSIONS": "Insufficient permissions",
-  "UPDATE_CHECK_FAILED": "Update check failed",
-  "COULD_NOT_CHECK_FOR_UPDATES": "Could not check if there is an update available.",
-  "VERSION": "Version {0}",
-  "BUILD_ID": "Build ID: {0}",
-  "OPEN_TEMP_DIR": "Open temp directory",
-  "OPEN_LOCAL_APPDATA_DIR": "Open Local AppData directory",
-  "OPEN_ROAMING_APPDATA_DIR": "Open Roaming AppData directory",
-  "OPEN_INSTALLATION_DIR": "Open installation directory",
-  "DUMP_ALL_COMMANDS": "Dump all commands",
-  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Dump all commands to a text file",
-  "CRASH": "Crash",
-  "CRASH_APP": "Crash application",
-  "DELETE_USR_PREFS": "Delete user preferences (Roaming AppData)",
-  "DELETE_SHORTCUT_FILE": "Delete shortcut file (Roaming AppData)",
-  "DELETE_EDITOR_DATA": "Delete editor data (Local AppData)",
-  "GENERATE_KEY_BINDINGS_TEMPLATE": "Generate key bindings template",
-  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generate key bindings json template",
-  "VALIDATE_SHORTCUT_MAP": "Validate shortcut map",
-  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Validates shortcut map",
-  "VALIDATION_KEYS_NOTICE_DIALOG": "Empty keys: {0}\nUnknown Commands: {1}",
-  "RESULT": "Result",
-  "CLEAR_RECENT_DOCUMENTS": "Clear recent documents",
-  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Clear recently opened documents",
-  "OPEN_CMD_DEBUG_WINDOW": "Open command debug window",
-  "PATH_DOES_NOT_EXIST": "{0} does not exist.",
-  "LOCATION_DOES_NOT_EXIST": "Location does not exist.",
-  "FILE_NOT_FOUND": "File not found.",
-  "ARE_YOU_SURE": "Are you sure?",
-  "ARE_YOU_SURE_PATH_FULL_PATH": "Are you sure you want to delete {0}?\nThis data will be lost for all installations.\n(Full Path: {1})",
-  "FAILED_TO_OPEN_FILE": "Failed to open the file",
-  "OLD_FILE_FORMAT": "Old file format",
-  "OLD_FILE_FORMAT_DESCRIPTION": "This .pixi file uses the old format,\n which is no longer supported and can't be opened.",
-  "NOTHING_FOUND": "Nothing found",
-  "EXPORT": "Export",
-  "EXPORT_IMAGE": "Export image",
-  "IMPORT": "Import",
-  "SHORTCUT_TEMPLATES": "Shortcut templates",
-  "RESET_ALL": "Reset all",
-  "LAYER": "Layer",
-  "LAYER_DELETE_SELECTED": "Delete active layer/folder",
-  "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Delete active layer or folder",
-  "LAYER_DELETE_ALL_SELECTED": "Delete all selected layers/folders",
-  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Delete all selected layers and/or folders",
-  "DELETE_SELECTED_PIXELS": "Delete selected pixels",
-  "NEW_FOLDER": "New folder",
-  "CREATE_NEW_FOLDER": "Create new folder",
-  "NEW_LAYER": "New layer",
-  "CREATE_NEW_LAYER": "Create new layer",
-  "NEW_IMAGE": "New image",
-  "CREATE_NEW_IMAGE": "Create new image",
-  "SAVE": "Save",
-  "SAVE_AS": "Save as...",
-  "IMAGE": "Image",
-  "SAVE_IMAGE": "Save image",
-  "SAVE_IMAGE_AS": "Save image as new",
-  "DUPLICATE": "Duplicate",
-  "DUPLICATE_SELECTED_LAYER": "Duplicate selected layer",
-  "CREATE_MASK": "Create mask",
-  "DELETE_MASK": "Delete mask",
-  "TOGGLE_MASK": "Toggle mask",
-  "APPLY_MASK": "Apply mask",
-  "TOGGLE_VISIBILITY": "Toggle visibility",
-  "MOVE_MEMBER_UP": "Move member upwards",
-  "MOVE_MEMBER_UP_DESCRIPTIVE": "Move selected layer or folder upwards",
-  "MOVE_MEMBER_DOWN": "Move member downwards",
-  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Move selected layer or folder downwards",
-  "MERGE_ALL_SELECTED_LAYERS": "Merge all selected layers",
-  "MERGE_WITH_ABOVE": "Merge selected layer with above",
-  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Merge selected layer with the one above it",
-  "MERGE_WITH_BELOW": "Merge selected layer with below",
-  "MERGE_WITH_BELOW_DESCRIPTIVE": "Merge selected layer with the one below it",
-  "ADD_REFERENCE_LAYER": "Add Reference Layer",
-  "DELETE_REFERENCE_LAYER": "Delete reference layer",
-  "TRANSFORM_REFERENCE_LAYER": "Transform reference layer",
-  "TOGGLE_REFERENCE_LAYER_POS": "Toggle reference layer position",
-  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Toggle reference layer between topmost or most below",
-  "RESET_REFERENCE_LAYER_POS": "Reset reference layer position",
-  "CLIP_CANVAS": "Clip Canvas",
-  "FLIP_IMG_VERTICALLY": "Flip Image Vertically",
-  "FLIP_IMG_HORIZONTALLY": "Flip Image Horizontally",
-  "FLIP_LAYERS_VERTICALLY": "Flip Selected Layers Vertically",
-  "FLIP_LAYERS_HORIZONTALLY": "Flip Selected Layers Horizontally",
-  "ROT_IMG_90": "Rotate Image 90 degrees",
-  "ROT_IMG_180": "Rotate Image 180 degrees",
-  "ROT_IMG_-90": "Rotate Image -90 degrees",
-  "ROT_LAYERS_90": "Rotate Selected Layers 90 degrees",
-  "ROT_LAYERS_180": "Rotate Selected Layers 180 degrees",
-  "ROT_LAYERS_-90": "Rotate Selected Layers -90 degrees",
-  "TOGGLE_VERT_SYMMETRY_AXIS": "Toggle vertical symmetry axis",
-  "TOGGLE_HOR_SYMMETRY_AXIS": "Toggle horizontal symmetry axis",
-  "DELETE_SELECTED": "Delete selected",
-  "DELETE_SELECTED_DESCRIPTIVE": "Delete selected element (layer, pixels, etc.)",
-  "RESIZE_DOCUMENT": "Resize document",
-  "RESIZE_CANVAS": "Resize canvas",
-  "CENTER_CONTENT": "Center content",
-  "CUT": "Cut",
-  "CUT_DESCRIPTIVE": "Cut selected area/layers",
-  "PASTE": "Paste",
-  "PASTE_DESCRIPTIVE": "Paste clipboard contents",
-  "PASTE_AS_NEW_LAYER": "Paste as new layer",
-  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Paste from clipboard as new layer",
-  "PASTE_REFERENCE_LAYER": "Paste reference layer",
-  "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Paste clipboard contents as reference layer",
-  "PASTE_COLOR": "Paste color",
-  "PASTE_COLOR_DESCRIPTIVE": "Paste color from clipboard",
-  "PASTE_COLOR_SECONDARY": "Paste color as secondary",
-  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Paste color from clipboard as secondary color",
-  "CLIPBOARD": "Clipboard",
-  "COPY": "Copy",
-  "COPY_DESCRIPTIVE": "Copy to clipboard",
-  "COPY_COLOR_HEX": "Copy primary color (HEX)",
-  "COPY_COLOR_HEX_DESCRIPTIVE": "Copy primary color as HEX code",
-  "COPY_COLOR_RGB": "Copy primary color (RGB)",
-  "COPY_COLOR_RGB_DESCRIPTIVE": "Copy primary color as RGB code",
-  "COPY_COLOR_SECONDARY_HEX": "Copy secondary color (HEX)",
-  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copy secondary color as HEX code",
-  "COPY_COLOR_SECONDARY_RGB": "Copy secondary color (RGB)",
-  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copy secondary color as RGB code",
-  "PALETTE_COLORS": "Palette Colors",
-  "REPLACE_SECONDARY_BY_PRIMARY": "Replace secondary color by primary",
-  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "Replace the secondary color by the primary color",
-  "REPLACE_PRIMARY_BY_SECONDARY": "Replace primary color by secondary",
-  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Replace the primary color by the secondary color",
-  "OPEN_PALETTE_BROWSER": "Open palette browser",
-  "OVERWRITE_PALETTE_CONSENT": "Palette '{0}' already exists, do you want to overwrite it?",
-  "PALETTE_EXISTS": "Palette already exists",
-  "REPLACE_PALETTE_CONSENT": "Replace current palette with selected one?",
-  "REPLACE_PALETTE": "Replace current palette",
-  "SELECT_COLOR_1": "Select color 1",
-  "SELECT_COLOR_2": "Select color 2",
-  "SELECT_COLOR_3": "Select color 3",
-  "SELECT_COLOR_4": "Select color 4",
-  "SELECT_COLOR_5": "Select color 5",
-  "SELECT_COLOR_6": "Select color 6",
-  "SELECT_COLOR_7": "Select color 7",
-  "SELECT_COLOR_8": "Select color 8",
-  "SELECT_COLOR_9": "Select color 9",
-  "SELECT_COLOR_10": "Select color 10",
-  "SELECT_TOOL": "Select {0} Tool",
-  "SELECT_COLOR_1_DESCRIPTIVE": "Select the first color in the palette",
-  "SELECT_COLOR_2_DESCRIPTIVE": "Select the second color in the palette",
-  "SELECT_COLOR_3_DESCRIPTIVE": "Select the third color in the palette",
-  "SELECT_COLOR_4_DESCRIPTIVE": "Select the fourth color in the palette",
-  "SELECT_COLOR_5_DESCRIPTIVE": "Select the fifth color in the palette",
-  "SELECT_COLOR_6_DESCRIPTIVE": "Select the sixth color in the palette",
-  "SELECT_COLOR_7_DESCRIPTIVE": "Select the seventh color in the palette",
-  "SELECT_COLOR_8_DESCRIPTIVE": "Select the eighth color in the palette",
-  "SELECT_COLOR_9_DESCRIPTIVE": "Select the ninth color in the palette",
-  "SELECT_COLOR_10_DESCRIPTIVE": "Select the tenth color in the palette",
-  "SWAP_COLORS": "Swap colors",
-  "SWAP_COLORS_DESCRIPTIVE": "Swap primary and secondary colors",
-  "SEARCH": "Search",
-  "COMMAND_SEARCH": "Command search",
-  "OPEN_COMMAND_SEARCH": "Open command search window",
-  "SELECT": "Select",
-  "DESELECT": "Deselect",
-  "INVERT": "Invert",
-  "SELECTION": "Selection",
-  "SELECT_ALL": "Select all",
-  "SELECT_ALL_DESCRIPTIVE": "Select everything",
-  "CLEAR_SELECTION": "Clear selection",
-  "INVERT_SELECTION": "Invert selection",
-  "INVERT_SELECTION_DESCRIPTIVE": "Invert the selection",
-  "TRANSFORM_SELECTED_AREA": "Transform selected area",
-  "NUDGE_SELECTED_LEFT": "Nudge selected object left",
-  "NUDGE_SELECTED_RIGHT": "Nudge selected object right",
-  "NUDGE_SELECTED_UP": "Nudge selected object up",
-  "NUDGE_SELECTED_DOWN": "Nudge selected object down",
-  "MASK_FROM_SELECTION": "New mask from selection",
-  "MASK_FROM_SELECTION_DESCRIPTIVE": "Selection to new mask",
-  "ADD_SELECTION_TO_MASK": "Add selection to mask",
-  "SUBTRACT_SELECTION_FROM_MASK": "Subtract selection from mask",
-  "INTERSECT_SELECTION_MASK": "Intersect selection with mask",
-  "SELECTION_TO_MASK": "Selection to mask",
-  "TO_NEW_MASK": "to new mask",
-  "ADD_TO_MASK": "add to mask",
-  "SUBTRACT_FROM_MASK": "subtract from mask",
-  "INTERSECT_WITH_MASK": "intersect with mask",
-  "STYLUS": "Stylus",
-  "TOGGLE_PEN_MODE": "Toggle pen mode",
-  "UNDO": "Undo",
-  "UNDO_DESCRIPTIVE": "Undo last action",
-  "REDO": "Redo",
-  "REDO_DESCRIPTIVE": "Redo last action",
-  "WINDOWS": "Windows",
-  "TOGGLE_GRIDLINES": "Toggle gridlines",
-  "GRIDLINES_SIZE": "Grid Size",
-  "ZOOM_IN": "Zoom in",
-  "ZOOM_OUT": "Zoom out",
-  "NEW_WINDOW_FOR_IMG": "New window for current image",
-  "CENTER_ACTIVE_VIEWPORT": "Center active viewport",
-  "FLIP_VIEWPORT_HORIZONTALLY": "Flip viewport horizontally",
-  "FLIP_VIEWPORT_VERTICALLY": "Flip viewport vertically",
-  "SETTINGS": "Settings",
-  "OPEN_SETTINGS": "Open settings",
-  "OPEN_SETTINGS_DESCRIPTIVE": "Open settings window",
-  "OPEN_STARTUP_WINDOW": "Open startup window",
-  "OPEN_SHORTCUT_WINDOW": "Open shortcuts window",
-  "OPEN_ABOUT_WINDOW": "Open about window",
-  "OPEN_PREVIEW_WINDOW": "Open preview window",
-  "ERROR": "Error",
-  "INTERNAL_ERROR": "Internal error",
-  "ERROR_SAVE_LOCATION": "Couldn't save the file to the specified location",
-  "ERROR_WHILE_SAVING": "An internal error occured while saving. Please try again.",
-  "UNKNOWN_ERROR_SAVING": "An error occured while saving.",
-  "FAILED_ASSOCIATE_LOSPEC": "Failed to associate Lospec Palette protocol.",
-  "REDDIT": "Reddit",
-  "GITHUB": "GitHub",
-  "YOUTUBE": "YouTube",
-  "DONATE": "Donate",
-  "YES": "Yes",
-  "NO": "No",
-  "CANCEL": "Cancel",
-  "UNNAMED": "Unnamed",
-  "OPEN_COMMAND_DEBUG_WINDOW": "Open command debug window",
-  "DELETE": "Delete",
-  "USER_PREFS": "User preferences (Roaming)",
-  "SHORTCUT_FILE": "Shortcut file (Roaming)",
-  "EDITOR_DATA": "Editor data (Local)",
-  "MOVE_VIEWPORT_TOOLTIP": "Moves viewport. ({0})",
-  "MOVE_VIEWPORT_ACTION_DISPLAY": "Click and move to pan the viewport",
-  "MOVE_TOOL_TOOLTIP": "Select and transform layers ({0}).",
-  "MOVE_TOOL_ACTION_DISPLAY": "Hold mouse to move selected pixels. Hold Ctrl to move all layers.",
-  "PEN_TOOL_TOOLTIP": "Pen. ({0})",
-  "PEN_TOOL_ACTION_DISPLAY": "Click and move to draw.",
-  "PIXEL_PERFECT_SETTING": "Pixel perfect",
-  "RECTANGLE_TOOL_TOOLTIP": "Draws rectangle on canvas ({0}). Hold Shift to draw a square.",
-  "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a rectangle. Hold Shift to draw a square.",
-  "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to draw a square.",
-  "KEEP_ORIGINAL_IMAGE_SETTING": "Keep original image",
-  "ROTATE_VIEWPORT_TOOLTIP": "Rotates viewport. ({0})",
-  "ROTATE_VIEWPORT_ACTION_DISPLAY": "Click and move to rotate the viewport",
-  "SELECT_TOOL_TOOLTIP": "Selects area. ({0})",
-  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to select an area. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
-  "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to add to the current selection.",
-  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Click and move to subtract from the current selection.",
-  "ZOOM_TOOL_TOOLTIP": "Zooms viewport ({0}). Click to zoom in, hold alt and click to zoom out.",
-  "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.",
-  "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Click and move to zoom. Click to zoom out, release ctrl and click to zoom in.",
-  "BRIGHTNESS_TOOL_TOOLTIP": "Makes pixels brighter or darker ({0}). Hold Ctrl to make pixels darker.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Draw on pixels to make them brighter. Hold Ctrl to darken.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Draw on pixels to make them darker. Release Ctrl to brighten.",
-  "COLOR_PICKER_TOOLTIP": "Picks the primary color from the canvas. ({0})",
-  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Click to pick colors. Hold Ctrl to hide the canvas. Hold Shift to hide the reference layer",
-  "ELLIPSE_TOOL_TOOLTIP": "Draws an ellipse on canvas ({0}). Hold Shift to draw a circle.",
-  "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move mouse to draw an ellipse. Hold Shift to draw a circle.",
-  "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a circle.",
-  "ERASER_TOOL_TOOLTIP": "Erases color from pixel. ({0})",
-  "ERASER_TOOL_ACTION_DISPLAY": "Click and move to erase.",
-  "FLOOD_FILL_TOOL_TOOLTIP": "Fills area with color. ({0})",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Press on an area to fill it. Hold down Ctrl to consider all layers.",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Press on an area to fill it. Release Ctrl to only consider the current layers.",
-  "LASSO_TOOL_TOOLTIP": "Lasso. ({0})",
-  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to select pixels inside of the lasso. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
-  "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to add pixels inside of the lasso to the selection.",
-  "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Click and move to subtract pixels inside of the lasso from the selection.",
-  "LINE_TOOL_TOOLTIP": "Draws line on canvas ({0}). Hold Shift to enable snapping.",
-  "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a line. Hold Shift to enable snapping.",
-  "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a line with snapping enabled.",
-  "MAGIC_WAND_TOOL_TOOLTIP": "Magic Wand ({0}). Flood's the selection",
-  "MAGIC_WAND_ACTION_DISPLAY": "Click to flood the selection.",
-  "PEN_TOOL": "Pen",
-  "BRIGHTNESS_TOOL": "Brightness",
-  "COLOR_PICKER_TOOL": "Color Picker",
-  "ELLIPSE_TOOL": "Ellipse",
-  "ERASER_TOOL": "Eraser",
-  "FLOOD_FILL_TOOL": "Flood Fill",
-  "LASSO_TOOL": "Lasso",
-  "LINE_TOOL": "Line",
-  "MAGIC_WAND_TOOL": "Magic Wand",
-  "MOVE_TOOL": "Move",
-  "MOVE_VIEWPORT_TOOL": "Move Viewport",
-  "RECTANGLE_TOOL": "Rectangle",
-  "ROTATE_VIEWPORT_TOOL": "Rotate Viewport",
-  "SELECT_TOOL_NAME": "Select",
-  "ZOOM_TOOL": "Zoom",
-  "SHAPE_LABEL": "Shape",
-  "MODE_LABEL": "Mode",
-  "SCOPE_LABEL": "Scope",
-  "FILL_SHAPE_LABEL": "Fill shape",
-  "FILL_COLOR_LABEL": "Fill color",
-  "TOOL_SIZE_LABEL": "Tool size",
-  "STRENGTH_LABEL": "Strength",
-  "NEW": "New",
-  "ADD": "Add",
-  "SUBTRACT": "Subtract",
-  "INTERSECT": "Intersect",
-  "RECTANGLE": "Rectangle",
-  "CIRCLE": "Circle",
-  "ABOUT": "About",
-  "MINIMIZE": "Minimize",
-  "RESTORE": "Restore",
-  "MAXIMIZE": "Maximize",
-  "CLOSE": "Close",
-  "EXPORT_SIZE_HINT": "If you want to share the image, try {0}% for the best clarity",
-  "CREATE": "Create",
-  "BASE_LAYER_NAME": "Base layer",
-  "ENABLE_MASK": "Enable mask",
-  "SELECTED_AREA_EMPTY": "Selected area is empty",
-  "NOTHING_TO_COPY": "Nothing to copy",
-  "REFERENCE_LAYER_PATH": "Reference layer path",
-  "FLIP": "Flip",
-  "ROTATION": "Rotation",
-  "ROT_IMG_90_D": "Rotate Image 90°",
-  "ROT_IMG_180_D": "Rotate Image 180°",
-  "ROT_IMG_-90_D": "Rotate Image -90°",
-  "ROT_LAYERS_90_D": "Rotate Selected Layers 90°",
-  "ROT_LAYERS_180_D": "Rotate Selected Layers 180°",
-  "ROT_LAYERS_-90_D": "Rotate Selected Layers -90°",
-  "UNNAMED_PALETTE": "Unnamed Palette",
-  "CLICK_SELECT_PRIMARY": "Click to select as main color.",
-  "PEN_MODE": "Pen mode",
-  "VIEW": "View",
-  "HORIZONTAL_LINE_SYMMETRY": "Horizontal line symmetry",
-  "VERTICAL_LINE_SYMMETRY": "Vertical line symmetry",
-  "COLOR_PICKER_TITLE": "Color Picker",
-  "COLOR_SLIDERS_TITLE": "Color Sliders",
-  "PALETTE_TITLE": "Palette",
-  "SWATCHES_TITLE": "Swatches",
-  "LAYERS_TITLE": "Layers",
-  "PREVIEW_TITLE": "Preview",
-  "NORMAL_BLEND_MODE": "Normal",
-  "ERASE_BLEND_MODE": "Erase",
-  "DARKEN_BLEND_MODE": "Darken",
-  "MULTIPLY_BLEND_MODE": "Multiply",
-  "COLOR_BURN_BLEND_MODE": "Color burn",
-  "LIGHTEN_BLEND_MODE": "Lighten",
-  "SCREEN_BLEND_MODE": "Screen",
-  "COLOR_DODGE_BLEND_MODE": "Color dodge",
-  "OVERLAY_BLEND_MODE": "Overlay",
-  "SOFT_LIGHT_BLEND_MODE": "Soft light",
-  "HARD_LIGHT_BLEND_MODE": "Hard light",
-  "DIFFERENCE_BLEND_MODE": "Difference",
-  "EXCLUSION_BLEND_MODE": "Exclusion",
-  "HUE_BLEND_MODE": "Hue",
-  "SATURATION_BLEND_MODE": "Saturation",
-  "LUMINOSITY_BLEND_MODE": "Luminosity",
-  "COLOR_BLEND_MODE": "Color",
-  "NOT_SUPPORTED_BLEND_MODE": "Not supported",
-  "RESTART": "Restart",
-  "SORT_BY": "Sort by",
-  "NAME": "Name",
-  "COLORS": "Colors",
-  "DEFAULT": "Default",
-  "ALPHABETICAL": "Alphabetical",
-  "COLOR_COUNT": "Color count",
-  "ANY": "Any",
-  "MAX": "Max",
-  "MIN": "Min",
-  "EXACT": "Exact",
-  "ASCENDING": "Ascending",
-  "DESCENDING": "Descending",
-  "NAME_IS_TOO_LONG": "The name is too long",
-  "STOP_IT_TEXT1": "That's enough. Tidy up your file names.",
-  "STOP_IT_TEXT2": "Can you stop copying these names please?",
-  "REPLACER_TOOLTIP": "Right click on palette color and choose 'Replace' or drop it here.",
-  "CLICK_TO_CHOOSE_COLOR": "Click to choose the color",
-  "REPLACE_COLOR": "Replace color",
-  "PALETTE_COLOR_TOOLTIP": "Click to select as main color. Drag and drop onto another palette color to swap them.",
-  "ADD_FROM_SWATCHES": "Add from swatches",
-  "ADD_COLOR_TO_PALETTE": "Add color to palette",
-  "USE_IN_CURRENT_IMAGE": "Use in current image",
-  "ADD_TO_FAVORITES": "Add to favorites",
-  "BROWSE_PALETTES": "Browse palettes",
-  "LOAD_PALETTE": "Load palette",
-  "SAVE_PALETTE": "Save palette",
-  "FAVORITES": "Favorites",
-  "ADD_FROM_CURRENT_PALETTE": "Add from current palette",
-  "OPEN_PALETTES_DIR_TOOLTIP": "Open palettes directory in explorer",
-  "BROWSE_ON_LOSPEC_TOOLTIP": "Browse palettes on Lospec",
-  "IMPORT_FROM_FILE_TOOLTIP": "Import from file",
-  "TOP_LEFT": "Top left",
-  "TOP_CENTER": "Top center",
-  "TOP_RIGHT": "Top right",
-  "MIDDLE_LEFT": "Middle left",
-  "MIDDLE_CENTER": "Middle center",
-  "MIDDLE_RIGHT": "Middle right",
-  "BOTTOM_LEFT": "Bottom left",
-  "BOTTOM_CENTER": "Bottom center",
-  "BOTTOM_RIGHT": "Bottom right",
-  "CLIP_TO_BELOW": "Clip to member below",
-  "MOVE_UPWARDS": "Move upwards",
-  "MOVE_DOWNWARDS": "Move downwards",
-  "MERGE_SELECTED": "Merge selected",
-  "LOCK_TRANSPARENCY": "Lock transparency",
-  "COULD_NOT_LOAD_PALETTE": "Couldn't fetch palettes",
-  "NO_PALETTES_FOUND": "No palettes found.",
-  "LOSPEC_LINK_TEXT": "I heard you can find some here: lospec.com/palette-list",
-  "PALETTE_BROWSER": "Palette Browser",
-  "DELETE_PALETTE_CONFIRMATION": "Are you sure you want to delete this palette? This cannot be undone.",
-  "SHORTCUTS_IMPORTED": "Shortcuts from {0} were imported successfully.",
-  "SHORTCUT_PROVIDER_DETECTED": "We've detected, that you have {0} installed. Do you want to import shortcuts from it?",
-  "IMPORT_FROM_INSTALLATION": "Import from installation",
-  "IMPORT_INSTALLATION_OPTION1": "Import from installation",
-  "IMPORT_INSTALLATION_OPTION2": "Use defaults",
-  "IMPORT_FROM_TEMPLATE": "Import from template",
-  "SHORTCUTS_IMPORTED_SUCCESS": "Shortcuts were imported successfully.",
-  "WARNING_RESET_SHORTCUTS_DEFAULT": "Are you sure you want to reset all shortcuts to their default value?",
-  "SUCCESS": "Success",
-  "WARNING": "Warning",
-  "ERROR_IMPORTING_IMAGE": "An error occured while importing the image.",
-  "SHORTCUTS_CORRUPTED_TITLE": "Corrupted shortcuts file",
-  "SHORTCUTS_CORRUPTED": "Shortcuts file was corrupted, resetting to default.",
-  "FAILED_DOWNLOAD_PALETTE": "Failed to download palette",
-  "FILE_INCORRECT_FORMAT": "The file was not in a correct format",
-  "INVALID_FILE": "Invalid file",
-  "SHORTCUTS_FILE_INCORRECT_FORMAT": "Shortcuts file was not in a correct format",
-  "UNSUPPORTED_FILE_FORMAT": "This file format is unsupported",
-  "ALREADY_ASSIGNED": "Already assigned",
-  "REPLACE": "Replace",
-  "SWAP": "Swap",
-  "SHORTCUT_ALREADY_ASSIGNED_SWAP": "This shortcut is already assigned to '{0}'\nDo you want to replace the existing shortcut or swap the two?",
-  "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "This shortcut is already assigned to '{0}'\nDo you want to replace the existing shortcut?",
-  "UNSAVED_CHANGES": "Unsaved changes",
-  "DOCUMENT_MODIFIED_SAVE": "The document has been modified. Do you want to save changes?",
-  "SESSION_UNSAVED_DATA": "{0} with unsaved data. Are you sure?",
-  "PROJECT_MAINTAINERS": "Project Maintainers",
-  "OTHER_AWESOME_CONTRIBUTORS": "And other awesome contributors",
-  "HELP": "Help",
-  "STOP_IT_TEXT3": "No, really, stop it.",
-  "STOP_IT_TEXT4": "Don't you have anything better to do?",
-  "LINEAR_DODGE_BLEND_MODE": "Linear dodge (Add)",
-  "PRESS_ANY_KEY": "Press any key",
-  "NONE_SHORTCUT": "None",
-  "REFERENCE": "Reference",
-  "PUT_REFERENCE_LAYER_ABOVE": "Put reference layer above",
-  "PUT_REFERENCE_LAYER_BELOW": "Put reference layer below",
-  "TOGGLE_VERTICAL_SYMMETRY": "Toggle vertical symmetry",
-  "TOGGLE_HORIZONTAL_SYMMETRY": "Toggle horizontal symmetry",
-  "RESET_VIEWPORT": "Reset viewport",
-  "VIEWPORT_SETTINGS": "Viewport settings",
-  "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Click and hold mouse to move pixels in selected layers.",
-  "CTRL_KEY": "Ctrl",
-  "SHIFT_KEY": "Shift",
-  "ALT_KEY": "Alt",
-  "RENAME": "Rename",
-  "PIXEL_UNIT": "px",
-  "OPEN_LOCALIZATION_DEBUG_WINDOW": "Open Localization Debug Window",
-  "FORCE_OTHER_FLOW_DIRECTION": "Force other flow direction",
-  "API_KEY": "API Key",
-  "LOCALIZATION_VIEW_TYPE": "Localization View Type",
-  "LOAD_LANGUAGE_FROM_FILE": "Load language from file",
-  "LOG_IN": "Log in",
-  "SYNC": "Sync",
-  "NOT_LOGGED_IN": "Not logged in",
-  "POE_EDITOR_ERROR": "POEditor Error: {0} {1}",
-  "HTTP_ERROR_MESSAGE": "HTTP Error: {0} {1}",
-  "LOGGED_IN": "Logged in",
-  "SYNCED_SUCCESSFULLY": "Synced successfully",
-  "EXCEPTION_ERROR": "Exception: {0}",
-  "DROP_PALETTE": "Drop palette here",
-  "SECURITY_ERROR": "Security error",
-  "SECURITY_ERROR_MSG": "No rights to write to the specified location.",
-  "IO_ERROR": "IO error",
-  "IO_ERROR_MSG": "Error while writing to disk.",
-  "FAILED_ASSOCIATE_PIXI": "Failed to associate .pixi file with PixiEditor.",
-  "COULD_NOT_SAVE_PALETTE": "There was an error while saving the palette.",
-  "NO_COLORS_TO_SAVE": "There are no colors to save.",
-  "CANVAS": "Canvas",
-  "SINGLE_LAYER": "Single Layer",
-  "CHOOSE": "Choose",
-  "REMOVE": "Remove",
-  "FILE_FORMAT_NOT_ASEPRITE_KEYS": "File is not a \".aseprite-keys\" file",
-  "FILE_HAS_INVALID_SHORTCUT": "The file contains an invalid shortcut",
-  "FILE_EXTENSION_NOT_SUPPORTED": "The file type '{0}' is not supported",
-  "ERROR_READING_FILE": "Error while reading the file",
-  "DISCARD_PALETTE": "Discard palette",
-  "DISCARD_PALETTE_CONFIRMATION": "Are you sure you want to discard current palette? This cannot be undone.",
-  "IMPORT_AS_NEW_LAYER": "Import as new layer",
-  "PASTE_AS_PRIMARY_COLOR": "Paste as primary color",
-  "IMPORT_AS_NEW_FILE": "Import as new file",
-  "IMPORT_PALETTE_FILE": "Import palette file",
-  "IMPORT_MULTIPLE_PALETTE_COLORS": "Import colors into palette",
-  "IMPORT_SINGLE_PALETTE_COLOR": "Import color into palette",
-  "IMPORT_AS_REFERENCE_LAYER": "Import as reference layer",
-  "NAVIGATOR_PICK_ACTION_DISPLAY": "Right-click to pick color, Shift-right-click to copy color to clipboard",
-  "OPEN_FILE_FROM_CLIPBOARD": "Open from clipboard",
-  "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Open from clipboard",
-  "OPEN_LOCALIZATION_DATA": "Do you want to open the LocalizationData.json?\nThe updated date has been put in the clipboard.\nNote that changes wont be applied until a restart",
-  "DOWNLOADING_LANGUAGE_FAILED": "Downloading language failed.\nAPI Key might have been overused.",
-  "LOCALIZATION_DATA_NOT_FOUND": "Localization data path not found",
-  "APPLY": "Apply",
-  "UPDATE_SOURCE": "Update source",
-  "COPY_TO_CLIPBOARD": "Copy to clipboard",
-  "LANGUAGE_FILE_NOT_FOUND": "Language file not found.\nLooking for {0}",
-  "PROJECT_ROOT_NOT_FOUND": "PixiEditor Project root not found.\nLooking for PixiEditor.csproj",
-  "LOCALIZATION_FOLDER_NOT_FOUND": "Localization folder not found.\nLooking for /Data/Localization",
-  "SELECT_A_LANGUAGE": "Select a language",
-  "DONE": "Done",
-  "SOURCE_UNSET_OR_MISSING": "Source missing/unset",
-  "SOURCE_NEWER": "Source newer",
-  "SOURCE_UP_TO_DATE": "Source is up to date",
-  "SOURCE_OLDER": "Cloud newer",
-  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Click to pick colors from the reference layer.",
-  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Click to pick colors from the canvas.",
-  "LOCALIZATION_DEBUG_WINDOW_TITLE": "Localization Debug Window",
-  "COMMAND_DEBUG_WINDOW_TITLE": "Command Debug Window",
-  "SHORTCUTS_TITLE": "Shortcuts",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Drag outside handles to rotate.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally.",
-  "LOCAL_PALETTE_SOURCE_NAME": "Local",
-  "ERROR_FORBIDDEN_UNIQUE_NAME": "Extension unique name cannot start with 'pixieditor'.",
-  "ERROR_MISSING_METADATA": "Extension metadata key '{0}' is missing.",
-  "ERROR_NO_CLASS_ENTRY": "Extension class entry is missing on path '{0}'.",
-  "ERROR_NO_ENTRY_ASSEMBLY": "Extension entry assembly is missing on path '{0}'.",
-  "ERROR_MISSING_ADDITIONAL_CONTENT": "Your current setup doesn't allow loading this extension. Perhaps you don't own it or don't have it installed. You can purchase it here '{0}'.",
-  "BUY_SUPPORTER_PACK": "Buy Supporter Pack",
-  "NEWS": "News",
-  "DISABLE_NEWS_PANEL": "Disable News panel in startup window",
-  "FAILED_FETCH_NEWS": "Failed to fetch news",
-  "CROP_TO_SELECTION": "Crop to selection",
-  "CROP_TO_SELECTION_DESCRIPTIVE": "Crop image to selection",
-  "SHOW_CONTEXT_MENU": "Show context menu",
-  "ERASE": "Erase",
-  "USE_SECONDARY_COLOR": "Use secondary color",
-  "RIGHT_CLICK_MODE": "Right click mode",
-  "ADD_PRIMARY_COLOR_TO_PALETTE": "Add primary color to palette",
-  "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Add primary color to current palette",
-  "EXPORT_SAVE_TITLE": "Choose a location to save the image",
-  "BROWSE_DIRECTORY": "Browse Directory",
-  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Not all documents were recovered",
-  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not recover all documents. Git gud at saving your work.",
-  "SEND": "Send report",
-  "OPEN_DOCKABLE_MENU": "Open Tab",
-  "TIMELINE_TITLE": "Timeline",
-  "EXPORT_IMAGE_HEADER": "Image",
-  "EXPORT_ANIMATION_HEADER": "Animation",
-  "EXPORT_SPRITESHEET_HEADER": "Spritesheet",
-  "PIXI_FILE": "PixiEditor Files",
-  "PNG_FILE": "PNG Images",
-  "JPEG_FILE": "JPEG Images",
-  "WEBP_FILE": "WebP Images",
-  "GIF_FILE": "GIFs",
-  "BMP_FILE": "BMP Images",
-  "IMAGE_FILES": "Image Files",
-  "VIDEO_FILES": "Video Files",
-  "OPEN_TYPE_FONT": "OpenType Fonts",
-  "TRUE_TYPE_FONT": "TrueType Fonts",
-  "SVG_FILE": "Scalable Vector Graphics",
-  "MP4_FILE": "MP4 Videos",
-  "COLUMNS": "Columns",
-  "ROWS": "Rows",
-  "BACKGROUND": "Background",
-  "OPACITY": "Opacity",
-  "IS_VISIBLE": "Is visible",
-  "CLIP_TO_MEMBER_BELOW": "Clip to member below",
-  "BLEND_MODE": "Blend mode",
-  "MASK": "Mask",
-  "MASK_IS_VISIBLE": "Mask is visible",
-  "OUTPUT": "Output",
-  "INPUT": "Input",
-  "NODE_GRAPH_TITLE": "Graph View",
-  "CONTENT": "Content",
-  "RADIUS": "Radius",
-  "STROKE_COLOR": "Stroke color",
-  "STROKE_WIDTH": "Stroke width",
-  "FILL_COLOR": "Fill color",
-  "TOP": "Top",
-  "BOTTOM": "Bottom",
-  "CHANNELS_DOCK_TITLE": "Channels",
-  "RED": "Red",
-  "GREEN": "Green",
-  "BLUE": "Blue",
-  "ALPHA": "Alpha",
-  "COLOR": "Color",
-  "COORDINATE": "Coordinate",
-  "VECTOR": "Vector",
-  "MATRIX": "Matrix",
-  "TRANSFORMED": "Transformed",
-  "GRAYSCALE": "Grayscale",
-  "CLAMP": "Clamp",
-  "SIZE": "Size",
-  "NOISE": "Noise",
-  "SCALE": "Scale",
-  "SEED": "Seed",
-  "KERNEL": "Kernel",
-  "KERNEL_VIEW_SUM": "Sum:",
-  "KERNEL_VIEW_SUM_TOOLTIP": "The sum of all values. You likely want to aim for a value of 1 or 0",
-  "GAIN": "Gain",
-  "BIAS": "Bias",
-  "TILE_MODE": "Tile Mode",
-  "ON_ALPHA": "On Alpha",
-  "PIXEL_COORDINATE": "Pixel Coordinate",
-  "OUTPUT_NODE": "Output",
-  "NOISE_NODE": "Noise",
-  "ELLIPSE_NODE": "Ellipse",
-  "CREATE_IMAGE_NODE": "Create Image",
-  "FOLDER_NODE": "Folder",
-  "IMAGE_LAYER_NODE": "Image Layer",
-  "KERNEL_FILTER_NODE": "Kernel Filter",
-  "MATH_NODE": "Math",
-  "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "Matrix Transform Filter",
-  "MERGE_NODE": "Merge",
-  "MODIFY_IMAGE_LEFT_NODE": "Begin Modify Image",
-  "MODIFY_IMAGE_RIGHT_NODE": "End Modify Image",
-  "MODIFY_IMAGE_PAIR_NODE": "Modify Image",
-  "COMBINE_CHANNELS_NODE": "Combine Channels",
-  "COMBINE_COLOR_NODE": "Combine Color",
-  "COMBINE_VECD_NODE": "Combine Vector",
-  "COMBINE_VECI_NODE": "Combine Integer Vector",
-  "SEPARATE_CHANNELS_NODE": "Separate Channels",
-  "SEPARATE_VECD_NODE": "Separate Vector",
-  "SEPARATE_VECI_NODE": "Separate Integer Vector",
-  "SEPARATE_COLOR_NODE": "Separate Color",
-  "TIME_NODE": "Time",
-  "FILTERS": "Filters",
-  "PREVIOUS": "Previous",
-  "FILL": "Fill",
-  "MATH_MODE": "Math Mode",
-  "NOISE_TYPE": "Noise Type",
-  "OCTAVES": "Octaves",
-  "ACTIVE_FRAME": "Active Frame",
-  "NORMALIZED_TIME": "Normalized Time",
-  "WITHOUT_FILTERS": "Without filters",
-  "RAW_LAYER_OUTPUT": "Raw",
-  "EXAMPLE_FILES": "Example Files",
-  "PROCEDURAL_GENERATION": "Procedural Animation",
-  "POND_EXAMPLE": "Pond",
-  "TREE_EXAMPLE": "Windy Tree",
-  "OUTLINE_EXAMPLE": "Automatic Outline",
-  "BETA_ANIMATIONS": "Animations",
-  "SLIME_EXAMPLE": "Animated Slime",
-  "SHOW_ALL_EXAMPLES": "Show all",
-  "APPLY_FILTER_NODE": "Apply Filter",
-  "FILTER": "Filter",
-  "LERP_NODE": "Lerp",
-  "GRAYSCALE_FILTER_NODE": "Grayscale Filter",
-  "FROM": "From",
-  "TO": "To",
-  "TIME": "Time",
-  "WARMING_UP": "Warming up",
-  "RENDERING_FRAME": "Generating Frame {0}/{1}",
-  "RENDERING_VIDEO": "Rendering Video",
-  "FINISHED": "Finished",
-  "GENERATING_SPRITE_SHEET": "Generating Sprite Sheet",
-  "RENDERING_IMAGE": "Rendering Image",
-  "PROGRESS_POPUP_TITLE": "Progress",
-  "POINTS": "Points",
-  "MIN_DISTANCE": "Min. Distance",
-  "MAX_POINTS": "Max. Points",
-  "PROBABILITY": "Probability",
-  "DISTRIBUTE_POINTS": "Distribute points",
-  "REMOVE_CLOSE_POINTS": "Remove close points",
-  "RASTERIZE_SHAPE": "Rasterize Shape",
-  "MODE": "Mode",
-  "Factor": "Factor",
-  "NORMALIZE": "Normalize",
-  "WEIGHT_FACTOR": "Weight",
-  "STARS_EXAMPLE": "Stars",
-  "ADD_EMPTY_FRAME": "Add empty frame",
-  "DUPLICATE_FRAME": "Duplicate frame",
-  "DELETE_FRAME": "Remove frame",
-  "DEFAULT_MEMBER_NAME": "New Element",
-  "NO_PARSER_FOUND": "No file parser found for extension '{0}'",
-  "SELECT_FILE_FORMAT": "Select file format",
-  "SELECT_FILE_FORMAT_DESCRIPTION": "Multiple file types of the same format are supported. Please select the one you want to use.",
-  "NEW_PALETTE_FILE": "palette",
-  "ISLAND_EXAMPLE": "Islands",
-  "ONION_FRAMES_COUNT": "Onion frames",
-  "ONION_OPACITY": "Onion opacity",
-  "TOGGLE_ONION_SKINNING": "Toggle onion skinning",
-  "CHANGE_ACTIVE_FRAME_PREVIOUS": "Change active frame to previous",
-  "CHANGE_ACTIVE_FRAME_NEXT": "Change active frame to next",
-  "TOGGLE_ANIMATION": "Toggle animation",
-  "NEW_FROM_CLIPBOARD": "New from clipboard",
-  "OFFSET": "Offset",
-  "SHAPE": "Shape",
-  "STRUCTURE": "Structure",
-  "NUMBERS": "Numbers",
-  "OPERATIONS": "Operations",
-  "GENERATION": "Generation",
-  "NUMBER": "Number",
-  "ANIMATION": "Animation",
-  "SAMPLE_IMAGE": "Sample Image",
-  "POSITION": "Position",
-  "MATH_ADD": "Add",
-  "MATH_SUBTRACT": "Subtract",
-  "MULTIPLY": "Multiply",
-  "DIVIDE": "Divide",
-  "SIN": "Sin",
-  "COS": "Cos",
-  "TAN": "Tan",
-  "GREATER_THAN": "Greater than",
-  "LESS_THAN": "Less than",
-  "LESS_THAN_OR_EQUAL": "Less than or equal",
-  "COMPARE": "Compare",
-  "MATH_POWER": "Power",
-  "LOGARITHM": "Logarithm",
-  "NATURAL_LOGARITHM": "Natural logarithm",
-  "ROOT": "Root",
-  "INVERSE_ROOT": "Inverse root",
-  "FRACTION": "Fraction",
-  "NEGATE": "Negate",
-  "FLOOR": "Floor",
-  "CEIL": "Ceil",
-  "ROUND": "Round",
-  "MODULO": "Modulo",
-  "STEP": "Step",
-  "SMOOTH_STEP": "Smoothstep",
-  "PIXEL_ART_TOOLSET": "Pixel Art",
-  "VECTOR_TOOLSET": "Vector",
-  "VECTOR_LAYER": "Vector Layer",
-  "STROKE_COLOR_LABEL": "Stroke",
-  "SYNC_WITH_PRIMARY_COLOR_LABEL": "Sync with primary color",
-  "RASTERIZE": "Rasterize",
-  "RASTERIZE_ACTIVE_LAYER": "Rasterize active layer",
-  "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "Convert/Rasterize the active layer into a image (raster) layer.",
-  "NEW_ELLIPSE_LAYER_NAME": "Ellipse",
-  "NEW_RECTANGLE_LAYER_NAME": "Rectangle",
-  "NEW_LINE_LAYER_NAME": "Line",
-  "RENDER_OUTPUT": "Render Output",
-  "PAINT_TOOLSET": "Painting",
-  "HARDNESS_SETTING": "Hardness",
-  "SPACING_SETTING": "Spacing",
-  "ANTI_ALIASING_SETTING": "Anti-aliasing",
-  "TOLERANCE_LABEL": "Tolerance",
-  "TOGGLE_SNAPPING": "Toggle snapping",
-  "HIGH_RES_PREVIEW": "High Resolution Preview",
-  "LOW_RES_PREVIEW": "Document Resolution Preview",
-  "TOGGLE_HIGH_RES_PREVIEW": "Toggle high resolution preview",
-  "FACTOR": "Factor",
-  "PATH_TOOL": "Path",
-  "PATH_TOOL_TOOLTIP": "Create vector paths and curves ({0}).",
-  "PATH_TOOL_ACTION_DISPLAY": "Click to add a point.",
-  "PATH_TOOL_ACTION_DISPLAY_CTRL": "Click on existing point and drag to make it a curve. Tap on a control point to select it.",
-  "PATH_TOOL_ACTION_DISPLAY_SHIFT": "Click to create a new layer.",
-  "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "Tap on a control point to add it to the selection.",
-  "PATH_TOOL_ACTION_DISPLAY_ALT": "Click on a control point and move to adjust only one side of the curve.",
-  "DEFAULT_PATH_LAYER_NAME": "Path",
-  "DELETE_NODES": "Delete nodes",
-  "DELETE_NODES_DESCRIPTIVE": "Delete selected nodes",
-  "DELETE_CELS": "Delete cels",
-  "DELETE_CELS_DESCRIPTIVE": "Delete selected cels",
-  "COPY_COLOR_TO_CLIPBOARD": "Copy color to clipboard",
-  "VIEWPORT_ROTATION": "Viewport rotation",
-  "NEXT_TOOL_SET": "Next tool set",
-  "PREVIOUS_TOOL_SET": "Previous tool set",
-  "FILL_MODE": "Fill mode",
-  "USE_LINEAR_SRGB_PROCESSING": "Use linear sRGB for processing colors",
-  "USE_LINEAR_SRGB_PROCESSING_DESC": "Convert document using sRGB blending mode to linear sRGB for processing colors. This will affect the colors of the document, but will make blending more accurate.",
-  "FILL_TYPE_WINDING": "Winding",
-  "FILL_TYPE_EVEN_ODD": "Even Odd",
-  "FILL_TYPE_INVERSE_WINDING": "Inverse Winding",
-  "FILL_TYPE_INVERSE_EVEN_ODD": "Inverse Even Odd",
-  "STROKE_CAP": "Stroke Cap",
-  "STROKE_JOIN": "Stroke Join",
-  "COPY_VISIBLE": "Copy visible",
-  "COPY_VISIBLE_DESCRIPTIVE": "Copy visible pixels",
-  "COLOR_SAMPLE_MODE": "Sample mode",
-  "CREATE_CEL": "Create cel",
-  "CREATE_CEL_DESCRIPTIVE": "Create a new cel",
-  "DUPLICATE_CEL": "Duplicate cel",
-  "DUPLICATE_CEL_DESCRIPTIVE": "Duplicate cel in the current frame",
-  "RENDER_PREVIEW": "Render preview",
-  "OUTPUT_NAME": "Output name",
-  "CUSTOM_OUTPUT_NODE": "Custom Output",
-  "TOGGLE_HUD": "Toggle HUD",
-  "OPEN_TIMELINE": "Open timeline",
-  "OPEN_NODE_GRAPH": "Open node graph",
-  "TOGGLE_PLAY": "Play/Pause animation",
-  "COPY_NODES": "Copy nodes",
-  "COPY_NODES_DESCRIPTIVE": "Copy selected nodes",
-  "PASTE_NODES": "Paste nodes",
-  "PASTE_NODES_DESCRIPTIVE": "Paste copied nodes",
-  "COPY_CELS": "Copy cels",
-  "COPY_CELS_DESCRIPTIVE": "Copy selected cels",
-  "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "Toggle onion skinning",
-  "VALUE": "Value",
-  "TARGET": "Target",
-  "EPSILON": "Epsilon",
-  "PRESERVE_ALPHA": "Preserve alpha",
-  "BLUR_FILTER_NODE": "Gaussian Blur Filter",
-  "LENGTH": "Length",
-  "GREATER_THAN_OR_EQUAL": "Greater than or equal",
-  "COLOR_NODE": "Color",
-  "CONVERT_TO_CURVE": "Convert to curve",
-  "CONVERT_TO_CURVE_DESCRIPTIVE": "Convert selected vector layer to a curve/path",
-  "FONT_FILES": "Font Files",
-  "UNIT_PT": "pt",
-  "FONT_LABEL": "Family",
-  "FONT_SIZE_LABEL": "Size",
-  "SPACING_LABEL": "Spacing",
-  "TEXT_TOOL": "Text",
-  "MISSING_FONT": "Missing font",
-  "TEXT_LAYER_NAME": "Text",
-  "TEXT_TOOL_TOOLTIP": "Create text ({0}).",
-  "BOLD_TOOLTIP": "Bold",
-  "ITALIC_TOOLTIP": "Italic",
-  "CUSTOM_FONT": "Custom font",
-  "DUMP_GPU_DIAGNOSTICS": "Dump GPU diagnostics",
-  "USE_SRGB_PROCESSING": "Use sRGB for processing colors",
-  "USE_SRGB_PROCESSING_DESC": "Convert document using linear sRGB to sRGB for processing colors. This will affect the colors of the document.",
-  "TEXT_NODE": "Text",
-  "TEXT_LABEL": "Text",
-  "TEXT_ON_PATH_NODE": "Text on Path",
-  "HIGH_DPI_RENDERING": "High DPI Rendering",
-  "THICKNESS": "Thickness",
-  "TYPE": "Type",
-  "EFFECTS": "Effects",
-  "OUTLINE_NODE": "Outline",
-  "SHADER_CODE": "Shader Code",
-  "SHADER_NODE": "Shader",
-  "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "Failed to open file",
-  "FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE": "Failed to edit this string in external editor. Reason: {0}",
-  "STRING_EDIT_IN_DEFAULT_APP": "Edit in default app",
-  "STRING_OPEN_IN_FOLDER": "Open in folder",
-  "DISCO_BALL_EXAMPLE": "Disco Ball",
-  "COLOR_SPACE": "Color Space",
-  "PHOTO_EXAMPLES": "Photo",
-  "MASK_EXAMPLE": "Mask",
-  "SHADOW_NODE": "Shadow Filter",
-  "INPUT_MATRIX": "Input Matrix",
-  "OUTPUT_MATRIX": "Output Matrix",
-  "CENTER": "Center",
-  "CONTENT_OFFSET": "Content Offset",
-  "CANVAS_POSITION": "Canvas Position",
-  "CENTER_POSITION": "Center Position",
-  "TILE_MODE_X": "Tile Mode X",
-  "TILE_MODE_Y": "Tile Mode Y",
-  "TILE_NODE": "Tile",
-  "SKEW": "Skew",
-  "OFFSET_NODE": "Offset",
-  "SKEW_NODE": "Skew",
-  "ROTATION_NODE": "Rotation",
-  "SCALE_NODE": "Scale",
-  "ROTATE_NODE": "Rotate",
-  "TRANSFORM_NODE": "Transform",
-  "UNIT": "Unit",
-  "ANGLE": "Angle",
-  "DOCUMENT_INFO_NODE": "Document Info",
-  "MASK_NODE": "Mask",
-  "SEPIA_FILTER_NODE": "Sepia Filter",
-  "INTENSITY": "Intensity",
-  "INVERT_FILTER_NODE": "Invert Filter",
-  "COLOR_ADJUSTMENTS_FILTER": "Color Adjustments Filter",
-  "ADJUST_BRIGHTNESS": "Adjust Brightness",
-  "ADJUST_CONTRAST": "Adjust Contrast",
-  "ADJUST_SATURATION": "Adjust Saturation",
-  "ADJUST_TEMPERATURE": "Adjust Temperature",
-  "ADJUST_TINT": "Adjust Tint",
-  "ADJUST_HUE": "Adjust Hue",
-  "HUE_VALUE": "Hue",
-  "SATURATION_VALUE": "Saturation",
-  "BRIGHTNESS_VALUE": "Brightness",
-  "CONTRAST_VALUE": "Contrast",
-  "TEMPERATURE_VALUE": "Temperature",
-  "TINT_VALUE": "Tint",
-  "FAILED_DOWNLOADING_UPDATE_TITLE": "Failed to download update",
-  "FAILED_DOWNLOADING_UPDATE": "Failed to download the update. Try again later.",
-  "UNEXPECTED_SHUTDOWN": "Unexpected shutdown",
-  "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor was unexpectedly shut down. We've loaded latest autosave of your files.",
-  "OK": "OK",
-  "OPEN_AUTOSAVES": "Browse Autosaves",
-  "AUTOSAVE_SETTINGS_HEADER": "Autosave",
-  "AUTOSAVE_SETTINGS_SAVE_STATE": "Reopen last files on startup",
-  "AUTOSAVE_SETTINGS_PERIOD": "Autosave period",
-  "AUTOSAVE_ENABLED": "Autosave enabled",
-  "MINUTE_UNIVERSAL": "min",
-  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Autosave to selected file",
-  "LOAD_LAZY_FILE_MESSAGE": "To improve startup time, PixiEditor didn't load this file. Click the button below to load it.",
-  "EASING_NODE": "Easing",
-  "EASING_TYPE": "Easing Type",
-  "OPEN_DIRECTORY_ON_EXPORT": "Open directory on export",
-  "ERROR_LOOP_DETECTED_MESSAGE": "Moving this layer will create a loop. Fix it in the Node Graph.",
-  "LINEAR_EASING_TYPE": "Linear",
-  "IN_SINE_EASING_TYPE": "In Sine",
-  "OUT_SINE_EASING_TYPE": "Out Sine",
-  "IN_OUT_SINE_EASING_TYPE": "In Out Sine",
-  "IN_QUAD_EASING_TYPE": "In Quad",
-  "OUT_QUAD_EASING_TYPE": "Out Quad",
-  "IN_OUT_QUAD_EASING_TYPE": "In Out Quad",
-  "IN_CUBIC_EASING_TYPE": "In Cubic",
-  "OUT_CUBIC_EASING_TYPE": "Out Cubic",
-  "IN_OUT_CUBIC_EASING_TYPE": "In Out Cubic",
-  "IN_QUART_EASING_TYPE": "In Quart",
-  "OUT_QUART_EASING_TYPE": "Out Quart",
-  "IN_OUT_QUART_EASING_TYPE": "In Out Quart",
-  "IN_QUINT_EASING_TYPE": "In Quint",
-  "OUT_QUINT_EASING_TYPE": "Out Quint",
-  "IN_OUT_QUINT_EASING_TYPE": "In Out Quint",
-  "IN_EXPO_EASING_TYPE": "In Expo",
-  "OUT_EXPO_EASING_TYPE": "Out Expo",
-  "IN_OUT_EXPO_EASING_TYPE": "In Out Expo",
-  "IN_CIRC_EASING_TYPE": "In Circ",
-  "OUT_CIRC_EASING_TYPE": "Out Circ",
-  "IN_OUT_CIRC_EASING_TYPE": "In Out Circ",
-  "IN_BACK_EASING_TYPE": "In Back",
-  "OUT_BACK_EASING_TYPE": "Out Back",
-  "IN_OUT_BACK_EASING_TYPE": "In Out Back",
-  "IN_ELASTIC_EASING_TYPE": "In Elastic",
-  "OUT_ELASTIC_EASING_TYPE": "Out Elastic",
-  "IN_OUT_ELASTIC_EASING_TYPE": "In Out Elastic",
-  "IN_BOUNCE_EASING_TYPE": "In Bounce",
-  "OUT_BOUNCE_EASING_TYPE": "Out Bounce",
-  "IN_OUT_BOUNCE_EASING_TYPE": "In Out Bounce",
-  "CLAMP_SHADER_TILE_NODE": "Clamp",
-  "REPEAT_SHADER_TILE_NODE": "Repeat",
-  "MIRROR_SHADER_TILE_NODE": "Mirror",
-  "DECAL_SHADER_TILE_NODE": "Decal",
-  "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
-  "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
-  "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
-  "COLOR_MANAGED_COLOR_SAMPLE_MODE": "Color Managed",
-  "RAW_COLOR_SAMPLE_MODE": "Raw",
-  "FRACTAL_PERLIN_NOISE_TYPE": "Perlin",
-  "TURBULENCE_PERLIN_NOISE_TYPE": "Turbulence",
-  "INHERIT_COLOR_SPACE_TYPE": "Inherit",
-  "SRGB_COLOR_SPACE_TYPE": "sRGB",
-  "LINEAR_SRGB_COLOR_SPACE_TYPE": "Linear sRGB",
-  "SIMPLE_OUTLINE_TYPE": "Simple",
-  "GAUSSIAN_OUTLINE_TYPE": "Gaussian",
-  "PIXEL_PERFECT_OUTLINE_TYPE": "Pixel Perfect",
-  "DEGREES_ROTATION_TYPE": "Degrees",
-  "RADIANS_ROTATION_TYPE": "Radians",
-  "WEIGHTED_GRAYSCALE_MODE": "Weighted",
-  "AVERAGE_GRAYSCALE_MODE": "Average",
-  "CUSTOM_GRAYSCALE_MODE": "Custom",
-  "CLAMP_TILE_MODE": "Clamp",
-  "REPEAT_TILE_MODE": "Repeat",
-  "MIRROR_TILE_MODE": "Mirror",
-  "DECAL_TILE_MODE": "Decal",
-  "ERR_UNKNOWN_FILE_FORMAT": "Unknown file format",
-  "ERR_EXPORT_SIZE_INVALID": "Invalid export size. Values must be greater than 0.",
-  "ERR_UNKNOWN_IMG_FORMAT": "Unknown image format '{0}'.",
-  "ERR_FAILED_GENERATE_SPRITE_SHEET": "Failed generating sprite sheet",
-  "ERR_NO_RENDERER": "Animation renderer not found.",
-  "ERR_RENDERING_FAILED": "Rendering failed",
-  "ENABLE_ANALYTICS": "Send anonymous analytics",
-  "ANALYTICS_INFO": "We collect anonymous usage data to improve PixiEditor. No personal data is collected.",
-  "LANGUAGE_INFO": "All translations are community-driven. Join our Discord server for more information.",
-  "UP_TO_DATE_UNKNOWN": "Couldn't check for updates",
-  "UP_TO_DATE": "PixiEditor is up to date",
-  "UPDATE_AVAILABLE": "Update {0} is available",
-  "CHECKING_UPDATES": "Checking for updates...",
-  "UPDATE_FAILED_DOWNLOAD": "Failed to download the update",
-  "UPDATE_READY_TO_INSTALL": "Update is ready. Switch to {0}?",
-  "SWITCH_TO_NEW_VERSION": "Switch",
-  "DOWNLOAD_UPDATE": "Download",
-  "DOWNLOADING_UPDATE": "Downloading update...",
-  "CHECKING_FOR_UPDATES": "Checking for updates...",
-  "PAINT_SHAPE_SETTING": "Brush shape",
-  "BOOL_OPERATION_NODE": "Boolean Operation",
-  "FIRST_SHAPE": "First shape",
-  "SECOND_SHAPE": "Second shape",
-  "OPERATION": "Operation",
-  "UNION_VECTOR_PATH_OP": "Union",
-  "DIFFERENCE_VECTOR_PATH_OP": "Difference",
-  "INTERSECT_VECTOR_PATH_OP": "Intersect",
-  "XOR_VECTOR_PATH_OP": "XOR",
-  "REVERSE_DIFFERENCE_VECTOR_PATH_OP": "Reverse Difference",
-  "NO_DOCUMENT_OPEN": "Nothing's here",
-  "EMPTY_DOCUMENT_ACTION_BTN": "Start creating",
-  "ONBOARDING_TITLE": "Welcome to",
-  "ONBOARDING_DESCRIPTION": "Let's set up your workspace!",
-  "ONBOARDING_SKIP_BTN": "Skip",
-  "ONBOARDING_ACTION_BTN": "Let's begin",
-  "ONB_SELECT_PRIMARY_TOOLSET": "Select Your Primary Toolset",
-  "ONB_NEXT_BTN": "Next",
-  "ONB_FINISH_BTN": "Finish",
-  "ONB_BACK_BTN": "Previous",
-  "ONB_ANALYTICS": "Anonymous Analytics",
-  "ONB_ALL_SET": "You are all set!",
-  "ONB_ALL_SET_BTN": "Start creating",
-  "ANALYTICS_INFO_DETAILED": "PixiEditor collects anonymous usage data to improve the app. The data does not contain any personal information. Among other things, PixiEditor tracks the following:\n- Which and how the tools are used\n- How long you've used the app for\n- Which commands are used\n- Performance data\n\n You can opt out of analytics at any time in the settings.",
-  "PRIVACY_POLICY": "Privacy Policy",
-  "ONB_SHORTCUTS": "Select Your Shortcuts",
-  "GRAPH_STATE_UNABLE_TO_CREATE_MEMBER": "Current Node Graph setup disallows creation of a new layer next to the selected one.",
-  "PRIMARY_TOOLSET": "Primary Toolset",
-  "OPEN_ONBOARDING_WINDOW": "Open onboarding window",
-  "USER_NOT_FOUND": "Please enter the email you used to purchase the Founder's Edition.",
-  "SESSION_NOT_VALID": "Session is not valid, please log in again",
-  "SESSION_NOT_FOUND": "Session not found, try logging in again",
-  "INTERNAL_SERVER_ERROR": "There was an internal server error. Please try again later.",
-  "TOO_MANY_REQUESTS": "Too many requests. Try again in {0} seconds.",
-  "SESSION_EXPIRED": "Session expired. Please log in again.",
-  "CONNECTION_ERROR": "Connection error. Please check your internet connection.",
-  "FAIL_LOAD_USER_DATA": "Failed to load saved user data",
-  "LOGOUT": "Logout",
-  "LOGGED_IN_AS": "Hello",
-  "EMAIL_SENT": "Email sent! Check your inbox.",
-  "RESEND_ACTIVATION": "Resend",
-  "INVALID_TOKEN": "Session is invalid or expired. Please log in again.",
-  "ENTER_EMAIL": "Enter your email",
-  "LOGIN_LINK": "Send Login Link",
-  "LOGIN_LINK_INFO": "We'll email you a secure link to log in. No password needed.",
-  "ACCOUNT_WINDOW_TITLE": "Account",
-  "CONNECTION_TIMEOUT": "Connection timed out. Please try again.",
-  "OPEN_ACCOUNT_WINDOW": "Manage Account",
-  "AUTO_SCALE_BACKGROUND": "Auto scale background",
-  "UPDATES": "Updates",
-  "SCENE": "Scene",
-  "CUSTOM_BACKGROUND_SCALE": "Custom background scale",
-  "PRIMARY_BG_COLOR": "Primary background color",
-  "SECONDARY_BG_COLOR": "Secondary background color",
-  "RESET": "Reset",
-  "INSTALL": "Install",
-  "MANAGE_ACCOUNT": "Manage",
-  "OWNED_PRODUCTS": "Owned Content",
-  "INSTALLING": "Installing",
-  "INSTALLED": "Installed",
-  "ACCOUNT_PROVIDER_INFO": "Account handled by",
-  "UPDATE": "Update",
-  "AUTOSAVE_OPEN_FOLDER": "Open autosave folder",
-  "AUTOSAVE_OPEN_FOLDER_DESCRIPTIVE": "Open the folder where autosaves are stored",
-  "AUTOSAVE_TOGGLE_DESCRIPTIVE": "Enable/disable autosave",
-  "FOUNDERS_BUNDLE": "Founder's Bundle",
-  "FOUNDERS_BUNDLE_SUBTEXT": "Support PixiEditor and boost your productivity!",
-  "BECOME_A_FOUNDER": "Become a Founder",
-  "LOGIN": "Login",
-  "NOT_FOUNDER_YET": "Not a Founder yet?",
-  "ERROR_GRAPH": "Graph setup produced an error. Fix it in the node graph",
-  "COLOR_MATRIX_FILTER_NODE": "Color Matrix Filter",
-  "WORKSPACE": "Workspace",
-  "EXPORT_ZONE_NODE": "Export Zone",
-  "IS_DEFAULT_EXPORT": "Is Default Export",
-  "EXPORT_OUTPUT": "Export Output",
-  "RENDER_OUTPUT_SIZE": "Render Output Size",
-  "RENDER_OUTPUT_CENTER": "Render Output Center",
-  "COLOR_PICKER": "Color Picker",
-  "UNAUTHORIZED_ACCESS": "Unauthorized access",
-  "SEPARATE_SHAPES": "Separate Shapes",
-  "SEPARATE_SHAPES_DESCRIPTIVE": "Separate shapes from current vector into individual layers",
-  "TEXT": "Text",
-  "EXTRACT_SELECTED_TEXT": "Extract selected text",
-  "EXTRACT_SELECTED_TEXT_DESCRIPTIVE": "Extract selected text into new layer.",
-  "EXTRACT_SELECTED_CHARACTERS": "Extract selected characters",
-  "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE": "Extract individual characters from selection into new layers.",
-  "STEP_START": "Step back to closest cel",
-  "STEP_END": "Step forward to closest cel",
-  "STEP_FORWARD": "Step forward one frame",
-  "STEP_BACK": "Step back one frame",
-  "ANIMATION_QUALITY_PRESET": "Quality Preset",
-  "VERY_LOW_QUALITY_PRESET": "Very Low",
-  "LOW_QUALITY_PRESET": "Low",
-  "MEDIUM_QUALITY_PRESET": "Medium",
-  "HIGH_QUALITY_PRESET": "High",
-  "VERY_HIGH_QUALITY_PRESET": "Very High",
-  "EXPORT_FRAMES": "Export Frames",
-  "NORMALIZE_OFFSET": "Normalize Offset",
-  "TANGENT": "Tangent",
-  "EVALUATE_PATH_NODE": "Evaluate Path",
-  "OLD_MIN": "Old Min",
-  "OLD_MAX": "Old Max",
-  "NEW_MIN": "New Min",
-  "NEW_MAX": "New Max",
-  "REMAP_NODE": "Remap",
-  "TEXT_TOOL_ACTION_DISPLAY": "Click on the canvas to add a new text (drag while clicking to set the size). Click on existing text to edit it.",
-  "PASTE_CELS": "Paste cels",
-  "SCALE_X": "Scale X",
-  "SCALE_Y": "Scale Y",
-  "TRANSLATE_X": "Translate X",
-  "TRANSLATE_Y": "Translate Y",
-  "SKEW_X": "Skew X",
-  "SKEW_Y": "Skew Y",
-  "PERSPECTIVE_0": "Perspective 0",
-  "PERSPECTIVE_1": "Perspective 1",
-  "PERSPECTIVE_2": "Perspective 2",
-  "COMPOSE_MATRIX": "Compose Matrix",
-  "DECOMPOSE_MATRIX": "Decompose Matrix",
-  "NORMALIZE_COORDINATES": "Normalize Coordinates",
-  "TRANSFORMED_POSITION": "Transformed Position",
-  "ACCOUNT_PROVIDER_NOT_AVAILABLE": "This build of PixiEditor does not support accounts. Use the official build from pixieditor.net to manage your account.",
-  "STEAM_OFFLINE": "Cannot validate the account. Steam is offline. Make sure Steam client is running and you are logged in.",
-  "ERROR_GPU_RESOURCES_CREATION": "Failed to create resources: Try updating your GPU drivers or try setting different rendering api in settings. \nError: '{0}'",
-  "ERROR_SAVING_PREFERENCES_DESC": "Failed to save preferences with error: '{0}'. Please check if you have write permissions to the PixiEditor data folder.",
-  "ERROR_SAVING_PREFERENCES": "Failed to save preferences",
-  "PREFERRED_RENDERER": "Preferred Render Api"
+    "RECENT_FILES": "Recent Files",
+    "OPEN_FILE": "Open file",
+    "NEW_FILE": "New",
+    "RECENT_EMPTY_TEXT": "So much empty space",
+    "LANGUAGE": "Language",
+    "GENERAL": "General",
+    "DISCORD": "Discord",
+    "KEY_BINDINGS": "Key Bindings",
+    "MISC": "Misc",
+    "SHOW_STARTUP_WINDOW": "Show Startup Window",
+    "RECENT_FILE_LENGTH": "Recent file list length",
+    "RECENT_FILE_LENGTH_TOOLTIP": "How many documents are shown under File > Recent. Default: 8",
+    "DEFAULT_NEW_SIZE": "Default new file size",
+    "WIDTH": "Width",
+    "HEIGHT": "Height",
+    "TOOLS": "Tools",
+    "ENABLE_SHARED_TOOLBAR": "Enable shared toolbar",
+    "AUTOMATIC_UPDATES": "Automatic Updates",
+    "CHECK_FOR_UPDATES": "Check updates on startup",
+    "UPDATE_STREAM": "Update stream",
+    "UPDATE_CHANNEL_HELP_TOOLTIP": "Update channels can only be changed in standalone version (downloaded from https://pixieditor.net).\nSteam and Microsoft Store versions handle updates separately.",
+    "DEBUG": "Debug",
+    "ENABLE_DEBUG_MODE": "Enable Debug mode",
+    "OPEN_CRASH_REPORTS_DIR": "Open crash reports directory",
+    "DISCORD_RICH_PRESENCE": "Rich Presence",
+    "ENABLED": "Enabled",
+    "SHOW_IMAGE_NAME": "Show image name",
+    "SHOW_IMAGE_SIZE": "Show image size",
+    "SHOW_LAYER_COUNT": "Show layer count",
+    "FILE": "File",
+    "RECENT": "Recent",
+    "OPEN": "Open",
+    "SAVE_PIXI": "Save (.pixi)",
+    "SAVE_AS_PIXI": "Save as... (.pixi)",
+    "EXPORT_IMG": "Export (.png, .jpg, etc.)",
+    "EDIT": "Edit",
+    "EXIT": "Exit",
+    "PERCENTAGE": "Percentage",
+    "ABSOLUTE": "Absolute",
+    "PRESERVE_ASPECT_RATIO": "Preserve aspect ratio",
+    "ANCHOR_POINT": "Anchor point",
+    "RESIZE_IMAGE": "Resize image",
+    "RESIZE": "Resize",
+    "DOCUMENTATION": "Documentation",
+    "WEBSITE": "Website",
+    "OPEN_WEBSITE": "Open website",
+    "REPOSITORY": "Repository",
+    "OPEN_REPOSITORY": "Open repository",
+    "OPEN_DOCUMENTATION": "Open documentation",
+    "LICENSE": "License",
+    "OPEN_LICENSE": "Open license",
+    "THIRD_PARTY_LICENSES": "Third party licenses",
+    "OPEN_THIRD_PARTY_LICENSES": "Open third party licenses",
+    "APPLY_TRANSFORM": "Apply transform",
+    "INCREASE_TOOL_SIZE": "Increase tool size",
+    "DECREASE_TOOL_SIZE": "Decrease tool size",
+    "UPDATE_READY": "Update is ready to be installed. Do you want to install it now?",
+    "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Couldn't update without admin privileges. Please run PixiEditor as administrator.",
+    "INSUFFICIENT_PERMISSIONS": "Insufficient permissions",
+    "VERSION": "Version {0}",
+    "BUILD_ID": "Build ID: {0}",
+    "OPEN_TEMP_DIR": "Open temp directory",
+    "OPEN_LOCAL_APPDATA_DIR": "Open Local AppData directory",
+    "OPEN_ROAMING_APPDATA_DIR": "Open Roaming AppData directory",
+    "OPEN_INSTALLATION_DIR": "Open installation directory",
+    "DUMP_ALL_COMMANDS": "Dump all commands",
+    "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Dump all commands to a text file",
+    "CRASH": "Crash",
+    "CRASH_APP": "Crash application",
+    "DELETE_USR_PREFS": "Delete user preferences (Roaming AppData)",
+    "DELETE_SHORTCUT_FILE": "Delete shortcut file (Roaming AppData)",
+    "DELETE_EDITOR_DATA": "Delete editor data (Local AppData)",
+    "GENERATE_KEY_BINDINGS_TEMPLATE": "Generate key bindings template",
+    "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generate key bindings json template",
+    "VALIDATE_SHORTCUT_MAP": "Validate shortcut map",
+    "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Validates shortcut map",
+    "VALIDATION_KEYS_NOTICE_DIALOG": "Empty keys: {0}\nUnknown Commands: {1}",
+    "RESULT": "Result",
+    "CLEAR_RECENT_DOCUMENTS": "Clear recent documents",
+    "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Clear recently opened documents",
+    "OPEN_CMD_DEBUG_WINDOW": "Open command debug window",
+    "PATH_DOES_NOT_EXIST": "{0} does not exist.",
+    "LOCATION_DOES_NOT_EXIST": "Location does not exist.",
+    "FILE_NOT_FOUND": "File not found.",
+    "ARE_YOU_SURE": "Are you sure?",
+    "ARE_YOU_SURE_PATH_FULL_PATH": "Are you sure you want to delete {0}?\nThis data will be lost for all installations.\n(Full Path: {1})",
+    "FAILED_TO_OPEN_FILE": "Failed to open the file",
+    "OLD_FILE_FORMAT": "Old file format",
+    "OLD_FILE_FORMAT_DESCRIPTION": "This .pixi file uses the old format,\n which is no longer supported and can't be opened.",
+    "NOTHING_FOUND": "Nothing found",
+    "EXPORT": "Export",
+    "EXPORT_IMAGE": "Export image",
+    "IMPORT": "Import",
+    "SHORTCUT_TEMPLATES": "Shortcut templates",
+    "RESET_ALL": "Reset all",
+    "LAYER": "Layer",
+    "LAYER_DELETE_ALL_SELECTED": "Delete all selected layers/folders",
+    "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Delete all selected layers and/or folders",
+    "NEW_FOLDER": "New folder",
+    "CREATE_NEW_FOLDER": "Create new folder",
+    "NEW_LAYER": "New layer",
+    "CREATE_NEW_LAYER": "Create new layer",
+    "NEW_IMAGE": "New image",
+    "CREATE_NEW_IMAGE": "Create new image",
+    "SAVE": "Save",
+    "SAVE_AS": "Save as...",
+    "IMAGE": "Image",
+    "SAVE_IMAGE": "Save image",
+    "SAVE_IMAGE_AS": "Save image as new",
+    "DUPLICATE": "Duplicate",
+    "DUPLICATE_SELECTED_LAYER": "Duplicate selected layer",
+    "CREATE_MASK": "Create mask",
+    "DELETE_MASK": "Delete mask",
+    "TOGGLE_MASK": "Toggle mask",
+    "APPLY_MASK": "Apply mask",
+    "TOGGLE_VISIBILITY": "Toggle visibility",
+    "MOVE_MEMBER_UP": "Move member upwards",
+    "MOVE_MEMBER_UP_DESCRIPTIVE": "Move selected layer or folder upwards",
+    "MOVE_MEMBER_DOWN": "Move member downwards",
+    "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Move selected layer or folder downwards",
+    "MERGE_ALL_SELECTED_LAYERS": "Merge all selected layers",
+    "MERGE_WITH_ABOVE": "Merge selected layer with above",
+    "MERGE_WITH_ABOVE_DESCRIPTIVE": "Merge selected layer with the one above it",
+    "MERGE_WITH_BELOW": "Merge selected layer with below",
+    "MERGE_WITH_BELOW_DESCRIPTIVE": "Merge selected layer with the one below it",
+    "ADD_REFERENCE_LAYER": "Add Reference Layer",
+    "DELETE_REFERENCE_LAYER": "Delete reference layer",
+    "TRANSFORM_REFERENCE_LAYER": "Transform reference layer",
+    "TOGGLE_REFERENCE_LAYER_POS": "Toggle reference layer position",
+    "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Toggle reference layer between topmost or most below",
+    "RESET_REFERENCE_LAYER_POS": "Reset reference layer position",
+    "CLIP_CANVAS": "Clip Canvas",
+    "FLIP_IMG_VERTICALLY": "Flip Image Vertically",
+    "FLIP_IMG_HORIZONTALLY": "Flip Image Horizontally",
+    "FLIP_LAYERS_VERTICALLY": "Flip Selected Layers Vertically",
+    "FLIP_LAYERS_HORIZONTALLY": "Flip Selected Layers Horizontally",
+    "ROT_IMG_90": "Rotate Image 90 degrees",
+    "ROT_IMG_180": "Rotate Image 180 degrees",
+    "ROT_IMG_-90": "Rotate Image -90 degrees",
+    "ROT_LAYERS_90": "Rotate Selected Layers 90 degrees",
+    "ROT_LAYERS_180": "Rotate Selected Layers 180 degrees",
+    "ROT_LAYERS_-90": "Rotate Selected Layers -90 degrees",
+    "TOGGLE_VERT_SYMMETRY_AXIS": "Toggle vertical symmetry axis",
+    "TOGGLE_HOR_SYMMETRY_AXIS": "Toggle horizontal symmetry axis",
+    "DELETE_SELECTED": "Delete selected",
+    "DELETE_SELECTED_DESCRIPTIVE": "Delete selected element (layer, pixels, etc.)",
+    "RESIZE_DOCUMENT": "Resize document",
+    "RESIZE_CANVAS": "Resize canvas",
+    "CENTER_CONTENT": "Center content",
+    "CUT": "Cut",
+    "CUT_DESCRIPTIVE": "Cut selected area/layers",
+    "PASTE": "Paste",
+    "PASTE_DESCRIPTIVE": "Paste clipboard contents",
+    "PASTE_AS_NEW_LAYER": "Paste as new layer",
+    "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Paste from clipboard as new layer",
+    "PASTE_REFERENCE_LAYER": "Paste reference layer",
+    "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Paste clipboard contents as reference layer",
+    "PASTE_COLOR": "Paste color",
+    "PASTE_COLOR_DESCRIPTIVE": "Paste color from clipboard",
+    "PASTE_COLOR_SECONDARY": "Paste color as secondary",
+    "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Paste color from clipboard as secondary color",
+    "CLIPBOARD": "Clipboard",
+    "COPY": "Copy",
+    "COPY_DESCRIPTIVE": "Copy to clipboard",
+    "COPY_COLOR_HEX": "Copy primary color (HEX)",
+    "COPY_COLOR_HEX_DESCRIPTIVE": "Copy primary color as HEX code",
+    "COPY_COLOR_RGB": "Copy primary color (RGB)",
+    "COPY_COLOR_RGB_DESCRIPTIVE": "Copy primary color as RGB code",
+    "COPY_COLOR_SECONDARY_HEX": "Copy secondary color (HEX)",
+    "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copy secondary color as HEX code",
+    "COPY_COLOR_SECONDARY_RGB": "Copy secondary color (RGB)",
+    "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copy secondary color as RGB code",
+    "PALETTE_COLORS": "Palette Colors",
+    "REPLACE_SECONDARY_BY_PRIMARY": "Replace secondary color by primary",
+    "REPLACE_PRIMARY_BY_SECONDARY": "Replace primary color by secondary",
+    "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Replace the primary color by the secondary color",
+    "OPEN_PALETTE_BROWSER": "Open palette browser",
+    "OVERWRITE_PALETTE_CONSENT": "Palette '{0}' already exists, do you want to overwrite it?",
+    "PALETTE_EXISTS": "Palette already exists",
+    "REPLACE_PALETTE_CONSENT": "Replace current palette with selected one?",
+    "REPLACE_PALETTE": "Replace current palette",
+    "SELECT_COLOR_1": "Select color 1",
+    "SELECT_COLOR_2": "Select color 2",
+    "SELECT_COLOR_3": "Select color 3",
+    "SELECT_COLOR_4": "Select color 4",
+    "SELECT_COLOR_5": "Select color 5",
+    "SELECT_COLOR_6": "Select color 6",
+    "SELECT_COLOR_7": "Select color 7",
+    "SELECT_COLOR_8": "Select color 8",
+    "SELECT_COLOR_9": "Select color 9",
+    "SELECT_COLOR_10": "Select color 10",
+    "SELECT_TOOL": "Select {0} Tool",
+    "SELECT_COLOR_1_DESCRIPTIVE": "Select the first color in the palette",
+    "SELECT_COLOR_2_DESCRIPTIVE": "Select the second color in the palette",
+    "SELECT_COLOR_3_DESCRIPTIVE": "Select the third color in the palette",
+    "SELECT_COLOR_4_DESCRIPTIVE": "Select the fourth color in the palette",
+    "SELECT_COLOR_5_DESCRIPTIVE": "Select the fifth color in the palette",
+    "SELECT_COLOR_6_DESCRIPTIVE": "Select the sixth color in the palette",
+    "SELECT_COLOR_7_DESCRIPTIVE": "Select the seventh color in the palette",
+    "SELECT_COLOR_8_DESCRIPTIVE": "Select the eighth color in the palette",
+    "SELECT_COLOR_9_DESCRIPTIVE": "Select the ninth color in the palette",
+    "SELECT_COLOR_10_DESCRIPTIVE": "Select the tenth color in the palette",
+    "SWAP_COLORS": "Swap colors",
+    "SWAP_COLORS_DESCRIPTIVE": "Swap primary and secondary colors",
+    "SEARCH": "Search",
+    "COMMAND_SEARCH": "Command search",
+    "OPEN_COMMAND_SEARCH": "Open command search window",
+    "SELECT": "Select",
+    "DESELECT": "Deselect",
+    "INVERT": "Invert",
+    "SELECTION": "Selection",
+    "SELECT_ALL": "Select all",
+    "SELECT_ALL_DESCRIPTIVE": "Select everything",
+    "CLEAR_SELECTION": "Clear selection",
+    "INVERT_SELECTION": "Invert selection",
+    "INVERT_SELECTION_DESCRIPTIVE": "Invert the selection",
+    "TRANSFORM_SELECTED_AREA": "Transform selected area",
+    "NUDGE_SELECTED_LEFT": "Nudge selected object left",
+    "NUDGE_SELECTED_RIGHT": "Nudge selected object right",
+    "NUDGE_SELECTED_UP": "Nudge selected object up",
+    "NUDGE_SELECTED_DOWN": "Nudge selected object down",
+    "MASK_FROM_SELECTION": "New mask from selection",
+    "MASK_FROM_SELECTION_DESCRIPTIVE": "Selection to new mask",
+    "ADD_SELECTION_TO_MASK": "Add selection to mask",
+    "SUBTRACT_SELECTION_FROM_MASK": "Subtract selection from mask",
+    "INTERSECT_SELECTION_MASK": "Intersect selection with mask",
+    "SELECTION_TO_MASK": "Selection to mask",
+    "TO_NEW_MASK": "to new mask",
+    "ADD_TO_MASK": "add to mask",
+    "SUBTRACT_FROM_MASK": "subtract from mask",
+    "INTERSECT_WITH_MASK": "intersect with mask",
+    "STYLUS": "Stylus",
+    "TOGGLE_PEN_MODE": "Toggle pen mode",
+    "UNDO": "Undo",
+    "UNDO_DESCRIPTIVE": "Undo last action",
+    "REDO": "Redo",
+    "REDO_DESCRIPTIVE": "Redo last action",
+    "WINDOWS": "Windows",
+    "TOGGLE_GRIDLINES": "Toggle gridlines",
+    "GRIDLINES_SIZE": "Grid Size",
+    "ZOOM_IN": "Zoom in",
+    "ZOOM_OUT": "Zoom out",
+    "NEW_WINDOW_FOR_IMG": "New window for current image",
+    "CENTER_ACTIVE_VIEWPORT": "Center active viewport",
+    "FLIP_VIEWPORT_HORIZONTALLY": "Flip viewport horizontally",
+    "FLIP_VIEWPORT_VERTICALLY": "Flip viewport vertically",
+    "SETTINGS": "Settings",
+    "OPEN_SETTINGS": "Open settings",
+    "OPEN_SETTINGS_DESCRIPTIVE": "Open settings window",
+    "OPEN_STARTUP_WINDOW": "Open startup window",
+    "OPEN_SHORTCUT_WINDOW": "Open shortcuts window",
+    "OPEN_ABOUT_WINDOW": "Open about window",
+    "OPEN_PREVIEW_WINDOW": "Open preview window",
+    "ERROR": "Error",
+    "INTERNAL_ERROR": "Internal error",
+    "ERROR_SAVE_LOCATION": "Couldn't save the file to the specified location",
+    "ERROR_WHILE_SAVING": "An internal error occured while saving. Please try again.",
+    "UNKNOWN_ERROR_SAVING": "An error occured while saving.",
+    "FAILED_ASSOCIATE_LOSPEC": "Failed to associate Lospec Palette protocol.",
+    "REDDIT": "Reddit",
+    "GITHUB": "GitHub",
+    "YOUTUBE": "YouTube",
+    "DONATE": "Donate",
+    "YES": "Yes",
+    "NO": "No",
+    "CANCEL": "Cancel",
+    "UNNAMED": "Unnamed",
+    "OPEN_COMMAND_DEBUG_WINDOW": "Open command debug window",
+    "DELETE": "Delete",
+    "USER_PREFS": "User preferences (Roaming)",
+    "SHORTCUT_FILE": "Shortcut file (Roaming)",
+    "EDITOR_DATA": "Editor data (Local)",
+    "MOVE_VIEWPORT_TOOLTIP": "Moves viewport. ({0})",
+    "MOVE_VIEWPORT_ACTION_DISPLAY": "Click and move to pan the viewport",
+    "MOVE_TOOL_TOOLTIP": "Select and transform layers ({0}).",
+    "MOVE_TOOL_ACTION_DISPLAY": "Hold mouse to move selected pixels. Hold Ctrl to move all layers.",
+    "PEN_TOOL_TOOLTIP": "Pen. ({0})",
+    "PEN_TOOL_ACTION_DISPLAY": "Click and move to draw.",
+    "PIXEL_PERFECT_SETTING": "Pixel perfect",
+    "RECTANGLE_TOOL_TOOLTIP": "Draws rectangle on canvas ({0}). Hold Shift to draw a square.",
+    "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a rectangle. Hold Shift to draw a square.",
+    "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to draw a square.",
+    "KEEP_ORIGINAL_IMAGE_SETTING": "Keep original image",
+    "ROTATE_VIEWPORT_TOOLTIP": "Rotates viewport. ({0})",
+    "ROTATE_VIEWPORT_ACTION_DISPLAY": "Click and move to rotate the viewport",
+    "SELECT_TOOL_TOOLTIP": "Selects area. ({0})",
+    "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to select an area. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
+    "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to add to the current selection.",
+    "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Click and move to subtract from the current selection.",
+    "ZOOM_TOOL_TOOLTIP": "Zooms viewport ({0}). Click to zoom in, hold alt and click to zoom out.",
+    "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.",
+    "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Click and move to zoom. Click to zoom out, release ctrl and click to zoom in.",
+    "BRIGHTNESS_TOOL_TOOLTIP": "Makes pixels brighter or darker ({0}). Hold Ctrl to make pixels darker.",
+    "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Draw on pixels to make them brighter. Hold Ctrl to darken.",
+    "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Draw on pixels to make them darker. Release Ctrl to brighten.",
+    "COLOR_PICKER_TOOLTIP": "Picks the primary color from the canvas. ({0})",
+    "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Click to pick colors. Hold Ctrl to hide the canvas. Hold Shift to hide the reference layer",
+    "ELLIPSE_TOOL_TOOLTIP": "Draws an ellipse on canvas ({0}). Hold Shift to draw a circle.",
+    "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move mouse to draw an ellipse. Hold Shift to draw a circle.",
+    "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a circle.",
+    "ERASER_TOOL_TOOLTIP": "Erases color from pixel. ({0})",
+    "ERASER_TOOL_ACTION_DISPLAY": "Click and move to erase.",
+    "FLOOD_FILL_TOOL_TOOLTIP": "Fills area with color. ({0})",
+    "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Press on an area to fill it. Hold down Ctrl to consider all layers.",
+    "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Press on an area to fill it. Release Ctrl to only consider the current layers.",
+    "LASSO_TOOL_TOOLTIP": "Lasso. ({0})",
+    "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to select pixels inside of the lasso. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
+    "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Click and move to add pixels inside of the lasso to the selection.",
+    "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Click and move to subtract pixels inside of the lasso from the selection.",
+    "LINE_TOOL_TOOLTIP": "Draws line on canvas ({0}). Hold Shift to enable snapping.",
+    "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a line. Hold Shift to enable snapping.",
+    "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a line with snapping enabled.",
+    "MAGIC_WAND_TOOL_TOOLTIP": "Magic Wand ({0}). Flood's the selection",
+    "MAGIC_WAND_ACTION_DISPLAY": "Click to flood the selection.",
+    "PEN_TOOL": "Pen",
+    "BRIGHTNESS_TOOL": "Brightness",
+    "COLOR_PICKER_TOOL": "Color Picker",
+    "ELLIPSE_TOOL": "Ellipse",
+    "ERASER_TOOL": "Eraser",
+    "FLOOD_FILL_TOOL": "Flood Fill",
+    "LASSO_TOOL": "Lasso",
+    "LINE_TOOL": "Line",
+    "MAGIC_WAND_TOOL": "Magic Wand",
+    "MOVE_TOOL": "Move",
+    "MOVE_VIEWPORT_TOOL": "Move Viewport",
+    "RECTANGLE_TOOL": "Rectangle",
+    "ROTATE_VIEWPORT_TOOL": "Rotate Viewport",
+    "SELECT_TOOL_NAME": "Select",
+    "ZOOM_TOOL": "Zoom",
+    "SHAPE_LABEL": "Shape",
+    "MODE_LABEL": "Mode",
+    "SCOPE_LABEL": "Scope",
+    "FILL_SHAPE_LABEL": "Fill shape",
+    "FILL_COLOR_LABEL": "Fill color",
+    "TOOL_SIZE_LABEL": "Tool size",
+    "STRENGTH_LABEL": "Strength",
+    "NEW": "New",
+    "ADD": "Add",
+    "SUBTRACT": "Subtract",
+    "INTERSECT": "Intersect",
+    "RECTANGLE": "Rectangle",
+    "CIRCLE": "Circle",
+    "ABOUT": "About",
+    "MINIMIZE": "Minimize",
+    "RESTORE": "Restore",
+    "MAXIMIZE": "Maximize",
+    "CLOSE": "Close",
+    "EXPORT_SIZE_HINT": "If you want to share the image, try {0}% for the best clarity",
+    "CREATE": "Create",
+    "BASE_LAYER_NAME": "Base layer",
+    "ENABLE_MASK": "Enable mask",
+    "SELECTED_AREA_EMPTY": "Selected area is empty",
+    "NOTHING_TO_COPY": "Nothing to copy",
+    "REFERENCE_LAYER_PATH": "Reference layer path",
+    "FLIP": "Flip",
+    "ROTATION": "Rotation",
+    "ROT_IMG_90_D": "Rotate Image 90°",
+    "ROT_IMG_180_D": "Rotate Image 180°",
+    "ROT_IMG_-90_D": "Rotate Image -90°",
+    "ROT_LAYERS_90_D": "Rotate Selected Layers 90°",
+    "ROT_LAYERS_180_D": "Rotate Selected Layers 180°",
+    "ROT_LAYERS_-90_D": "Rotate Selected Layers -90°",
+    "UNNAMED_PALETTE": "Unnamed Palette",
+    "CLICK_SELECT_PRIMARY": "Click to select as main color.",
+    "PEN_MODE": "Pen mode",
+    "VIEW": "View",
+    "HORIZONTAL_LINE_SYMMETRY": "Horizontal line symmetry",
+    "VERTICAL_LINE_SYMMETRY": "Vertical line symmetry",
+    "COLOR_PICKER_TITLE": "Color Picker",
+    "COLOR_SLIDERS_TITLE": "Color Sliders",
+    "PALETTE_TITLE": "Palette",
+    "SWATCHES_TITLE": "Swatches",
+    "LAYERS_TITLE": "Layers",
+    "PREVIEW_TITLE": "Preview",
+    "NORMAL_BLEND_MODE": "Normal",
+    "ERASE_BLEND_MODE": "Erase",
+    "DARKEN_BLEND_MODE": "Darken",
+    "MULTIPLY_BLEND_MODE": "Multiply",
+    "COLOR_BURN_BLEND_MODE": "Color burn",
+    "LIGHTEN_BLEND_MODE": "Lighten",
+    "SCREEN_BLEND_MODE": "Screen",
+    "COLOR_DODGE_BLEND_MODE": "Color dodge",
+    "OVERLAY_BLEND_MODE": "Overlay",
+    "SOFT_LIGHT_BLEND_MODE": "Soft light",
+    "HARD_LIGHT_BLEND_MODE": "Hard light",
+    "DIFFERENCE_BLEND_MODE": "Difference",
+    "EXCLUSION_BLEND_MODE": "Exclusion",
+    "HUE_BLEND_MODE": "Hue",
+    "SATURATION_BLEND_MODE": "Saturation",
+    "LUMINOSITY_BLEND_MODE": "Luminosity",
+    "COLOR_BLEND_MODE": "Color",
+    "NOT_SUPPORTED_BLEND_MODE": "Not supported",
+    "RESTART": "Restart",
+    "SORT_BY": "Sort by",
+    "NAME": "Name",
+    "COLORS": "Colors",
+    "DEFAULT": "Default",
+    "ALPHABETICAL": "Alphabetical",
+    "COLOR_COUNT": "Color count",
+    "ANY": "Any",
+    "MAX": "Max",
+    "MIN": "Min",
+    "EXACT": "Exact",
+    "ASCENDING": "Ascending",
+    "DESCENDING": "Descending",
+    "NAME_IS_TOO_LONG": "The name is too long",
+    "STOP_IT_TEXT1": "That's enough. Tidy up your file names.",
+    "STOP_IT_TEXT2": "Can you stop copying these names please?",
+    "REPLACER_TOOLTIP": "Right click on palette color and choose 'Replace' or drop it here.",
+    "CLICK_TO_CHOOSE_COLOR": "Click to choose the color",
+    "REPLACE_COLOR": "Replace color",
+    "PALETTE_COLOR_TOOLTIP": "Click to select as main color. Drag and drop onto another palette color to swap them.",
+    "ADD_FROM_SWATCHES": "Add from swatches",
+    "ADD_COLOR_TO_PALETTE": "Add color to palette",
+    "USE_IN_CURRENT_IMAGE": "Use in current image",
+    "ADD_TO_FAVORITES": "Add to favorites",
+    "BROWSE_PALETTES": "Browse palettes",
+    "LOAD_PALETTE": "Load palette",
+    "SAVE_PALETTE": "Save palette",
+    "FAVORITES": "Favorites",
+    "ADD_FROM_CURRENT_PALETTE": "Add from current palette",
+    "OPEN_PALETTES_DIR_TOOLTIP": "Open palettes directory in explorer",
+    "BROWSE_ON_LOSPEC_TOOLTIP": "Browse palettes on Lospec",
+    "IMPORT_FROM_FILE_TOOLTIP": "Import from file",
+    "TOP_LEFT": "Top left",
+    "TOP_CENTER": "Top center",
+    "TOP_RIGHT": "Top right",
+    "MIDDLE_LEFT": "Middle left",
+    "MIDDLE_CENTER": "Middle center",
+    "MIDDLE_RIGHT": "Middle right",
+    "BOTTOM_LEFT": "Bottom left",
+    "BOTTOM_CENTER": "Bottom center",
+    "BOTTOM_RIGHT": "Bottom right",
+    "CLIP_TO_BELOW": "Clip to member below",
+    "MOVE_UPWARDS": "Move upwards",
+    "MOVE_DOWNWARDS": "Move downwards",
+    "MERGE_SELECTED": "Merge selected",
+    "LOCK_TRANSPARENCY": "Lock transparency",
+    "COULD_NOT_LOAD_PALETTE": "Couldn't fetch palettes",
+    "NO_PALETTES_FOUND": "No palettes found.",
+    "LOSPEC_LINK_TEXT": "I heard you can find some here: lospec.com/palette-list",
+    "PALETTE_BROWSER": "Palette Browser",
+    "DELETE_PALETTE_CONFIRMATION": "Are you sure you want to delete this palette? This cannot be undone.",
+    "SHORTCUTS_IMPORTED": "Shortcuts from {0} were imported successfully.",
+    "SHORTCUT_PROVIDER_DETECTED": "We've detected, that you have {0} installed. Do you want to import shortcuts from it?",
+    "IMPORT_INSTALLATION_OPTION1": "Import from installation",
+    "IMPORT_INSTALLATION_OPTION2": "Use defaults",
+    "IMPORT_FROM_TEMPLATE": "Import from template",
+    "SHORTCUTS_IMPORTED_SUCCESS": "Shortcuts were imported successfully.",
+    "WARNING_RESET_SHORTCUTS_DEFAULT": "Are you sure you want to reset all shortcuts to their default value?",
+    "SUCCESS": "Success",
+    "WARNING": "Warning",
+    "ERROR_IMPORTING_IMAGE": "An error occured while importing the image.",
+    "SHORTCUTS_CORRUPTED_TITLE": "Corrupted shortcuts file",
+    "SHORTCUTS_CORRUPTED": "Shortcuts file was corrupted, resetting to default.",
+    "FAILED_DOWNLOAD_PALETTE": "Failed to download palette",
+    "FILE_INCORRECT_FORMAT": "The file was not in a correct format",
+    "INVALID_FILE": "Invalid file",
+    "SHORTCUTS_FILE_INCORRECT_FORMAT": "Shortcuts file was not in a correct format",
+    "UNSUPPORTED_FILE_FORMAT": "This file format is unsupported",
+    "ALREADY_ASSIGNED": "Already assigned",
+    "REPLACE": "Replace",
+    "SWAP": "Swap",
+    "SHORTCUT_ALREADY_ASSIGNED_SWAP": "This shortcut is already assigned to '{0}'\nDo you want to replace the existing shortcut or swap the two?",
+    "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "This shortcut is already assigned to '{0}'\nDo you want to replace the existing shortcut?",
+    "UNSAVED_CHANGES": "Unsaved changes",
+    "DOCUMENT_MODIFIED_SAVE": "The document has been modified. Do you want to save changes?",
+    "PROJECT_MAINTAINERS": "Project Maintainers",
+    "OTHER_AWESOME_CONTRIBUTORS": "And other awesome contributors",
+    "HELP": "Help",
+    "STOP_IT_TEXT3": "No, really, stop it.",
+    "STOP_IT_TEXT4": "Don't you have anything better to do?",
+    "LINEAR_DODGE_BLEND_MODE": "Linear dodge (Add)",
+    "PRESS_ANY_KEY": "Press any key",
+    "NONE_SHORTCUT": "None",
+    "REFERENCE": "Reference",
+    "PUT_REFERENCE_LAYER_ABOVE": "Put reference layer above",
+    "PUT_REFERENCE_LAYER_BELOW": "Put reference layer below",
+    "TOGGLE_VERTICAL_SYMMETRY": "Toggle vertical symmetry",
+    "TOGGLE_HORIZONTAL_SYMMETRY": "Toggle horizontal symmetry",
+    "RESET_VIEWPORT": "Reset viewport",
+    "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Click and hold mouse to move pixels in selected layers.",
+    "CTRL_KEY": "Ctrl",
+    "SHIFT_KEY": "Shift",
+    "ALT_KEY": "Alt",
+    "RENAME": "Rename",
+    "PIXEL_UNIT": "px",
+    "OPEN_LOCALIZATION_DEBUG_WINDOW": "Open Localization Debug Window",
+    "FORCE_OTHER_FLOW_DIRECTION": "Force other flow direction",
+    "API_KEY": "API Key",
+    "LOCALIZATION_VIEW_TYPE": "Localization View Type",
+    "LOAD_LANGUAGE_FROM_FILE": "Load language from file",
+    "LOG_IN": "Log in",
+    "SYNC": "Sync",
+    "NOT_LOGGED_IN": "Not logged in",
+    "POE_EDITOR_ERROR": "POEditor Error: {0} {1}",
+    "HTTP_ERROR_MESSAGE": "HTTP Error: {0} {1}",
+    "LOGGED_IN": "Logged in",
+    "SYNCED_SUCCESSFULLY": "Synced successfully",
+    "EXCEPTION_ERROR": "Exception: {0}",
+    "DROP_PALETTE": "Drop palette here",
+    "SECURITY_ERROR": "Security error",
+    "SECURITY_ERROR_MSG": "No rights to write to the specified location.",
+    "IO_ERROR": "IO error",
+    "IO_ERROR_MSG": "Error while writing to disk.",
+    "COULD_NOT_SAVE_PALETTE": "There was an error while saving the palette.",
+    "NO_COLORS_TO_SAVE": "There are no colors to save.",
+    "CANVAS": "Canvas",
+    "SINGLE_LAYER": "Single Layer",
+    "CHOOSE": "Choose",
+    "REMOVE": "Remove",
+    "FILE_FORMAT_NOT_ASEPRITE_KEYS": "File is not a \".aseprite-keys\" file",
+    "FILE_HAS_INVALID_SHORTCUT": "The file contains an invalid shortcut",
+    "FILE_EXTENSION_NOT_SUPPORTED": "The file type '{0}' is not supported",
+    "ERROR_READING_FILE": "Error while reading the file",
+    "DISCARD_PALETTE": "Discard palette",
+    "DISCARD_PALETTE_CONFIRMATION": "Are you sure you want to discard current palette? This cannot be undone.",
+    "IMPORT_AS_NEW_LAYER": "Import as new layer",
+    "PASTE_AS_PRIMARY_COLOR": "Paste as primary color",
+    "IMPORT_AS_NEW_FILE": "Import as new file",
+    "IMPORT_PALETTE_FILE": "Import palette file",
+    "IMPORT_MULTIPLE_PALETTE_COLORS": "Import colors into palette",
+    "IMPORT_SINGLE_PALETTE_COLOR": "Import color into palette",
+    "IMPORT_AS_REFERENCE_LAYER": "Import as reference layer",
+    "NAVIGATOR_PICK_ACTION_DISPLAY": "Right-click to pick color, Shift-right-click to copy color to clipboard",
+    "OPEN_FILE_FROM_CLIPBOARD": "Open from clipboard",
+    "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Open from clipboard",
+    "OPEN_LOCALIZATION_DATA": "Do you want to open the LocalizationData.json?\nThe updated date has been put in the clipboard.\nNote that changes wont be applied until a restart",
+    "DOWNLOADING_LANGUAGE_FAILED": "Downloading language failed.\nAPI Key might have been overused.",
+    "LOCALIZATION_DATA_NOT_FOUND": "Localization data path not found",
+    "APPLY": "Apply",
+    "UPDATE_SOURCE": "Update source",
+    "COPY_TO_CLIPBOARD": "Copy to clipboard",
+    "LANGUAGE_FILE_NOT_FOUND": "Language file not found.\nLooking for {0}",
+    "PROJECT_ROOT_NOT_FOUND": "PixiEditor Project root not found.\nLooking for PixiEditor.csproj",
+    "LOCALIZATION_FOLDER_NOT_FOUND": "Localization folder not found.\nLooking for /Data/Localization",
+    "SELECT_A_LANGUAGE": "Select a language",
+    "DONE": "Done",
+    "SOURCE_UNSET_OR_MISSING": "Source missing/unset",
+    "SOURCE_NEWER": "Source newer",
+    "SOURCE_UP_TO_DATE": "Source is up to date",
+    "SOURCE_OLDER": "Cloud newer",
+    "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Click to pick colors from the reference layer.",
+    "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Click to pick colors from the canvas.",
+    "LOCALIZATION_DEBUG_WINDOW_TITLE": "Localization Debug Window",
+    "SHORTCUTS_TITLE": "Shortcuts",
+    "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
+    "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
+    "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally. Drag outside handles to rotate.",
+    "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to scale from center. Hold Shift to scale proportionally.",
+    "LOCAL_PALETTE_SOURCE_NAME": "Local",
+    "ERROR_FORBIDDEN_UNIQUE_NAME": "Extension unique name cannot start with 'pixieditor'.",
+    "ERROR_MISSING_METADATA": "Extension metadata key '{0}' is missing.",
+    "ERROR_NO_CLASS_ENTRY": "Extension class entry is missing on path '{0}'.",
+    "ERROR_NO_ENTRY_ASSEMBLY": "Extension entry assembly is missing on path '{0}'.",
+    "ERROR_MISSING_ADDITIONAL_CONTENT": "Your current setup doesn't allow loading this extension. Perhaps you don't own it or don't have it installed. You can purchase it here '{0}'.",
+    "BUY_SUPPORTER_PACK": "Buy Supporter Pack",
+    "NEWS": "News",
+    "DISABLE_NEWS_PANEL": "Disable News panel in startup window",
+    "FAILED_FETCH_NEWS": "Failed to fetch news",
+    "CROP_TO_SELECTION": "Crop to selection",
+    "CROP_TO_SELECTION_DESCRIPTIVE": "Crop image to selection",
+    "SHOW_CONTEXT_MENU": "Show context menu",
+    "ERASE": "Erase",
+    "USE_SECONDARY_COLOR": "Use secondary color",
+    "RIGHT_CLICK_MODE": "Right click mode",
+    "ADD_PRIMARY_COLOR_TO_PALETTE": "Add primary color to palette",
+    "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Add primary color to current palette",
+    "EXPORT_SAVE_TITLE": "Choose a location to save the image",
+    "BROWSE_DIRECTORY": "Browse Directory",
+    "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Not all documents were recovered",
+    "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not recover all documents. Git gud at saving your work.",
+    "SEND": "Send report",
+    "OPEN_DOCKABLE_MENU": "Open Tab",
+    "TIMELINE_TITLE": "Timeline",
+    "EXPORT_IMAGE_HEADER": "Image",
+    "EXPORT_ANIMATION_HEADER": "Animation",
+    "EXPORT_SPRITESHEET_HEADER": "Spritesheet",
+    "PIXI_FILE": "PixiEditor Files",
+    "PNG_FILE": "PNG Images",
+    "JPEG_FILE": "JPEG Images",
+    "WEBP_FILE": "WebP Images",
+    "GIF_FILE": "GIFs",
+    "BMP_FILE": "BMP Images",
+    "IMAGE_FILES": "Image Files",
+    "VIDEO_FILES": "Video Files",
+    "OPEN_TYPE_FONT": "OpenType Fonts",
+    "TRUE_TYPE_FONT": "TrueType Fonts",
+    "SVG_FILE": "Scalable Vector Graphics",
+    "MP4_FILE": "MP4 Videos",
+    "COLUMNS": "Columns",
+    "ROWS": "Rows",
+    "BACKGROUND": "Background",
+    "OPACITY": "Opacity",
+    "IS_VISIBLE": "Is visible",
+    "BLEND_MODE": "Blend mode",
+    "MASK": "Mask",
+    "MASK_IS_VISIBLE": "Mask is visible",
+    "OUTPUT": "Output",
+    "INPUT": "Input",
+    "NODE_GRAPH_TITLE": "Graph View",
+    "CONTENT": "Content",
+    "RADIUS": "Radius",
+    "STROKE_COLOR": "Stroke color",
+    "STROKE_WIDTH": "Stroke width",
+    "FILL_COLOR": "Fill color",
+    "TOP": "Top",
+    "BOTTOM": "Bottom",
+    "CHANNELS_DOCK_TITLE": "Channels",
+    "RED": "Red",
+    "GREEN": "Green",
+    "BLUE": "Blue",
+    "ALPHA": "Alpha",
+    "COLOR": "Color",
+    "COORDINATE": "Coordinate",
+    "VECTOR": "Vector",
+    "MATRIX": "Matrix",
+    "TRANSFORMED": "Transformed",
+    "GRAYSCALE": "Grayscale",
+    "CLAMP": "Clamp",
+    "SIZE": "Size",
+    "NOISE": "Noise",
+    "SCALE": "Scale",
+    "SEED": "Seed",
+    "KERNEL": "Kernel",
+    "KERNEL_VIEW_SUM": "Sum:",
+    "KERNEL_VIEW_SUM_TOOLTIP": "The sum of all values. You likely want to aim for a value of 1 or 0",
+    "GAIN": "Gain",
+    "BIAS": "Bias",
+    "TILE_MODE": "Tile Mode",
+    "ON_ALPHA": "On Alpha",
+    "OUTPUT_NODE": "Output",
+    "NOISE_NODE": "Noise",
+    "ELLIPSE_NODE": "Ellipse",
+    "LINE_NODE": "Line",
+    "LINE_START": "Start",
+    "LINE_END": "End",
+    "RECTANGLE_NODE": "Rectangle",
+    "CREATE_IMAGE_NODE": "Create Image",
+    "FOLDER_NODE": "Folder",
+    "IMAGE_LAYER_NODE": "Image Layer",
+    "KERNEL_FILTER_NODE": "Kernel Filter",
+    "MATH_NODE": "Math",
+    "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "Matrix Transform Filter",
+    "MERGE_NODE": "Merge",
+    "MODIFY_IMAGE_LEFT_NODE": "Begin Modify Image",
+    "MODIFY_IMAGE_RIGHT_NODE": "End Modify Image",
+    "COMBINE_CHANNELS_NODE": "Combine Channels",
+    "COMBINE_COLOR_NODE": "Combine Color",
+    "COMBINE_VECD_NODE": "Combine Vector",
+    "COMBINE_VECI_NODE": "Combine Integer Vector",
+    "SEPARATE_CHANNELS_NODE": "Separate Channels",
+    "SEPARATE_VECD_NODE": "Separate Vector",
+    "SEPARATE_VECI_NODE": "Separate Integer Vector",
+    "SEPARATE_COLOR_NODE": "Separate Color",
+    "TIME_NODE": "Time",
+    "FILTERS": "Filters",
+    "PREVIOUS": "Previous",
+    "FILL": "Fill",
+    "MATH_MODE": "Math Mode",
+    "NOISE_TYPE": "Noise Type",
+    "OCTAVES": "Octaves",
+    "ACTIVE_FRAME": "Active Frame",
+    "NORMALIZED_TIME": "Normalized Time",
+    "WITHOUT_FILTERS": "Without filters",
+    "RAW_LAYER_OUTPUT": "Raw",
+    "EXAMPLE_FILES": "Example Files",
+    "PROCEDURAL_GENERATION": "Procedural Animation",
+    "POND_EXAMPLE": "Pond",
+    "TREE_EXAMPLE": "Windy Tree",
+    "OUTLINE_EXAMPLE": "Automatic Outline",
+    "BETA_ANIMATIONS": "Animations",
+    "SLIME_EXAMPLE": "Animated Slime",
+    "APPLY_FILTER_NODE": "Apply Filter",
+    "FILTER": "Filter",
+    "LERP_NODE": "Lerp",
+    "GRAYSCALE_FILTER_NODE": "Grayscale Filter",
+    "FROM": "From",
+    "TO": "To",
+    "TIME": "Time",
+    "WARMING_UP": "Warming up",
+    "RENDERING_FRAME": "Generating Frame {0}/{1}",
+    "RENDERING_VIDEO": "Rendering Video",
+    "FINISHED": "Finished",
+    "GENERATING_SPRITE_SHEET": "Generating Sprite Sheet",
+    "RENDERING_IMAGE": "Rendering Image",
+    "PROGRESS_POPUP_TITLE": "Progress",
+    "POINTS": "Points",
+    "MIN_DISTANCE": "Min. Distance",
+    "MAX_POINTS": "Max. Points",
+    "DISTRIBUTE_POINTS": "Distribute points",
+    "REMOVE_CLOSE_POINTS": "Remove close points",
+    "RASTERIZE_SHAPE": "Rasterize Shape",
+    "MODE": "Mode",
+    "Factor": "Factor",
+    "NORMALIZE": "Normalize",
+    "WEIGHT_FACTOR": "Weight",
+    "STARS_EXAMPLE": "Stars",
+    "ADD_EMPTY_FRAME": "Add empty frame",
+    "DUPLICATE_FRAME": "Duplicate frame",
+    "DELETE_FRAME": "Remove frame",
+    "DEFAULT_MEMBER_NAME": "New Element",
+    "NO_PARSER_FOUND": "No file parser found for extension '{0}'",
+    "SELECT_FILE_FORMAT": "Select file format",
+    "SELECT_FILE_FORMAT_DESCRIPTION": "Multiple file types of the same format are supported. Please select the one you want to use.",
+    "NEW_PALETTE_FILE": "palette",
+    "ISLAND_EXAMPLE": "Islands",
+    "ONION_FRAMES_COUNT": "Onion frames",
+    "ONION_OPACITY": "Onion opacity",
+    "TOGGLE_ONION_SKINNING": "Toggle onion skinning",
+    "CHANGE_ACTIVE_FRAME_PREVIOUS": "Change active frame to previous",
+    "CHANGE_ACTIVE_FRAME_NEXT": "Change active frame to next",
+    "TOGGLE_ANIMATION": "Toggle animation",
+    "NEW_FROM_CLIPBOARD": "New from clipboard",
+    "OFFSET": "Offset",
+    "SHAPE": "Shape",
+    "STRUCTURE": "Structure",
+    "NUMBERS": "Numbers",
+    "OPERATIONS": "Operations",
+    "GENERATION": "Generation",
+    "NUMBER": "Number",
+    "ANIMATION": "Animation",
+    "SAMPLE_IMAGE": "Sample Image",
+    "POSITION": "Position",
+    "MATH_ADD": "Add",
+    "MATH_SUBTRACT": "Subtract",
+    "MULTIPLY": "Multiply",
+    "DIVIDE": "Divide",
+    "SIN": "Sin",
+    "COS": "Cos",
+    "TAN": "Tan",
+    "GREATER_THAN": "Greater than",
+    "LESS_THAN": "Less than",
+    "LESS_THAN_OR_EQUAL": "Less than or equal",
+    "COMPARE": "Compare",
+    "MATH_POWER": "Power",
+    "LOGARITHM": "Logarithm",
+    "NATURAL_LOGARITHM": "Natural logarithm",
+    "ROOT": "Root",
+    "INVERSE_ROOT": "Inverse root",
+    "FRACTION": "Fraction",
+    "NEGATE": "Negate",
+    "FLOOR": "Floor",
+    "CEIL": "Ceil",
+    "ROUND": "Round",
+    "MODULO": "Modulo",
+    "STEP": "Step",
+    "SMOOTH_STEP": "Smoothstep",
+    "PIXEL_ART_TOOLSET": "Pixel Art",
+    "VECTOR_TOOLSET": "Vector",
+    "VECTOR_LAYER": "Vector Layer",
+    "STROKE_COLOR_LABEL": "Stroke",
+    "SYNC_WITH_PRIMARY_COLOR_LABEL": "Sync with primary color",
+    "RASTERIZE": "Rasterize",
+    "RASTERIZE_ACTIVE_LAYER": "Rasterize active layer",
+    "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "Convert/Rasterize the active layer into a image (raster) layer.",
+    "NEW_ELLIPSE_LAYER_NAME": "Ellipse",
+    "NEW_RECTANGLE_LAYER_NAME": "Rectangle",
+    "NEW_LINE_LAYER_NAME": "Line",
+    "RENDER_OUTPUT": "Render Output",
+    "PAINT_TOOLSET": "Painting",
+    "HARDNESS_SETTING": "Hardness",
+    "SPACING_SETTING": "Spacing",
+    "ANTI_ALIASING_SETTING": "Anti-aliasing",
+    "TOLERANCE_LABEL": "Tolerance",
+    "TOGGLE_SNAPPING": "Toggle snapping",
+    "HIGH_RES_PREVIEW": "High Resolution Preview",
+    "LOW_RES_PREVIEW": "Document Resolution Preview",
+    "TOGGLE_HIGH_RES_PREVIEW": "Toggle high resolution preview",
+    "FACTOR": "Factor",
+    "PATH_TOOL": "Path",
+    "PATH_TOOL_TOOLTIP": "Create vector paths and curves ({0}).",
+    "PATH_TOOL_ACTION_DISPLAY": "Click to add a point.",
+    "PATH_TOOL_ACTION_DISPLAY_CTRL": "Click on existing point and drag to make it a curve. Tap on a control point to select it.",
+    "PATH_TOOL_ACTION_DISPLAY_SHIFT": "Click to create a new layer.",
+    "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "Tap on a control point to add it to the selection.",
+    "PATH_TOOL_ACTION_DISPLAY_ALT": "Click on a control point and move to adjust only one side of the curve.",
+    "DEFAULT_PATH_LAYER_NAME": "Path",
+    "DELETE_NODES": "Delete nodes",
+    "DELETE_NODES_DESCRIPTIVE": "Delete selected nodes",
+    "DELETE_CELS": "Delete cels",
+    "DELETE_CELS_DESCRIPTIVE": "Delete selected cels",
+    "COPY_COLOR_TO_CLIPBOARD": "Copy color to clipboard",
+    "VIEWPORT_ROTATION": "Viewport rotation",
+    "NEXT_TOOL_SET": "Next tool set",
+    "PREVIOUS_TOOL_SET": "Previous tool set",
+    "FILL_MODE": "Fill mode",
+    "USE_LINEAR_SRGB_PROCESSING": "Use linear sRGB for processing colors",
+    "USE_LINEAR_SRGB_PROCESSING_DESC": "Convert document using sRGB blending mode to linear sRGB for processing colors. This will affect the colors of the document, but will make blending more accurate.",
+    "FILL_TYPE_WINDING": "Winding",
+    "FILL_TYPE_EVEN_ODD": "Even Odd",
+    "FILL_TYPE_INVERSE_WINDING": "Inverse Winding",
+    "FILL_TYPE_INVERSE_EVEN_ODD": "Inverse Even Odd",
+    "STROKE_CAP": "Stroke Cap",
+    "STROKE_JOIN": "Stroke Join",
+    "COPY_VISIBLE": "Copy visible",
+    "COPY_VISIBLE_DESCRIPTIVE": "Copy visible pixels",
+    "COLOR_SAMPLE_MODE": "Sample mode",
+    "CREATE_CEL": "Create cel",
+    "CREATE_CEL_DESCRIPTIVE": "Create a new cel",
+    "DUPLICATE_CEL": "Duplicate cel",
+    "DUPLICATE_CEL_DESCRIPTIVE": "Duplicate cel in the current frame",
+    "RENDER_PREVIEW": "Render preview",
+    "OUTPUT_NAME": "Output name",
+    "CUSTOM_OUTPUT_NODE": "Custom Output",
+    "TOGGLE_HUD": "Toggle HUD",
+    "OPEN_TIMELINE": "Open timeline",
+    "OPEN_NODE_GRAPH": "Open node graph",
+    "TOGGLE_PLAY": "Play/Pause animation",
+    "COPY_NODES": "Copy nodes",
+    "COPY_NODES_DESCRIPTIVE": "Copy selected nodes",
+    "PASTE_NODES": "Paste nodes",
+    "PASTE_NODES_DESCRIPTIVE": "Paste copied nodes",
+    "COPY_CELS": "Copy cels",
+    "COPY_CELS_DESCRIPTIVE": "Copy selected cels",
+    "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "Toggle onion skinning",
+    "VALUE": "Value",
+    "TARGET": "Target",
+    "EPSILON": "Epsilon",
+    "PRESERVE_ALPHA": "Preserve alpha",
+    "BLUR_FILTER_NODE": "Gaussian Blur Filter",
+    "LENGTH": "Length",
+    "GREATER_THAN_OR_EQUAL": "Greater than or equal",
+    "COLOR_NODE": "Color",
+    "CONVERT_TO_CURVE": "Convert to curve",
+    "CONVERT_TO_CURVE_DESCRIPTIVE": "Convert selected vector layer to a curve/path",
+    "FONT_FILES": "Font Files",
+    "UNIT_PT": "pt",
+    "FONT_LABEL": "Family",
+    "FONT_SIZE_LABEL": "Size",
+    "SPACING_LABEL": "Spacing",
+    "TEXT_TOOL": "Text",
+    "MISSING_FONT": "Missing font",
+    "TEXT_LAYER_NAME": "Text",
+    "TEXT_TOOL_TOOLTIP": "Create text ({0}).",
+    "BOLD_TOOLTIP": "Bold",
+    "ITALIC_TOOLTIP": "Italic",
+    "CUSTOM_FONT": "Custom font",
+    "DUMP_GPU_DIAGNOSTICS": "Dump GPU diagnostics",
+    "USE_SRGB_PROCESSING": "Use sRGB for processing colors",
+    "USE_SRGB_PROCESSING_DESC": "Convert document using linear sRGB to sRGB for processing colors. This will affect the colors of the document.",
+    "TEXT_NODE": "Text",
+    "TEXT_LABEL": "Text",
+    "TEXT_ON_PATH_NODE": "Text on Path",
+    "HIGH_DPI_RENDERING": "High DPI Rendering",
+    "THICKNESS": "Thickness",
+    "TYPE": "Type",
+    "EFFECTS": "Effects",
+    "OUTLINE_NODE": "Outline",
+    "SHADER_CODE": "Shader Code",
+    "SHADER_NODE": "Shader",
+    "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "Failed to open file",
+    "FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE": "Failed to edit this string in external editor. Reason: {0}",
+    "STRING_EDIT_IN_DEFAULT_APP": "Edit in default app",
+    "STRING_OPEN_IN_FOLDER": "Open in folder",
+    "DISCO_BALL_EXAMPLE": "Disco Ball",
+    "COLOR_SPACE": "Color Space",
+    "PHOTO_EXAMPLES": "Photo",
+    "MASK_EXAMPLE": "Mask",
+    "SHADOW_NODE": "Shadow Filter",
+    "INPUT_MATRIX": "Input Matrix",
+    "OUTPUT_MATRIX": "Output Matrix",
+    "CENTER": "Center",
+    "CANVAS_POSITION": "Canvas Position",
+    "CENTER_POSITION": "Center Position",
+    "TILE_MODE_X": "Tile Mode X",
+    "TILE_MODE_Y": "Tile Mode Y",
+    "TILE_NODE": "Tile",
+    "SKEW": "Skew",
+    "OFFSET_NODE": "Offset",
+    "SKEW_NODE": "Skew",
+    "SCALE_NODE": "Scale",
+    "ROTATE_NODE": "Rotate",
+    "TRANSFORM_NODE": "Transform",
+    "UNIT": "Unit",
+    "ANGLE": "Angle",
+    "DOCUMENT_INFO_NODE": "Document Info",
+    "MASK_NODE": "Mask",
+    "SEPIA_FILTER_NODE": "Sepia Filter",
+    "INTENSITY": "Intensity",
+    "INVERT_FILTER_NODE": "Invert Filter",
+    "COLOR_ADJUSTMENTS_FILTER": "Color Adjustments Filter",
+    "ADJUST_BRIGHTNESS": "Adjust Brightness",
+    "ADJUST_CONTRAST": "Adjust Contrast",
+    "ADJUST_SATURATION": "Adjust Saturation",
+    "ADJUST_TEMPERATURE": "Adjust Temperature",
+    "ADJUST_TINT": "Adjust Tint",
+    "ADJUST_HUE": "Adjust Hue",
+    "HUE_VALUE": "Hue",
+    "SATURATION_VALUE": "Saturation",
+    "BRIGHTNESS_VALUE": "Brightness",
+    "CONTRAST_VALUE": "Contrast",
+    "TEMPERATURE_VALUE": "Temperature",
+    "TINT_VALUE": "Tint",
+    "UNEXPECTED_SHUTDOWN": "Unexpected shutdown",
+    "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor was unexpectedly shut down. We've loaded latest autosave of your files.",
+    "OK": "OK",
+    "OPEN_AUTOSAVES": "Browse Autosaves",
+    "AUTOSAVE_SETTINGS_HEADER": "Autosave",
+    "AUTOSAVE_SETTINGS_SAVE_STATE": "Reopen last files on startup",
+    "AUTOSAVE_SETTINGS_PERIOD": "Autosave period",
+    "AUTOSAVE_ENABLED": "Autosave enabled",
+    "MINUTE_UNIVERSAL": "min",
+    "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Autosave to selected file",
+    "LOAD_LAZY_FILE_MESSAGE": "To improve startup time, PixiEditor didn't load this file. Click the button below to load it.",
+    "EASING_NODE": "Easing",
+    "EASING_TYPE": "Easing Type",
+    "OPEN_DIRECTORY_ON_EXPORT": "Open directory on export",
+    "ERROR_LOOP_DETECTED_MESSAGE": "Moving this layer will create a loop. Fix it in the Node Graph.",
+    "LINEAR_EASING_TYPE": "Linear",
+    "IN_SINE_EASING_TYPE": "In Sine",
+    "OUT_SINE_EASING_TYPE": "Out Sine",
+    "IN_OUT_SINE_EASING_TYPE": "In Out Sine",
+    "IN_QUAD_EASING_TYPE": "In Quad",
+    "OUT_QUAD_EASING_TYPE": "Out Quad",
+    "IN_OUT_QUAD_EASING_TYPE": "In Out Quad",
+    "IN_CUBIC_EASING_TYPE": "In Cubic",
+    "OUT_CUBIC_EASING_TYPE": "Out Cubic",
+    "IN_OUT_CUBIC_EASING_TYPE": "In Out Cubic",
+    "IN_QUART_EASING_TYPE": "In Quart",
+    "OUT_QUART_EASING_TYPE": "Out Quart",
+    "IN_OUT_QUART_EASING_TYPE": "In Out Quart",
+    "IN_QUINT_EASING_TYPE": "In Quint",
+    "OUT_QUINT_EASING_TYPE": "Out Quint",
+    "IN_OUT_QUINT_EASING_TYPE": "In Out Quint",
+    "IN_EXPO_EASING_TYPE": "In Expo",
+    "OUT_EXPO_EASING_TYPE": "Out Expo",
+    "IN_OUT_EXPO_EASING_TYPE": "In Out Expo",
+    "IN_CIRC_EASING_TYPE": "In Circ",
+    "OUT_CIRC_EASING_TYPE": "Out Circ",
+    "IN_OUT_CIRC_EASING_TYPE": "In Out Circ",
+    "IN_BACK_EASING_TYPE": "In Back",
+    "OUT_BACK_EASING_TYPE": "Out Back",
+    "IN_OUT_BACK_EASING_TYPE": "In Out Back",
+    "IN_ELASTIC_EASING_TYPE": "In Elastic",
+    "OUT_ELASTIC_EASING_TYPE": "Out Elastic",
+    "IN_OUT_ELASTIC_EASING_TYPE": "In Out Elastic",
+    "IN_BOUNCE_EASING_TYPE": "In Bounce",
+    "OUT_BOUNCE_EASING_TYPE": "Out Bounce",
+    "IN_OUT_BOUNCE_EASING_TYPE": "In Out Bounce",
+    "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
+    "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
+    "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
+    "COLOR_MANAGED_COLOR_SAMPLE_MODE": "Color Managed",
+    "RAW_COLOR_SAMPLE_MODE": "Raw",
+    "FRACTAL_PERLIN_NOISE_TYPE": "Perlin",
+    "TURBULENCE_PERLIN_NOISE_TYPE": "Turbulence",
+    "VORONOI_NOISE_TYPE": "Voronoi",
+    "VORONOI_FEATURE": "Feature",
+    "F1_VORONOI_FEATURE": "F1",
+    "F2_VORONOI_FEATURE": "F2",
+    "F2_MINUS_F1_VORONOI_FEATURE": "F2-F1",
+    "RANDOMNESS": "Randomness",
+    "ANGLE_OFFSET": "Angle Offset",
+    "INHERIT_COLOR_SPACE_TYPE": "Inherit",
+    "SRGB_COLOR_SPACE_TYPE": "sRGB",
+    "LINEAR_SRGB_COLOR_SPACE_TYPE": "Linear sRGB",
+    "SIMPLE_OUTLINE_TYPE": "Simple",
+    "GAUSSIAN_OUTLINE_TYPE": "Gaussian",
+    "PIXEL_PERFECT_OUTLINE_TYPE": "Pixel Perfect",
+    "DEGREES_ROTATION_TYPE": "Degrees",
+    "RADIANS_ROTATION_TYPE": "Radians",
+    "WEIGHTED_GRAYSCALE_MODE": "Weighted",
+    "AVERAGE_GRAYSCALE_MODE": "Average",
+    "CUSTOM_GRAYSCALE_MODE": "Custom",
+    "CLAMP_TILE_MODE": "Clamp",
+    "REPEAT_TILE_MODE": "Repeat",
+    "MIRROR_TILE_MODE": "Mirror",
+    "DECAL_TILE_MODE": "Decal",
+    "ERR_UNKNOWN_FILE_FORMAT": "Unknown file format",
+    "ERR_EXPORT_SIZE_INVALID": "Invalid export size. Values must be greater than 0.",
+    "ERR_UNKNOWN_IMG_FORMAT": "Unknown image format '{0}'.",
+    "ERR_FAILED_GENERATE_SPRITE_SHEET": "Failed generating sprite sheet",
+    "ERR_NO_RENDERER": "Animation renderer not found.",
+    "ERR_RENDERING_FAILED": "Rendering failed",
+    "ENABLE_ANALYTICS": "Send anonymous analytics",
+    "ANALYTICS_INFO": "We collect anonymous usage data to improve PixiEditor. No personal data is collected.",
+    "LANGUAGE_INFO": "All translations are community-driven. Join our Discord server for more information.",
+    "UP_TO_DATE_UNKNOWN": "Couldn't check for updates",
+    "UP_TO_DATE": "PixiEditor is up to date",
+    "UPDATE_AVAILABLE": "Update {0} is available",
+    "UPDATE_FAILED_DOWNLOAD": "Failed to download the update",
+    "UPDATE_READY_TO_INSTALL": "Update is ready. Switch to {0}?",
+    "SWITCH_TO_NEW_VERSION": "Switch",
+    "DOWNLOAD_UPDATE": "Download",
+    "DOWNLOADING_UPDATE": "Downloading update...",
+    "CHECKING_FOR_UPDATES": "Checking for updates...",
+    "PAINT_SHAPE_SETTING": "Brush shape",
+    "BOOL_OPERATION_NODE": "Boolean Operation",
+    "FIRST_SHAPE": "First shape",
+    "SECOND_SHAPE": "Second shape",
+    "OPERATION": "Operation",
+    "UNION_VECTOR_PATH_OP": "Union",
+    "DIFFERENCE_VECTOR_PATH_OP": "Difference",
+    "INTERSECT_VECTOR_PATH_OP": "Intersect",
+    "XOR_VECTOR_PATH_OP": "XOR",
+    "REVERSE_DIFFERENCE_VECTOR_PATH_OP": "Reverse Difference",
+    "NO_DOCUMENT_OPEN": "Nothing's here",
+    "EMPTY_DOCUMENT_ACTION_BTN": "Start creating",
+    "ONBOARDING_TITLE": "Welcome to",
+    "ONBOARDING_DESCRIPTION": "Let's set up your workspace!",
+    "ONBOARDING_SKIP_BTN": "Skip",
+    "ONBOARDING_ACTION_BTN": "Let's begin",
+    "ONB_SELECT_PRIMARY_TOOLSET": "Select Your Primary Toolset",
+    "ONB_NEXT_BTN": "Next",
+    "ONB_FINISH_BTN": "Finish",
+    "ONB_BACK_BTN": "Previous",
+    "ONB_ANALYTICS": "Anonymous Analytics",
+    "ONB_ALL_SET": "You are all set!",
+    "ONB_ALL_SET_BTN": "Start creating",
+    "ANALYTICS_INFO_DETAILED": "PixiEditor collects anonymous usage data to improve the app. The data does not contain any personal information. Among other things, PixiEditor tracks the following:\n- Which and how the tools are used\n- How long you've used the app for\n- Which commands are used\n- Performance data\n\n You can opt out of analytics at any time in the settings.",
+    "PRIVACY_POLICY": "Privacy Policy",
+    "ONB_SHORTCUTS": "Select Your Shortcuts",
+    "GRAPH_STATE_UNABLE_TO_CREATE_MEMBER": "Current Node Graph setup disallows creation of a new layer next to the selected one.",
+    "PRIMARY_TOOLSET": "Primary Toolset",
+    "OPEN_ONBOARDING_WINDOW": "Open onboarding window",
+    "USER_NOT_FOUND": "Please enter the email you used to purchase the Founder's Edition.",
+    "SESSION_NOT_VALID": "Session is not valid, please log in again",
+    "SESSION_NOT_FOUND": "Session not found, try logging in again",
+    "INTERNAL_SERVER_ERROR": "There was an internal server error. Please try again later.",
+    "TOO_MANY_REQUESTS": "Too many requests. Try again in {0} seconds.",
+    "SESSION_EXPIRED": "Session expired. Please log in again.",
+    "CONNECTION_ERROR": "Connection error. Please check your internet connection.",
+    "FAIL_LOAD_USER_DATA": "Failed to load saved user data",
+    "LOGOUT": "Logout",
+    "LOGGED_IN_AS": "Hello",
+    "EMAIL_SENT": "Email sent! Check your inbox.",
+    "RESEND_ACTIVATION": "Resend",
+    "INVALID_TOKEN": "Session is invalid or expired. Please log in again.",
+    "ENTER_EMAIL": "Enter your email",
+    "LOGIN_LINK": "Send Login Link",
+    "LOGIN_LINK_INFO": "We'll email you a secure link to log in. No password needed.",
+    "ACCOUNT_WINDOW_TITLE": "Account",
+    "CONNECTION_TIMEOUT": "Connection timed out. Please try again.",
+    "OPEN_ACCOUNT_WINDOW": "Manage Account",
+    "AUTO_SCALE_BACKGROUND": "Auto scale background",
+    "UPDATES": "Updates",
+    "SCENE": "Scene",
+    "CUSTOM_BACKGROUND_SCALE": "Custom background scale",
+    "PRIMARY_BG_COLOR": "Primary background color",
+    "SECONDARY_BG_COLOR": "Secondary background color",
+    "RESET": "Reset",
+    "INSTALL": "Install",
+    "OWNED_PRODUCTS": "Owned Content",
+    "INSTALLING": "Installing",
+    "INSTALLED": "Installed",
+    "ACCOUNT_PROVIDER_INFO": "Account handled by",
+    "UPDATE": "Update",
+    "AUTOSAVE_OPEN_FOLDER": "Open autosave folder",
+    "AUTOSAVE_OPEN_FOLDER_DESCRIPTIVE": "Open the folder where autosaves are stored",
+    "AUTOSAVE_TOGGLE_DESCRIPTIVE": "Enable/disable autosave",
+    "FOUNDERS_BUNDLE": "Founder's Bundle",
+    "FOUNDERS_BUNDLE_SUBTEXT": "Support PixiEditor and boost your productivity!",
+    "BECOME_A_FOUNDER": "Become a Founder",
+    "LOGIN": "Login",
+    "NOT_FOUNDER_YET": "Not a Founder yet?",
+    "ERROR_GRAPH": "Graph setup produced an error. Fix it in the node graph",
+    "COLOR_MATRIX_FILTER_NODE": "Color Matrix Filter",
+    "WORKSPACE": "Workspace",
+    "IS_DEFAULT_EXPORT": "Is Default Export",
+    "EXPORT_OUTPUT": "Export Output",
+    "RENDER_OUTPUT_SIZE": "Render Output Size",
+    "RENDER_OUTPUT_CENTER": "Render Output Center",
+    "COLOR_PICKER": "Color Picker",
+    "UNAUTHORIZED_ACCESS": "Unauthorized access",
+    "SEPARATE_SHAPES": "Separate Shapes",
+    "SEPARATE_SHAPES_DESCRIPTIVE": "Separate shapes from current vector into individual layers",
+    "TEXT": "Text",
+    "EXTRACT_SELECTED_TEXT": "Extract selected text",
+    "EXTRACT_SELECTED_TEXT_DESCRIPTIVE": "Extract selected text into new layer.",
+    "EXTRACT_SELECTED_CHARACTERS": "Extract selected characters",
+    "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE": "Extract individual characters from selection into new layers.",
+    "STEP_START": "Step back to closest cel",
+    "STEP_END": "Step forward to closest cel",
+    "STEP_FORWARD": "Step forward one frame",
+    "STEP_BACK": "Step back one frame",
+    "ANIMATION_QUALITY_PRESET": "Quality Preset",
+    "VERY_LOW_QUALITY_PRESET": "Very Low",
+    "LOW_QUALITY_PRESET": "Low",
+    "MEDIUM_QUALITY_PRESET": "Medium",
+    "HIGH_QUALITY_PRESET": "High",
+    "VERY_HIGH_QUALITY_PRESET": "Very High",
+    "EXPORT_FRAMES": "Export Frames",
+    "NORMALIZE_OFFSET": "Normalize Offset",
+    "TANGENT": "Tangent",
+    "EVALUATE_PATH_NODE": "Evaluate Path",
+    "OLD_MIN": "Old Min",
+    "OLD_MAX": "Old Max",
+    "NEW_MIN": "New Min",
+    "NEW_MAX": "New Max",
+    "REMAP_NODE": "Remap",
+    "TEXT_TOOL_ACTION_DISPLAY": "Click on the canvas to add a new text (drag while clicking to set the size). Click on existing text to edit it.",
+    "PASTE_CELS": "Paste cels",
+    "PASTE_CELS_DESCRIPTIVE": "Paste cels from clipboard into the current frame",
+    "SCALE_X": "Scale X",
+    "SCALE_Y": "Scale Y",
+    "TRANSLATE_X": "Translate X",
+    "TRANSLATE_Y": "Translate Y",
+    "SKEW_X": "Skew X",
+    "SKEW_Y": "Skew Y",
+    "PERSPECTIVE_0": "Perspective 0",
+    "PERSPECTIVE_1": "Perspective 1",
+    "PERSPECTIVE_2": "Perspective 2",
+    "COMPOSE_MATRIX": "Compose Matrix",
+    "DECOMPOSE_MATRIX": "Decompose Matrix",
+    "NORMALIZE_COORDINATES": "Normalize Coordinates",
+    "TRANSFORMED_POSITION": "Transformed Position",
+    "ACCOUNT_PROVIDER_NOT_AVAILABLE": "This build of PixiEditor does not support accounts. Use the official build from pixieditor.net to manage your account.",
+    "STEAM_OFFLINE": "Cannot validate the account. Steam is offline. Make sure Steam client is running and you are logged in.",
+    "ERROR_GPU_RESOURCES_CREATION": "Failed to create resources: Try updating your GPU drivers or try setting different rendering api in settings. \nError: '{0}'",
+    "ERROR_SAVING_PREFERENCES_DESC": "Failed to save preferences with error: '{0}'. Please check if you have write permissions to the PixiEditor data folder.",
+    "ERROR_SAVING_PREFERENCES": "Failed to save preferences",
+    "PREFERRED_RENDERER": "Preferred Render Api",
+    "PERFORMANCE": "Performance",
+    "DISABLE_PREVIEWS": "Disable Previews",
+    "MAX_BILINEAR_CANVAS_SIZE": "Max Bilinear Canvas Size",
+    "MAX_BILINEAR_CANVAS_SIZE_DESC": "Maximum canvas size for bilinear filtering. Set to 0 to disable bilinear filtering. Bilinear filtering improves the quality of the canvas, but can cause performance issues on large canvases.",
+    "INVERT_MASK": "Invert mask",
+    "TOGGLE_TINTING_SELECTION": "Toggle selection tinting",
+    "TOGGLE_TINTING_SELECTION_DESCRIPTIVE": "Toggle selection tinting",
+    "TINT_SELECTION": "Selection tinting",
+    "PAINT_BRUSH_SHAPE_CIRCLE": "Circle",
+    "PAINT_BRUSH_SHAPE_SQUARE": "Square",
+    "BRIGHTNESS_MODE_DEFAULT": "Default",
+    "BRIGHTNESS_MODE_REPEAT": "Repeat",
+    "ROUND_STROKE_CAP": "Round",
+    "BUTT_STROKE_CAP": "Butt",
+    "SQUARE_STROKE_CAP": "Square",
+    "ROUND_STROKE_JOIN": "Round",
+    "MITER_STROKE_JOIN": "Miter",
+    "BEVEL_STROKE_JOIN": "Bevel",
+    "TOGGLE_FULLSCREEN": "Toggle Fullscreen",
+    "TOGGLE_FULLSCREEN_DESCRIPTIVE": "Enter or Exit Fullscreen Mode"
 }

+ 106 - 100
src/PixiEditor/Data/Localization/Languages/es.json

@@ -1,33 +1,33 @@
 {
-  "RECENT_FILES": "Archivo Reciente",
-  "OPEN_FILE": "Abrir Archivo",
+  "RECENT_FILES": "Archivos recientes",
+  "OPEN_FILE": "Abrir archivo",
   "NEW_FILE": "Nuevo",
-  "RECENT_EMPTY_TEXT": "Demasiado espacio vacio",
+  "RECENT_EMPTY_TEXT": "Bastante vacío aquí",
   "LANGUAGE": "Idioma",
   "GENERAL": "General",
   "DISCORD": "Discord",
-  "KEY_BINDINGS": "Fijaciones de teclas",
-  "MISC": "Otros",
-  "SHOW_STARTUP_WINDOW": "Abrir Al Iniciar Windows",
+  "KEY_BINDINGS": "Atajos del teclado",
+  "MISC": "Misceláneo",
+  "SHOW_STARTUP_WINDOW": "Mostrar ventana de bienvenida",
   "RECENT_FILE_LENGTH": "Longitud de la lista de archivos reciente",
-  "RECENT_FILE_LENGTH_TOOLTIP": "Cuántos documentos se muestran en Archivo > Recientes. Por defecto: 8",
-  "DEFAULT_NEW_SIZE": "Nuevo archivo por defecto",
-  "WIDTH": "Anchura",
-  "HEIGHT": "Altura",
+  "RECENT_FILE_LENGTH_TOOLTIP": "Cuántos documentos se muestran en Archivo > Recientes. Predeterminado: 8",
+  "DEFAULT_NEW_SIZE": "Tamaño de imagen predeterminado",
+  "WIDTH": "Ancho",
+  "HEIGHT": "Alto",
   "TOOLS": "Herramientas",
   "ENABLE_SHARED_TOOLBAR": "Activar la barra de herramientas compartida",
-  "AUTOMATIC_UPDATES": "Actualizaciones Automáticas",
+  "AUTOMATIC_UPDATES": "Actualizaciones automáticas",
   "CHECK_FOR_UPDATES": "Buscar actualizaciones al iniciar",
-  "UPDATE_STREAM": "Actualizar stream",
-  "UPDATE_CHANNEL_HELP_TOOLTIP": "Los canales de actualización sólo se pueden cambiar en la versión independiente (descargada de https://pixieditor.net).\nLas versiones de Steam y Microsoft Store gestionan las actualizaciones por separado.",
-  "DEBUG": "Debug",
-  "ENABLE_DEBUG_MODE": "Activar modo Debug",
-  "OPEN_CRASH_REPORTS_DIR": "Abrir lista de reportes",
+  "UPDATE_STREAM": "Frecuencia de actualizaciones",
+  "UPDATE_CHANNEL_HELP_TOOLTIP": "Las actualizaciones sólo se pueden gestionar manualmente en la versión descargada por el sitio del programa (https://pixieditor.net).\nSteam y la tienda de Microsoft gestionan las actualizaciones por separado.",
+  "DEBUG": "Depuración",
+  "ENABLE_DEBUG_MODE": "Activar modo de depuración",
+  "OPEN_CRASH_REPORTS_DIR": "Abrir carpeta de reportes de colapsos",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "ENABLED": "Activado",
-  "SHOW_IMAGE_NAME": "Mostrar nombre del archivo",
-  "SHOW_IMAGE_SIZE": "Mostrar tamaño del archivo",
-  "SHOW_LAYER_COUNT": "Mostrar recuento de capas",
+  "SHOW_IMAGE_NAME": "Mostrar nombre de la imagen",
+  "SHOW_IMAGE_SIZE": "Mostrar tamaño de la imagen",
+  "SHOW_LAYER_COUNT": "Mostrar cantidad de capas",
   "FILE": "Archivo",
   "RECENT": "Reciente",
   "OPEN": "Abrir",
@@ -38,119 +38,119 @@
   "EXIT": "Salir",
   "PERCENTAGE": "Porcentaje",
   "ABSOLUTE": "Absoluto",
-  "PRESERVE_ASPECT_RATIO": "Conservar la relación de aspecto",
-  "ANCHOR_POINT": "Punto de anclaje",
-  "RESIZE_IMAGE": "Cambiar el tamaño de la imagen",
-  "RESIZE": "Cambie el tamaño de",
-  "DOCUMENTATION": "Documentacion",
-  "WEBSITE": "Pagina Web",
-  "OPEN_WEBSITE": "Abrir Pagina Web",
+  "PRESERVE_ASPECT_RATIO": "Preservar la relación de aspecto",
+  "ANCHOR_POINT": "Anclaje",
+  "RESIZE_IMAGE": "Reescalar la imagen",
+  "RESIZE": "Confirmar",
+  "DOCUMENTATION": "Documentación",
+  "WEBSITE": "Pagina web",
+  "OPEN_WEBSITE": "Abrir Pagina web",
   "REPOSITORY": "Repositorio",
   "OPEN_REPOSITORY": "Abrir Repositorio",
   "LICENSE": "Licencia",
-  "OPEN_LICENSE": "Abrir Licencia",
+  "OPEN_LICENSE": "Abrir licencia",
   "THIRD_PARTY_LICENSES": "Licencias de terceros",
   "OPEN_THIRD_PARTY_LICENSES": "Abrir licencias de terceros",
   "APPLY_TRANSFORM": "Aplicar transformacion",
-  "INCREASE_TOOL_SIZE": "Aumentar el tamaño de la herramienta",
-  "DECREASE_TOOL_SIZE": "Disminuir el tamaño de la herramienta",
-  "DOWNLOADING_UPDATE": "Instalando actualizacion...",
-  "UPDATE_READY": "Actualización listo para instalar. ¿Desea instalarlo ahora?",
-  "NEW_UPDATE": "Nueva actualizacion",
-  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "No se ha podido actualizar sin privilegios de administrador. Por favor, ejecute PixiEditor como administrador.",
+  "INCREASE_TOOL_SIZE": "Agrandar la herramienta",
+  "DECREASE_TOOL_SIZE": "Desagrandar la herramienta",
+  "DOWNLOADING_UPDATE": "Descargando actualización...",
+  "UPDATE_READY": "La actualización está lista para instalar. ¿Quieres instalarla ahora?",
+  "NEW_UPDATE": "Nueva actualización",
+  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "No se puede actualizar sin privilegios de administrador. Ejecuta PixiEditor como administrador.",
   "INSUFFICIENT_PERMISSIONS": "Permisos insuficientes",
-  "UPDATE_CHECK_FAILED": "Error en la comprobación de la actualización",
-  "COULD_NOT_CHECK_FOR_UPDATES": "No se ha podido comprobar si hay una actualización disponible.",
-  "VERSION": "Version {0}",
+  "UPDATE_CHECK_FAILED": "Error en detección de actualizaciones",
+  "COULD_NOT_CHECK_FOR_UPDATES": "No se pudo detectar si hay actualizaciones.",
+  "VERSION": "Versión {0}",
   "OPEN_TEMP_DIR": "Abrir directorio temporal",
-  "OPEN_LOCAL_APPDATA_DIR": "Abra el directorio local AppData",
-  "OPEN_ROAMING_APPDATA_DIR": "Abrir el directorio Roaming AppData",
+  "OPEN_LOCAL_APPDATA_DIR": "Abra la carpeta Local AppData",
+  "OPEN_ROAMING_APPDATA_DIR": "Abrir la carpeta Roaming AppData",
   "OPEN_INSTALLATION_DIR": "Abrir el directorio de instalación",
-  "DUMP_ALL_COMMANDS": "Volcar todos los comandos",
-  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Volcar todos los comandos a un archivo de texto",
-  "CRASH": "Error",
-  "CRASH_APP": "Error en la aplicacion",
-  "DELETE_USR_PREFS": "Eliminar las preferencias del usuario (Roaming AppData)",
+  "DUMP_ALL_COMMANDS": "Exportar todos los comandos",
+  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Exportar todos los comandos a un archivo de texto",
+  "CRASH": "Forzar cierre",
+  "CRASH_APP": "Forzar el cierre de la aplicación",
+  "DELETE_USR_PREFS": "Eliminar las preferencias de usuario (Roaming AppData)",
   "DELETE_SHORTCUT_FILE": "Eliminar el archivo de acceso directo (Roaming AppData)",
   "DELETE_EDITOR_DATA": "Eliminar datos del editor (Local AppData)",
-  "GENERATE_KEY_BINDINGS_TEMPLATE": "Generar plantilla de enlaces de teclas",
-  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generar plantilla json de enlaces de claves",
-  "VALIDATE_SHORTCUT_MAP": "Validar dirección de accesos directos",
-  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Validados dirección de accesos directos",
-  "VALIDATION_KEYS_NOTICE_DIALOG": "Llaves vacías: {0}\nComandos desconocidos: {1}",
+  "GENERATE_KEY_BINDINGS_TEMPLATE": "Generar plantilla de atajos del teclado",
+  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generar plantilla de atajos de teclado en formato JSON",
+  "VALIDATE_SHORTCUT_MAP": "Validar mapa de atajos del teclado",
+  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Valida los atajos del teclado",
+  "VALIDATION_KEYS_NOTICE_DIALOG": "Claves vacías: {0}\nComandos desconocidos: {1}",
   "RESULT": "Resultado",
-  "CLEAR_RECENT_DOCUMENTS": "Limpiar archivos recientes",
-  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Limpiar documentos recientemente abiertos",
-  "OPEN_CMD_DEBUG_WINDOW": "Abrir caja de comandos debug de windows",
+  "CLEAR_RECENT_DOCUMENTS": "Limpiar documentos recientes",
+  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Limpiar documentos abiertos recientemente",
+  "OPEN_CMD_DEBUG_WINDOW": "Abrir ventana de comandos de depuración",
   "PATH_DOES_NOT_EXIST": "{0} no existe.",
   "LOCATION_DOES_NOT_EXIST": "La ubicación no existe.",
   "FILE_NOT_FOUND": "Archivo no encontrado.",
   "ARE_YOU_SURE": "¿Estas seguro?",
   "ARE_YOU_SURE_PATH_FULL_PATH": "¿Está seguro de que desea borrar {0}?\nEstos datos se perderán para todas las instalaciones.\n(Ruta completa: {1})",
-  "FAILED_TO_OPEN_FILE": "Error al abrir el archivo",
-  "OLD_FILE_FORMAT": "Formato de archivo viejo",
+  "FAILED_TO_OPEN_FILE": "No se pudo abrir el archivo",
+  "OLD_FILE_FORMAT": "Formato de archivo antiguo",
   "OLD_FILE_FORMAT_DESCRIPTION": "Este archivo .pixi utiliza el formato antiguo,\nque ya no es compatible y no puede abrirse.",
   "NOTHING_FOUND": "Nada encontrado",
   "EXPORT": "Exportar",
   "EXPORT_IMAGE": "Exportar imagen",
   "IMPORT": "Importar",
-  "SHORTCUT_TEMPLATES": "Plantillas de acceso directo",
-  "RESET_ALL": "Reiniciar todo",
+  "SHORTCUT_TEMPLATES": "Plantillas de atajos del teclado",
+  "RESET_ALL": "Restaurar todos",
   "LAYER": "Capa",
   "LAYER_DELETE_SELECTED": "Borrar capa/carpeta activa",
   "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Borrar capa o carpeta activa",
   "LAYER_DELETE_ALL_SELECTED": "Eliminar todas las capas/carpetas seleccionadas",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Eliminar todas las capas y/o carpetas seleccionadas",
-  "DELETE_SELECTED_PIXELS": "Borrar píxeles seleccionados",
+  "DELETE_SELECTED_PIXELS": "Eliminar pixeles seleccionados",
   "NEW_FOLDER": "Nueva carpeta",
   "CREATE_NEW_FOLDER": "Crear nueva carpeta",
   "NEW_LAYER": "Nueva capa",
   "CREATE_NEW_LAYER": "Crear nueva capa",
-  "NEW_IMAGE": "Imagen nueva",
-  "CREATE_NEW_IMAGE": "Crear imagen nueva",
+  "NEW_IMAGE": "Nueva imagen",
+  "CREATE_NEW_IMAGE": "Crear nueva imagen",
   "SAVE": "Guardar",
   "SAVE_AS": "Guardar como...",
   "IMAGE": "Imagen",
   "SAVE_IMAGE": "Guardar imagen",
-  "SAVE_IMAGE_AS": "Guardar imagen como nuevo",
+  "SAVE_IMAGE_AS": "Guardar imagen como nuevo archivo",
   "DUPLICATE": "Duplicar",
   "DUPLICATE_SELECTED_LAYER": "Duplicar capa selecionada",
-  "CREATE_MASK": "Crear mascara",
-  "DELETE_MASK": "Borrar mascara",
-  "TOGGLE_MASK": "Máscara de conmutación",
-  "APPLY_MASK": "Aplicar mascara",
+  "CREATE_MASK": "Crear máscara",
+  "DELETE_MASK": "Borrar máscara",
+  "TOGGLE_MASK": "Alternar máscara",
+  "APPLY_MASK": "Aplicar máscara",
   "TOGGLE_VISIBILITY": "Alternar visibilidad",
-  "MOVE_MEMBER_UP": "Desplazar al miembro hacia arriba",
-  "MOVE_MEMBER_UP_DESCRIPTIVE": "Mover hacia arriba la capa o carpeta seleccionada",
-  "MOVE_MEMBER_DOWN": "Desplazar el miembro hacia abajo",
-  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Mover hacia abajo la capa o carpeta seleccionada",
-  "MERGE_ALL_SELECTED_LAYERS": "Fusionar todas las capas seleccionadas",
-  "MERGE_WITH_ABOVE": "Fusionar la capa seleccionada con la anterior",
-  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Fusionar la capa seleccionada con la superior",
-  "MERGE_WITH_BELOW": "Fusionar la capa seleccionada con la de abajo",
-  "MERGE_WITH_BELOW_DESCRIPTIVE": "Fusionar la capa seleccionada con la inferior",
+  "MOVE_MEMBER_UP": "Mover miembro hacia arriba",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "Mover la capa o carpeta seleccionada hacia arriba",
+  "MOVE_MEMBER_DOWN": "Desplazar miembro hacia abajo",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Mover la capa o carpeta seleccionada hacia abajo",
+  "MERGE_ALL_SELECTED_LAYERS": "Combinar todas las capas seleccionadas",
+  "MERGE_WITH_ABOVE": "Combinar la capa seleccionada con la de arriba",
+  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Combinar la capa seleccionada con la que esté por encima",
+  "MERGE_WITH_BELOW": "Combinar la capa seleccionada con la de abajo",
+  "MERGE_WITH_BELOW_DESCRIPTIVE": "Combinar la capa seleccionada con la inferior",
   "ADD_REFERENCE_LAYER": "Añadir capa de referencia",
   "DELETE_REFERENCE_LAYER": "Eliminar capa de referencia",
   "TRANSFORM_REFERENCE_LAYER": "Transformar capa de referencia",
-  "TOGGLE_REFERENCE_LAYER_POS": "Conmutar la posición de la capa de referencia",
-  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Conmutar la capa de referencia entre la superior o la inferior",
+  "TOGGLE_REFERENCE_LAYER_POS": "Alternar la posición de la capa de referencia",
+  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Configurar el si la capa de referencia se encuentra arriba o abajo de todo",
   "RESET_REFERENCE_LAYER_POS": "Restablecer la posición de la capa de referencia",
-  "CLIP_CANVAS": "Adjuntar Canvas",
-  "FLIP_IMG_VERTICALLY": "Voltear imagen verticalmente",
-  "FLIP_IMG_HORIZONTALLY": "Voltear imagen horizontalmente",
-  "FLIP_LAYERS_VERTICALLY": "Voltear verticalmente las capas seleccionadas",
-  "FLIP_LAYERS_HORIZONTALLY": "Voltear horizontalmente las capas seleccionadas",
-  "ROT_IMG_90": "Girar la imagen 90 grados",
-  "ROT_IMG_180": "Girar la imagen 180 grados",
-  "ROT_IMG_-90": "Girar la imagen -90 grados",
-  "ROT_LAYERS_90": "Girar 90 grados las capas seleccionadas",
-  "ROT_LAYERS_180": "Girar 180 grados las capas seleccionadas",
-  "ROT_LAYERS_-90": "Girar -90 grados las capas seleccionadas",
-  "TOGGLE_VERT_SYMMETRY_AXIS": "Conmutar el eje de simetría vertical",
-  "TOGGLE_HOR_SYMMETRY_AXIS": "Conmutar el eje de simetría horizontal",
-  "RESIZE_DOCUMENT": "Cambiar el tamaño del documento",
-  "RESIZE_CANVAS": "Cambiar el tamaño del canvas",
-  "CENTER_CONTENT": "Contenido central",
+  "CLIP_CANVAS": "Recortar lienzo",
+  "FLIP_IMG_VERTICALLY": "Espejar imagen verticalmente",
+  "FLIP_IMG_HORIZONTALLY": "Espejar imagen horizontalmente",
+  "FLIP_LAYERS_VERTICALLY": "Espejar verticalmente las capas seleccionadas",
+  "FLIP_LAYERS_HORIZONTALLY": "Espejar horizontalmente las capas seleccionadas",
+  "ROT_IMG_90": "Rotar la imagen 90 grados",
+  "ROT_IMG_180": "Rotar la imagen 180 grados",
+  "ROT_IMG_-90": "Rotar la imagen -90 grados",
+  "ROT_LAYERS_90": "Rotar las capas seleccionadas 90 grados",
+  "ROT_LAYERS_180": "Rotar las capas seleccionadas 180 grados",
+  "ROT_LAYERS_-90": "Rotar las capas seleccionadas -90 grados",
+  "TOGGLE_VERT_SYMMETRY_AXIS": "Alternar el eje de simetría vertical",
+  "TOGGLE_HOR_SYMMETRY_AXIS": "Alternar el eje de simetría horizontal",
+  "RESIZE_DOCUMENT": "Reescalar documento",
+  "RESIZE_CANVAS": "Reescalar lienzo",
+  "CENTER_CONTENT": "Centrar contenido",
   "CUT": "Cortar",
   "CUT_DESCRIPTIVE": "Cortar el área o las capas seleccionadas",
   "PASTE": "Pegar",
@@ -167,20 +167,20 @@
   "COPY": "Copiar",
   "COPY_DESCRIPTIVE": "Copiar al portapapeles",
   "COPY_COLOR_HEX": "Copiar color primario (HEX)",
-  "COPY_COLOR_HEX_DESCRIPTIVE": "Copiar color primario como código HEX",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "Copiar color primario como código hexadecimal",
   "COPY_COLOR_RGB": "Copiar color primario (RGB)",
   "COPY_COLOR_RGB_DESCRIPTIVE": "Copiar color primario como código RGB",
   "COPY_COLOR_SECONDARY_HEX": "Copiar color secundario (HEX)",
-  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copiar color secundario como código HEX",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copiar color secundario como código hexadecimal",
   "COPY_COLOR_SECONDARY_RGB": "Copiar color secundario (RGB)",
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copiar color secundario como código RGB",
   "PALETTE_COLORS": "Colores de la paleta",
-  "REPLACE_SECONDARY_BY_PRIMARY": "Sustituir el color secundario por el primario",
+  "REPLACE_SECONDARY_BY_PRIMARY": "Reemplazar el color secundario por el primario",
   "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "Sustituye el color secundario por el primario",
   "REPLACE_PRIMARY_BY_SECONDARY": "Sustituir el color primario por el secundario",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Sustituir el color primario por el secundario",
   "OPEN_PALETTE_BROWSER": "Abrir el navegador de paletas",
-  "OVERWRITE_PALETTE_CONSENT": "La paleta '{0}' ya existe, ¿desea sobrescribirla?",
+  "OVERWRITE_PALETTE_CONSENT": "La paleta '{0}' ya existe, ¿deseas sobrescribirla?",
   "PALETTE_EXISTS": "La paleta ya existe",
   "REPLACE_PALETTE_CONSENT": "¿Reemplazar la paleta actual por la seleccionada?",
   "REPLACE_PALETTE": "Sustituir la paleta actual",
@@ -194,11 +194,11 @@
   "SELECT_COLOR_8": "Seleccionar color  8",
   "SELECT_COLOR_9": "Seleccionar color 9",
   "SELECT_COLOR_10": "Seleccionar color 10",
-  "SELECT_TOOL": "Seleccionar {0} Herramienta",
-  "SELECT_COLOR_1_DESCRIPTIVE": "Seleccione el primer color de la paleta",
-  "SELECT_COLOR_2_DESCRIPTIVE": "Seleccione el segundo color de la paleta",
-  "SELECT_COLOR_3_DESCRIPTIVE": "Seleccione el tercero color de la paleta",
-  "SELECT_COLOR_4_DESCRIPTIVE": "Seleccione el cuarto color de la paleta",
+  "SELECT_TOOL": "Seleccionar la herramienta de {0}",
+  "SELECT_COLOR_1_DESCRIPTIVE": "Seleccione el primer color en la paleta",
+  "SELECT_COLOR_2_DESCRIPTIVE": "Seleccione el segundo color en la paleta",
+  "SELECT_COLOR_3_DESCRIPTIVE": "Seleccione el tercero color en la paleta",
+  "SELECT_COLOR_4_DESCRIPTIVE": "Seleccione el cuarto color en la paleta",
   "SELECT_COLOR_5_DESCRIPTIVE": "Seleccione el quinto color de la paleta",
   "SELECT_COLOR_6_DESCRIPTIVE": "Seleccione el sexto color de la paleta",
   "SELECT_COLOR_7_DESCRIPTIVE": "Seleccione el séptimo color de la paleta",
@@ -543,5 +543,11 @@
   "SOURCE_UNSET_OR_MISSING": "Fuente ausente/desactivada",
   "SOURCE_NEWER": "Fuente más reciente",
   "SOURCE_UP_TO_DATE": "La fuente está actualizada",
-  "SOURCE_OLDER": "Nube más reciente"
+  "SOURCE_OLDER": "Nube más reciente",
+  "NEWS": "Noticias",
+  "DISABLE_NEWS_PANEL": "No mostrar el panel de noticias en la ventana de bienvenida",
+  "FAILED_FETCH_NEWS": "No se pudieron cargar las noticias",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Pérdida de documentos tras colapso",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "No todos los documentos pudieron ser recuperados tras un cierre inesperado. Metele ganas a guardar tu trabajo.",
+  "EXAMPLE_FILES": "Archivos de ejemplo"
 }

+ 131 - 1
src/PixiEditor/Data/Localization/Languages/ru.json

@@ -678,5 +678,135 @@
   "REMOVE_CLOSE_POINTS": "Удалить близкие точки",
   "RASTERIZE_SHAPE": "Растрировать фигуру",
   "MODE": "Режим",
-  "Factor": "Степень"
+  "Factor": "Степень",
+  "SEND": "Отправить отчет",
+  "INPUT": "Ввод",
+  "CHANNELS_DOCK_TITLE": "Каналы",
+  "COORDINATE": "Координата",
+  "NOISE": "Шум",
+  "PIXEL_COORDINATE": "Координаты пикселя",
+  "MODIFY_IMAGE_PAIR_NODE": "Редактировать изображение",
+  "WARMING_UP": "Разогреваемся",
+  "RENDERING_FRAME": "Генерация кадра {0}/{1}",
+  "RENDERING_VIDEO": "Рендеринг видео",
+  "GENERATING_SPRITE_SHEET": "Создание атласа спрайтов",
+  "PROBABILITY": "Вероятность",
+  "STARS_EXAMPLE": "Звёзды",
+  "ADD_EMPTY_FRAME": "Создать пустой кадр",
+  "DUPLICATE_FRAME": "Дублировать кадр",
+  "DELETE_FRAME": "Удалить кадр",
+  "NO_PARSER_FOUND": "Не найден парсер файлов для расширения '{0}'",
+  "SELECT_FILE_FORMAT": "Выберите формат файла",
+  "ISLAND_EXAMPLE": "Острова",
+  "SHAPE": "Фигура",
+  "STRUCTURE": "Структура",
+  "NUMBERS": "Числа",
+  "OPERATIONS": "Операции",
+  "GENERATION": "Генерация",
+  "NUMBER": "Число",
+  "ANIMATION": "Анимация",
+  "SAMPLE_IMAGE": "Цвет с изображения",
+  "POSITION": "Позиция",
+  "MATH_ADD": "Сложение",
+  "MATH_SUBTRACT": "Вычитание",
+  "MULTIPLY": "Умножение",
+  "DIVIDE": "Деление",
+  "SIN": "Синус",
+  "COS": "Косинус",
+  "TAN": "Тангенс",
+  "PIXEL_ART_TOOLSET": "Пиксель-арт",
+  "VECTOR_TOOLSET": "Векторная графика",
+  "VECTOR_LAYER": "Векторный слой",
+  "STROKE_COLOR_LABEL": "Контур",
+  "SYNC_WITH_PRIMARY_COLOR_LABEL": "Синхронизировать с основным цветом",
+  "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "Преобразовать/растеризовать выбранный слой в растровый слой (в изображение)",
+  "NEW_ELLIPSE_LAYER_NAME": "Овал",
+  "NEW_RECTANGLE_LAYER_NAME": "Прямоугольник",
+  "NEW_LINE_LAYER_NAME": "Линия",
+  "PAINT_TOOLSET": "Рисование",
+  "HARDNESS_SETTING": "Жесткость",
+  "SPACING_SETTING": "Промежуток",
+  "ANTI_ALIASING_SETTING": "Сглаживание",
+  "TOLERANCE_LABEL": "Допуск",
+  "HIGH_RES_PREVIEW": "Предварительный просмотр в высоком разрешении",
+  "LOW_RES_PREVIEW": "Предварительный просмотр в разрешении документа",
+  "TOGGLE_HIGH_RES_PREVIEW": "Вкл/выкл предварительный просмотр в высоком разрешении",
+  "PATH_TOOL": "Кривая",
+  "PATH_TOOL_TOOLTIP": "Создание векторных кривых ({0}).",
+  "PATH_TOOL_ACTION_DISPLAY": "Клик для добавления новой точки",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL": "Потяните за точку для создания кривой. Кликните точку для выделения.",
+  "PATH_TOOL_ACTION_DISPLAY_SHIFT": "Кликните для создания нового слоя.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "Кликните точку чтобы добавить её в выделение",
+  "PATH_TOOL_ACTION_DISPLAY_ALT": "Потяните за контрольную точку для изменения кривой только с одной стороны.",
+  "DEFAULT_PATH_LAYER_NAME": "Кривая",
+  "DELETE_NODES": "Удалить ноды",
+  "DELETE_NODES_DESCRIPTIVE": "Удалить выбранные ноды",
+  "DELETE_CELS": "Удалить кадры",
+  "DELETE_CELS_DESCRIPTIVE": "Удалить выбранные кадры",
+  "COPY_COLOR_TO_CLIPBOARD": "Скопировать цвет в буфер обмена",
+  "VIEWPORT_ROTATION": "Поворот камеры",
+  "NEXT_TOOL_SET": "Следующий набор инструментов",
+  "PREVIOUS_TOOL_SET": "Предыдущий набор инструментов",
+  "FILL_MODE": "Режим заливки",
+  "USE_LINEAR_SRGB_PROCESSING": "Использовать линейный sRGB при смешивании цветов",
+  "USE_LINEAR_SRGB_PROCESSING_DESC": "Включить линейное смешивание цветов в этом документе. После включения цвета будут смешиваться более естественно, но внешний вид изображения может измениться.",
+  "STROKE_CAP": "Конец контура",
+  "STROKE_JOIN": "Стык контура",
+  "COPY_VISIBLE": "Копировать видимое",
+  "COPY_VISIBLE_DESCRIPTIVE": "Копировать видимые пиксели",
+  "CREATE_CEL": "Создать кадр",
+  "CREATE_CEL_DESCRIPTIVE": "Создать новый кадр",
+  "DUPLICATE_CEL": "Дублировать кадр",
+  "DUPLICATE_CEL_DESCRIPTIVE": "Дублировать выбранный кадр",
+  "RENDER_PREVIEW": "Предварительный просмотр",
+  "TOGGLE_PLAY": "Плей/пауза анимации",
+  "OPEN_PREVIEW_WINDOW": "Открыть окно предварительного просмотра",
+  "PREVIEW_TITLE": "Предварительный просмотр",
+  "GREATER_THAN": "Больше",
+  "LESS_THAN": "Меньше",
+  "LESS_THAN_OR_EQUAL": "Меньше или равно",
+  "COMPARE": "Сравнить",
+  "MATH_POWER": "Степень",
+  "LOGARITHM": "Логарифм",
+  "ROOT": "Корень",
+  "COPY_NODES_DESCRIPTIVE": "Копировать выбранные ноды",
+  "PASTE_NODES": "Вставить ноды",
+  "PASTE_NODES_DESCRIPTIVE": "Вставить скопированные ноды",
+  "COPY_CELS": "Копировать кадры",
+  "COPY_CELS_DESCRIPTIVE": "Копировать выбранные кадры",
+  "VALUE": "Значение",
+  "PRESERVE_ALPHA": "Не затрагивать alpha",
+  "BLUR_FILTER_NODE": "Размытие по гауссу",
+  "LENGTH": "Длина",
+  "GREATER_THAN_OR_EQUAL": "Больше или равно",
+  "WEBP_FILE": "Изображения WebP",
+  "COLOR_NODE": "Цвет",
+  "CONVERT_TO_CURVE": "Преобразовать в кривую",
+  "CONVERT_TO_CURVE_DESCRIPTIVE": "Преобразовать выбранный векторный слой в кривую",
+  "FONT_FILES": "Файлы шрифтов",
+  "UNIT_PT": "тч",
+  "FONT_LABEL": "Шрифт",
+  "FONT_SIZE_LABEL": "Размер",
+  "SPACING_LABEL": "Интервал",
+  "TEXT_TOOL": "Текст",
+  "MISSING_FONT": "Отсутствует шрифт",
+  "TEXT_LAYER_NAME": "Текст",
+  "TEXT_TOOL_TOOLTIP": "Добавить текст ({0}).",
+  "BOLD_TOOLTIP": "Жирный",
+  "ITALIC_TOOLTIP": "Курсив",
+  "STRING_OPEN_IN_FOLDER": "Показать в проводнике",
+  "DISCO_BALL_EXAMPLE": "Диско-шар",
+  "COLOR_SPACE": "Пространство цветов",
+  "PHOTO_EXAMPLES": "Фотография",
+  "MASK_EXAMPLE": "Маска",
+  "SHADOW_NODE": "Тень",
+  "INPUT_MATRIX": "Входная матрица",
+  "OUTPUT_MATRIX": "Выходная матрица",
+  "CENTER": "Центр",
+  "CONTENT_OFFSET": "Отступ содержимого",
+  "CANVAS_POSITION": "Позиция холста",
+  "ROTATION_NODE": "Поворот",
+  "SCALE_NODE": "Масштабирование",
+  "ROTATE_NODE": "Поворот",
+  "HUE_VALUE": "Тон"
 }

+ 1127 - 0
src/PixiEditor/Data/Localization/Languages/tr.json

@@ -0,0 +1,1127 @@
+{
+  "RECENT_FILES": "Son Dosyalar",
+  "OPEN_FILE": "Dosya Aç",
+  "NEW_FILE": "Yeni",
+  "RECENT_EMPTY_TEXT": "Çok fazla boş alan",
+  "LANGUAGE": "Dil",
+  "GENERAL": "Genel",
+  "DISCORD": "Discord",
+  "KEY_BINDINGS": "Klavye Kısayolları",
+  "MISC": "Çeşitli",
+  "SHOW_STARTUP_WINDOW": "Başlangıç Penceresini Göster",
+  "RECENT_FILE_LENGTH": "Son dosyalar listesi uzunluğu",
+  "RECENT_FILE_LENGTH_TOOLTIP": "Dosya > Son Kullanılanlar altında kaç belgenin gösterileceği. Varsayılan: 8",
+  "DEFAULT_NEW_SIZE": "Varsayılan yeni dosya boyutu",
+  "WIDTH": "Genişlik",
+  "HEIGHT": "Yükseklik",
+  "TOOLS": "Araçlar",
+  "ENABLE_SHARED_TOOLBAR": "Paylaşılan araç çubuğunu etkinleştir",
+  "AUTOMATIC_UPDATES": "Otomatik Güncellemeler",
+  "CHECK_FOR_UPDATES": "Başlangıçta güncellemeleri kontrol et",
+  "UPDATE_STREAM": "Güncelleme akışı",
+  "UPDATE_CHANNEL_HELP_TOOLTIP": "Güncelleme kanalları yalnızca bağımsız sürümde değiştirilebilir (https://pixieditor.net adresinden indirilir).\nSteam ve Microsoft Store sürümleri güncellemeleri ayrı olarak yönetir.",
+  "DEBUG": "Hata Ayıklama",
+  "ENABLE_DEBUG_MODE": "Hata ayıklama modunu etkinleştir",
+  "OPEN_CRASH_REPORTS_DIR": "Kilitlenme raporları dizinini aç",
+  "DISCORD_RICH_PRESENCE": "Rich Presence",
+  "ENABLED": "Etkin",
+  "SHOW_IMAGE_NAME": "Resim adını göster",
+  "SHOW_IMAGE_SIZE": "Resim boyutunu göster",
+  "SHOW_LAYER_COUNT": "Katman sayısını göster",
+  "FILE": "Dosya",
+  "RECENT": "Son Kullanılanlar",
+  "OPEN": "Aç",
+  "SAVE_PIXI": "Kaydet (.pixi)",
+  "SAVE_AS_PIXI": "Farklı Kaydet... (.pixi)",
+  "EXPORT_IMG": "Dışa Aktar (.png, .jpg, vb.)",
+  "EDIT": "Düzenle",
+  "EXIT": "Çıkış",
+  "PERCENTAGE": "Yüzde",
+  "ABSOLUTE": "Mutlak",
+  "PRESERVE_ASPECT_RATIO": "En boy oranını koru",
+  "ANCHOR_POINT": "Bağlantı noktası",
+  "RESIZE_IMAGE": "Resmi yeniden boyutlandır",
+  "RESIZE": "Yeniden Boyutlandır",
+  "DOCUMENTATION": "Belgelendirme",
+  "WEBSITE": "Web Sitesi",
+  "OPEN_WEBSITE": "Web sitesini aç",
+  "REPOSITORY": "Depo",
+  "OPEN_REPOSITORY": "Depoyu aç",
+  "OPEN_DOCUMENTATION": "Belgelendirmeyi aç",
+  "LICENSE": "Lisans",
+  "OPEN_LICENSE": "Lisansı aç",
+  "THIRD_PARTY_LICENSES": "Üçüncü taraf lisansları",
+  "OPEN_THIRD_PARTY_LICENSES": "Üçüncü taraf lisanslarını aç",
+  "APPLY_TRANSFORM": "Dönüşümü uygula",
+  "INCREASE_TOOL_SIZE": "Araç boyutunu artır",
+  "DECREASE_TOOL_SIZE": "Araç boyutunu azalt",
+  "UPDATE_READY": "Güncelleme yüklenmeye hazır. Şimdi yüklemek ister misiniz?",
+  "NEW_UPDATE": "Yeni güncelleme",
+  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Yönetici ayrıcalıkları olmadan güncelleme yapılamadı. Lütfen PixiEditor'ı yönetici olarak çalıştırın.",
+  "INSUFFICIENT_PERMISSIONS": "Yetersiz izinler",
+  "UPDATE_CHECK_FAILED": "Güncelleme kontrolü başarısız oldu",
+  "COULD_NOT_CHECK_FOR_UPDATES": "Yeni bir güncelleme olup olmadığı kontrol edilemedi.",
+  "VERSION": "Sürüm {0}",
+  "BUILD_ID": "Yapı ID: {0}",
+  "OPEN_TEMP_DIR": "Temp dizini aç",
+  "OPEN_LOCAL_APPDATA_DIR": "Local AppData dizinini aç",
+  "OPEN_ROAMING_APPDATA_DIR": "Roaming AppData dizinini aç",
+  "OPEN_INSTALLATION_DIR": "Kurulum dizinini aç",
+  "DUMP_ALL_COMMANDS": "Tüm komutları dök",
+  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Tüm komutları bir metin dosyasına dök",
+  "CRASH": "Çökme",
+  "CRASH_APP": "Uygulamayı çökert",
+  "DELETE_USR_PREFS": "Kullanıcı tercihlerini sil (Roaming AppData)",
+  "DELETE_SHORTCUT_FILE": "Kısayol dosyasını sil (Roaming AppData)",
+  "DELETE_EDITOR_DATA": "Editör verilerini sil (Local AppData)",
+  "GENERATE_KEY_BINDINGS_TEMPLATE": "Klavye kısayolları şablonu oluştur",
+  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Klavye kısayolları json şablonu oluştur",
+  "VALIDATE_SHORTCUT_MAP": "Kısayol haritasını doğrula",
+  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Kısayol haritasını doğrular",
+  "VALIDATION_KEYS_NOTICE_DIALOG": "Boş anahtarlar: {0}\nBilinmeyen Komutlar: {1}",
+  "RESULT": "Sonuç",
+  "CLEAR_RECENT_DOCUMENTS": "Son belgeleri temizle",
+  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Son açılan belgeleri temizle",
+  "OPEN_CMD_DEBUG_WINDOW": "Komut hata ayıklama penceresini aç",
+  "PATH_DOES_NOT_EXIST": "{0} mevcut değil.",
+  "LOCATION_DOES_NOT_EXIST": "Konum mevcut değil.",
+  "FILE_NOT_FOUND": "Dosya bulunamadı.",
+  "ARE_YOU_SURE": "Emin misiniz?",
+  "ARE_YOU_SURE_PATH_FULL_PATH": "{0} dosyasını silmek istediğinizden emin misiniz?\nBu veriler tüm kurulumlar için kaybolacaktır.\n(Tam Yol: {1})",
+  "FAILED_TO_OPEN_FILE": "Dosya açılamadı",
+  "OLD_FILE_FORMAT": "Eski dosya biçimi",
+  "OLD_FILE_FORMAT_DESCRIPTION": "Bu .pixi dosyası artık desteklenmeyen\nve açılamayan eski biçimi kullanıyor.",
+  "NOTHING_FOUND": "Hiçbir şey bulunamadı",
+  "EXPORT": "Dışa Aktar",
+  "EXPORT_IMAGE": "Resmi dışa aktar",
+  "IMPORT": "İçe Aktar",
+  "SHORTCUT_TEMPLATES": "Kısayol şablonları",
+  "RESET_ALL": "Tümünü sıfırla",
+  "LAYER": "Katman",
+  "LAYER_DELETE_SELECTED": "Aktif katmanı/klasörü sil",
+  "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Aktif katmanı veya klasörü sil",
+  "LAYER_DELETE_ALL_SELECTED": "Seçili tüm katmanları/klasörleri sil",
+  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Seçili tüm katmanları ve/veya klasörleri sil",
+  "DELETE_SELECTED_PIXELS": "Seçili pikselleri sil",
+  "NEW_FOLDER": "Yeni klasör",
+  "CREATE_NEW_FOLDER": "Yeni klasör oluştur",
+  "NEW_LAYER": "Yeni katman",
+  "CREATE_NEW_LAYER": "Yeni katman oluştur",
+  "NEW_IMAGE": "Yeni resim",
+  "CREATE_NEW_IMAGE": "Yeni resim oluştur",
+  "SAVE": "Kaydet",
+  "SAVE_AS": "Farklı kaydet...",
+  "IMAGE": "Resim",
+  "SAVE_IMAGE": "Resmi kaydet",
+  "SAVE_IMAGE_AS": "Resmi yeni olarak kaydet",
+  "DUPLICATE": "Çoğalt",
+  "DUPLICATE_SELECTED_LAYER": "Seçili katmanı çoğalt",
+  "CREATE_MASK": "Maske oluştur",
+  "DELETE_MASK": "Maskeyi sil",
+  "TOGGLE_MASK": "Maskeyi aç/kapat",
+  "APPLY_MASK": "Maskeyi uygula",
+  "TOGGLE_VISIBILITY": "Görünürlüğü aç/kapat",
+  "MOVE_MEMBER_UP": "Öğeyi yukarı taşı",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "Seçili katmanı veya klasörü yukarı taşı",
+  "MOVE_MEMBER_DOWN": "Öğeyi aşağı taşı",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Seçili katmanı veya klasörü aşağı taşı",
+  "MERGE_ALL_SELECTED_LAYERS": "Seçili tüm katmanları birleştir",
+  "MERGE_WITH_ABOVE": "Seçili katmanı üsttekiyle birleştir",
+  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Seçili katmanı üstündekiyle birleştir",
+  "MERGE_WITH_BELOW": "Seçili katmanı alttakiyle birleştir",
+  "MERGE_WITH_BELOW_DESCRIPTIVE": "Seçili katmanı altındakiyle birleştir",
+  "ADD_REFERENCE_LAYER": "Referans Katmanı Ekle",
+  "DELETE_REFERENCE_LAYER": "Referans katmanını sil",
+  "TRANSFORM_REFERENCE_LAYER": "Referans katmanını dönüştür",
+  "TOGGLE_REFERENCE_LAYER_POS": "Referans katmanı konumunu değiştir",
+  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Referans katmanını en üst veya en alt arasında değiştir",
+  "RESET_REFERENCE_LAYER_POS": "Referans katmanı konumunu sıfırla",
+  "CLIP_CANVAS": "Tuvali Kırp",
+  "FLIP_IMG_VERTICALLY": "Resmi Dikey Çevir",
+  "FLIP_IMG_HORIZONTALLY": "Resmi Yatay Çevir",
+  "FLIP_LAYERS_VERTICALLY": "Seçili Katmanları Dikey Çevir",
+  "FLIP_LAYERS_HORIZONTALLY": "Seçili Katmanları Yatay Çevir",
+  "ROT_IMG_90": "Resmi 90 derece döndür",
+  "ROT_IMG_180": "Resmi 180 derece döndür",
+  "ROT_IMG_-90": "Resmi -90 derece döndür",
+  "ROT_LAYERS_90": "Seçili Katmanları 90 derece döndür",
+  "ROT_LAYERS_180": "Seçili Katmanları 180 derece döndür",
+  "ROT_LAYERS_-90": "Seçili Katmanları -90 derece döndür",
+  "TOGGLE_VERT_SYMMETRY_AXIS": "Dikey simetri eksenini aç/kapat",
+  "TOGGLE_HOR_SYMMETRY_AXIS": "Yatay simetri eksenini aç/kapat",
+  "DELETE_SELECTED": "Seçili olanı sil",
+  "DELETE_SELECTED_DESCRIPTIVE": "Seçili öğeyi sil (katman, pikseller, vb.)",
+  "RESIZE_DOCUMENT": "Belgeyi yeniden boyutlandır",
+  "RESIZE_CANVAS": "Tuvali yeniden boyutlandır",
+  "CENTER_CONTENT": "İçeriği ortala",
+  "CUT": "Kes",
+  "CUT_DESCRIPTIVE": "Seçili alanı/katmanları kes",
+  "PASTE": "Yapıştır",
+  "PASTE_DESCRIPTIVE": "Pano içeriğini yapıştır",
+  "PASTE_AS_NEW_LAYER": "Yeni katman olarak yapıştır",
+  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Panodan yeni katman olarak yapıştır",
+  "PASTE_REFERENCE_LAYER": "Referans katmanı yapıştır",
+  "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Pano içeriğini referans katmanı olarak yapıştır",
+  "PASTE_COLOR": "Rengi yapıştır",
+  "PASTE_COLOR_DESCRIPTIVE": "Panodan rengi yapıştır",
+  "PASTE_COLOR_SECONDARY": "Rengi ikincil olarak yapıştır",
+  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Panodan rengi ikincil renk olarak yapıştır",
+  "CLIPBOARD": "Pano",
+  "COPY": "Kopyala",
+  "COPY_DESCRIPTIVE": "Panoya kopyala",
+  "COPY_COLOR_HEX": "Birincil rengi kopyala (HEX)",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "Birincil rengi HEX kodu olarak kopyala",
+  "COPY_COLOR_RGB": "Birincil rengi kopyala (RGB)",
+  "COPY_COLOR_RGB_DESCRIPTIVE": "Birincil rengi RGB kodu olarak kopyala",
+  "COPY_COLOR_SECONDARY_HEX": "İkincil rengi kopyala (HEX)",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "İkincil rengi HEX kodu olarak kopyala",
+  "COPY_COLOR_SECONDARY_RGB": "İkincil rengi kopyala (RGB)",
+  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "İkincil rengi RGB kodu olarak kopyala",
+  "PALETTE_COLORS": "Palet Renkleri",
+  "REPLACE_SECONDARY_BY_PRIMARY": "İkincil rengi birincil ile değiştir",
+  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "İkincil rengi birincil renk ile değiştir",
+  "REPLACE_PRIMARY_BY_SECONDARY": "Birincil rengi ikincil ile değiştir",
+  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Birincil rengi ikincil renk ile değiştir",
+  "OPEN_PALETTE_BROWSER": "Palet tarayıcısını aç",
+  "OVERWRITE_PALETTE_CONSENT": "'{0}' paleti zaten mevcut, üzerine yazmak istiyor musunuz?",
+  "PALETTE_EXISTS": "Palet zaten mevcut",
+  "REPLACE_PALETTE_CONSENT": "Mevcut paleti seçilenle değiştirmek istiyor musunuz?",
+  "REPLACE_PALETTE": "Mevcut paleti değiştir",
+  "SELECT_COLOR_1": "Renk 1'i seç",
+  "SELECT_COLOR_2": "Renk 2'yi seç",
+  "SELECT_COLOR_3": "Renk 3'ü seç",
+  "SELECT_COLOR_4": "Renk 4'ü seç",
+  "SELECT_COLOR_5": "Renk 5'i seç",
+  "SELECT_COLOR_6": "Renk 6'yı seç",
+  "SELECT_COLOR_7": "Renk 7'yi seç",
+  "SELECT_COLOR_8": "Renk 8'i seç",
+  "SELECT_COLOR_9": "Renk 9'u seç",
+  "SELECT_COLOR_10": "Renk 10'u seç",
+  "SELECT_TOOL": "{0} Aracını Seç",
+  "SELECT_COLOR_1_DESCRIPTIVE": "Paletteki ilk rengi seç",
+  "SELECT_COLOR_2_DESCRIPTIVE": "Paletteki ikinci rengi seç",
+  "SELECT_COLOR_3_DESCRIPTIVE": "Paletteki üçüncü rengi seç",
+  "SELECT_COLOR_4_DESCRIPTIVE": "Paletteki dördüncü rengi seç",
+  "SELECT_COLOR_5_DESCRIPTIVE": "Paletteki beşinci rengi seç",
+  "SELECT_COLOR_6_DESCRIPTIVE": "Paletteki altıncı rengi seç",
+  "SELECT_COLOR_7_DESCRIPTIVE": "Paletteki yedinci rengi seç",
+  "SELECT_COLOR_8_DESCRIPTIVE": "Paletteki sekizinci rengi seç",
+  "SELECT_COLOR_9_DESCRIPTIVE": "Paletteki dokuzuncu rengi seç",
+  "SELECT_COLOR_10_DESCRIPTIVE": "Paletteki onuncu rengi seç",
+  "SWAP_COLORS": "Renkleri değiştir",
+  "SWAP_COLORS_DESCRIPTIVE": "Birincil ve ikincil renkleri değiştir",
+  "SEARCH": "Ara",
+  "COMMAND_SEARCH": "Komut arama",
+  "OPEN_COMMAND_SEARCH": "Komut arama penceresini aç",
+  "SELECT": "Seç",
+  "DESELECT": "Seçimi Kaldır",
+  "INVERT": "Ters Çevir",
+  "SELECTION": "Seçim",
+  "SELECT_ALL": "Tümünü seç",
+  "SELECT_ALL_DESCRIPTIVE": "Her şeyi seç",
+  "CLEAR_SELECTION": "Seçimi temizle",
+  "INVERT_SELECTION": "Seçimi ters çevir",
+  "INVERT_SELECTION_DESCRIPTIVE": "Seçimi ters çevir",
+  "TRANSFORM_SELECTED_AREA": "Seçili alanı dönüştür",
+  "NUDGE_SELECTED_LEFT": "Seçili nesneyi sola kaydır",
+  "NUDGE_SELECTED_RIGHT": "Seçili nesneyi sağa kaydır",
+  "NUDGE_SELECTED_UP": "Seçili nesneyi yukarı kaydır",
+  "NUDGE_SELECTED_DOWN": "Seçili nesneyi aşağı kaydır",
+  "MASK_FROM_SELECTION": "Seçimden yeni maske",
+  "MASK_FROM_SELECTION_DESCRIPTIVE": "Seçimi yeni maskeye dönüştür",
+  "ADD_SELECTION_TO_MASK": "Seçimi maskeye ekle",
+  "SUBTRACT_SELECTION_FROM_MASK": "Seçimi maskeden çıkar",
+  "INTERSECT_SELECTION_MASK": "Seçimi maskeyle kesiştir",
+  "SELECTION_TO_MASK": "Seçimi maskeye",
+  "TO_NEW_MASK": "yeni maskeye",
+  "ADD_TO_MASK": "maskeye ekle",
+  "SUBTRACT_FROM_MASK": "maskeden çıkar",
+  "INTERSECT_WITH_MASK": "maskeyle kesiştir",
+  "STYLUS": "Kalem",
+  "TOGGLE_PEN_MODE": "Kalem modunu aç/kapat",
+  "UNDO": "Geri Al",
+  "UNDO_DESCRIPTIVE": "Son eylemi geri al",
+  "REDO": "Yinele",
+  "REDO_DESCRIPTIVE": "Son eylemi yinele",
+  "WINDOWS": "Pencereler",
+  "TOGGLE_GRIDLINES": "Kılavuz çizgilerini aç/kapat",
+  "GRIDLINES_SIZE": "Kılavuz Boyutu",
+  "ZOOM_IN": "Yakınlaştır",
+  "ZOOM_OUT": "Uzaklaştır",
+  "NEW_WINDOW_FOR_IMG": "Mevcut resim için yeni pencere",
+  "CENTER_ACTIVE_VIEWPORT": "Aktif görünüm alanını ortala",
+  "FLIP_VIEWPORT_HORIZONTALLY": "Görünüm alanını yatay çevir",
+  "FLIP_VIEWPORT_VERTICALLY": "Görünüm alanını dikey çevir",
+  "SETTINGS": "Ayarlar",
+  "OPEN_SETTINGS": "Ayarları aç",
+  "OPEN_SETTINGS_DESCRIPTIVE": "Ayarlar penceresini aç",
+  "OPEN_STARTUP_WINDOW": "Başlangıç penceresini aç",
+  "OPEN_SHORTCUT_WINDOW": "Kısayollar penceresini aç",
+  "OPEN_ABOUT_WINDOW": "Hakkında penceresini aç",
+  "OPEN_PREVIEW_WINDOW": "Önizleme penceresini aç",
+  "ERROR": "Hata",
+  "INTERNAL_ERROR": "İç Hata",
+  "ERROR_SAVE_LOCATION": "Dosya belirtilen konuma kaydedilemedi",
+  "ERROR_WHILE_SAVING": "Kaydederken bir iç hata oluştu. Lütfen tekrar deneyin.",
+  "UNKNOWN_ERROR_SAVING": "Kaydederken bir hata oluştu.",
+  "FAILED_ASSOCIATE_LOSPEC": "Lospec Palet protokolü ilişkilendirilemedi.",
+  "REDDIT": "Reddit",
+  "GITHUB": "GitHub",
+  "YOUTUBE": "YouTube",
+  "DONATE": "Bağış Yap",
+  "YES": "Evet",
+  "NO": "Hayır",
+  "CANCEL": "İptal",
+  "UNNAMED": "Adsız",
+  "DELETE": "Sil",
+  "USER_PREFS": "Kullanıcı tercihleri (Roaming)",
+  "SHORTCUT_FILE": "Kısayol dosyası (Roaming)",
+  "EDITOR_DATA": "Editör verileri (Local)",
+  "MOVE_VIEWPORT_TOOLTIP": "Görünüm alanını taşır. ({0})",
+  "MOVE_VIEWPORT_ACTION_DISPLAY": "Görünüm alanını kaydırmak için tıklayın ve hareket ettirin",
+  "MOVE_TOOL_TOOLTIP": "Katmanları seç ve dönüştür ({0}).",
+  "MOVE_TOOL_ACTION_DISPLAY": "Seçili pikselleri taşımak için fareyi basılı tutun. Tüm katmanları taşımak için Ctrl tuşunu basılı tutun.",
+  "PEN_TOOL_TOOLTIP": "Kalem. ({0})",
+  "PEN_TOOL_ACTION_DISPLAY": "Çizmek için tıklayın ve hareket ettirin.",
+  "PIXEL_PERFECT_SETTING": "Piksel mükemmelliği",
+  "RECTANGLE_TOOL_TOOLTIP": "Tuval üzerine dikdörtgen çizer ({0}). Kare çizmek için Shift tuşunu basılı tutun.",
+  "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir dikdörtgen çizmek için tıklayın ve hareket ettirin. Kare çizmek için Shift tuşunu basılı tutun.",
+  "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Bir kare çizmek için tıklayın ve hareket ettirin.",
+  "KEEP_ORIGINAL_IMAGE_SETTING": "Orijinal resmi koru",
+  "ROTATE_VIEWPORT_TOOLTIP": "Görünüm alanını döndürür. ({0})",
+  "ROTATE_VIEWPORT_ACTION_DISPLAY": "Görünüm alanını döndürmek için tıklayın ve hareket ettirin",
+  "SELECT_TOOL_TOOLTIP": "Alan seçer. ({0})",
+  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Bir alan seçmek için tıklayın ve hareket ettirin. Mevcut seçime eklemek için Shift tuşunu basılı tutun. Ondan çıkarmak için Ctrl tuşunu basılı tutun.",
+  "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Mevcut seçime eklemek için tıklayın ve hareket ettirin.",
+  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Mevcut seçimden çıkarmak için tıklayın ve hareket ettirin.",
+  "ZOOM_TOOL_TOOLTIP": "Görünüm alanını yakınlaştırır ({0}). Yakınlaştırmak için tıklayın, uzaklaştırmak için alt tuşunu basılı tutup tıklayın.",
+  "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Yakınlaştırmak için tıklayın ve hareket ettirin. Yakınlaştırmak için tıklayın, uzaklaştırmak için ctrl tuşunu basılı tutup tıklayın.",
+  "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Yakınlaştırmak için tıklayın ve hareket ettirin. Uzaklaştırmak için tıklayın, yakınlaştırmak için ctrl tuşunu bırakıp tıklayın.",
+  "BRIGHTNESS_TOOL_TOOLTIP": "Pikselleri daha parlak veya daha koyu yapar ({0}). Pikselleri daha koyu yapmak için Ctrl tuşunu basılı tutun.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Pikselleri daha parlak yapmak için üzerine çizin. Koyulaştırmak için Ctrl tuşunu basılı tutun.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Pikselleri daha koyu yapmak için üzerine çizin. Parlatmak için Ctrl tuşunu bırakın.",
+  "COLOR_PICKER_TOOLTIP": "Tuvalden birincil rengi seçer. ({0})",
+  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Renkleri seçmek için tıklayın. Tuvali gizlemek için Ctrl tuşunu basılı tutun. Referans katmanını gizlemek için Shift tuşunu basılı tutun",
+  "ELLIPSE_TOOL_TOOLTIP": "Tuval üzerine bir elips çizer ({0}). Bir daire çizmek için Shift tuşunu basılı tutun.",
+  "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir elips çizmek için fareyi tıklayın ve hareket ettirin. Bir daire çizmek için Shift tuşunu basılı tutun.",
+  "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Bir daire çizmek için fareyi tıklayın ve hareket ettirin.",
+  "ERASER_TOOL_TOOLTIP": "Pikselden rengi siler. ({0})",
+  "ERASER_TOOL_ACTION_DISPLAY": "Silmek için tıklayın ve hareket ettirin.",
+  "FLOOD_FILL_TOOL_TOOLTIP": "Alanı renkle doldurur. ({0})",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Doldurmak için bir alana basın. Tüm katmanları dikkate almak için Ctrl tuşunu basılı tutun.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Doldurmak için bir alana basın. Sadece mevcut katmanları dikkate almak için Ctrl tuşunu bırakın.",
+  "LASSO_TOOL_TOOLTIP": "Kement. ({0})",
+  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Kementin içindeki pikselleri seçmek için tıklayın ve hareket ettirin. Mevcut seçime eklemek için Shift tuşunu basılı tutun. Ondan çıkarmak için Ctrl tuşunu basılı tutun.",
+  "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Kementin içindeki pikselleri seçime eklemek için tıklayın ve hareket ettirin.",
+  "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Kementin içindeki pikselleri seçimden çıkarmak için tıklayın ve hareket ettirin.",
+  "LINE_TOOL_TOOLTIP": "Tuval üzerine çizgi çizer ({0}). Hizalamayı etkinleştirmek için Shift tuşunu basılı tutun.",
+  "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Bir çizgi çizmek için tıklayın ve hareket ettirin. Hizalamayı etkinleştirmek için Shift tuşunu basılı tutun.",
+  "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Hizalama etkinken bir çizgi çizmek için fareyi tıklayın ve hareket ettirin.",
+  "MAGIC_WAND_TOOL_TOOLTIP": "Sihirli Değnek ({0}). Seçimi doldurur",
+  "MAGIC_WAND_ACTION_DISPLAY": "Seçimi doldurmak için tıklayın.",
+  "PEN_TOOL": "Kalem",
+  "BRIGHTNESS_TOOL": "Parlaklık",
+  "COLOR_PICKER_TOOL": "Renk Seçici",
+  "ELLIPSE_TOOL": "Elips",
+  "ERASER_TOOL": "Silgi",
+  "FLOOD_FILL_TOOL": "Boya Kovası",
+  "LASSO_TOOL": "Kement",
+  "LINE_TOOL": "Çizgi",
+  "MAGIC_WAND_TOOL": "Sihirli Değnek",
+  "MOVE_TOOL": "Taşı",
+  "MOVE_VIEWPORT_TOOL": "Görünüm Alanını Taşı",
+  "RECTANGLE_TOOL": "Dikdörtgen",
+  "ROTATE_VIEWPORT_TOOL": "Görünüm Alanını Döndür",
+  "SELECT_TOOL_NAME": "Seç",
+  "ZOOM_TOOL": "Yakınlaştır",
+  "SHAPE_LABEL": "Şekil",
+  "MODE_LABEL": "Mod",
+  "SCOPE_LABEL": "Kapsam",
+  "FILL_SHAPE_LABEL": "Şekli doldur",
+  "FILL_COLOR_LABEL": "Dolgu rengi",
+  "TOOL_SIZE_LABEL": "Araç boyutu",
+  "STRENGTH_LABEL": "Güç",
+  "NEW": "Yeni",
+  "ADD": "Ekle",
+  "SUBTRACT": "Çıkar",
+  "INTERSECT": "Kesiştir",
+  "RECTANGLE": "Dikdörtgen",
+  "CIRCLE": "Daire",
+  "ABOUT": "Hakkında",
+  "MINIMIZE": "Simge Durumuna Küçült",
+  "RESTORE": "Geri Yükle",
+  "MAXIMIZE": "Ekranı Kapla",
+  "CLOSE": "Kapat",
+  "EXPORT_SIZE_HINT": "Resmi paylaşmak istiyorsanız, en iyi netlik için %{0} deneyin",
+  "CREATE": "Oluştur",
+  "BASE_LAYER_NAME": "Temel katman",
+  "ENABLE_MASK": "Maskeyi etkinleştir",
+  "SELECTED_AREA_EMPTY": "Seçili alan boş",
+  "NOTHING_TO_COPY": "Kopyalanacak bir şey yok",
+  "REFERENCE_LAYER_PATH": "Referans katmanı yolu",
+  "FLIP": "Çevir",
+  "ROTATION": "Döndürme",
+  "ROT_IMG_90_D": "Resmi 90° Döndür",
+  "ROT_IMG_180_D": "Resmi 180° Döndür",
+  "ROT_IMG_-90_D": "Resmi -90° Döndür",
+  "ROT_LAYERS_90_D": "Seçili Katmanları 90° Döndür",
+  "ROT_LAYERS_180_D": "Seçili Katmanları 180° Döndür",
+  "ROT_LAYERS_-90_D": "Seçili Katmanları -90° Döndür",
+  "UNNAMED_PALETTE": "Adsız Palet",
+  "CLICK_SELECT_PRIMARY": "Ana renk olarak seçmek için tıklayın.",
+  "PEN_MODE": "Kalem modu",
+  "VIEW": "Görünüm",
+  "HORIZONTAL_LINE_SYMMETRY": "Yatay çizgi simetrisi",
+  "VERTICAL_LINE_SYMMETRY": "Dikey çizgi simetrisi",
+  "COLOR_PICKER_TITLE": "Renk Seçici",
+  "COLOR_SLIDERS_TITLE": "Renk Kaydırıcıları",
+  "PALETTE_TITLE": "Palet",
+  "SWATCHES_TITLE": "Renk Örnekleri",
+  "LAYERS_TITLE": "Katmanlar",
+  "PREVIEW_TITLE": "Önizleme",
+  "NORMAL_BLEND_MODE": "Normal",
+  "ERASE_BLEND_MODE": "Sil",
+  "DARKEN_BLEND_MODE": "Koyulaştır",
+  "MULTIPLY_BLEND_MODE": "Çarp",
+  "COLOR_BURN_BLEND_MODE": "Renk Yanması",
+  "LIGHTEN_BLEND_MODE": "Açıklaştır",
+  "SCREEN_BLEND_MODE": "Ekran",
+  "COLOR_DODGE_BLEND_MODE": "Renk Soldurma",
+  "OVERLAY_BLEND_MODE": "Kaplama",
+  "SOFT_LIGHT_BLEND_MODE": "Yumuşak Işık",
+  "HARD_LIGHT_BLEND_MODE": "Sert Işık",
+  "DIFFERENCE_BLEND_MODE": "Fark",
+  "EXCLUSION_BLEND_MODE": "Dışlama",
+  "HUE_BLEND_MODE": "Ton",
+  "SATURATION_BLEND_MODE": "Doygunluk",
+  "LUMINOSITY_BLEND_MODE": "Parlaklık",
+  "COLOR_BLEND_MODE": "Renk",
+  "NOT_SUPPORTED_BLEND_MODE": "Desteklenmiyor",
+  "RESTART": "Yeniden Başlat",
+  "SORT_BY": "Sırala",
+  "NAME": "Ad",
+  "COLORS": "Renkler",
+  "DEFAULT": "Varsayılan",
+  "ALPHABETICAL": "Alfabetik",
+  "COLOR_COUNT": "Renk sayısı",
+  "ANY": "Herhangi biri",
+  "MAX": "Maks",
+  "MIN": "Min",
+  "EXACT": "Tam",
+  "ASCENDING": "Artan",
+  "DESCENDING": "Azalan",
+  "NAME_IS_TOO_LONG": "Ad çok uzun",
+  "STOP_IT_TEXT1": "Bu kadar yeter. Dosya adlarını düzenle.",
+  "STOP_IT_TEXT2": "Lütfen bu adları kopyalamayı bırakır mısın?",
+  "REPLACER_TOOLTIP": "Palet rengine sağ tıklayın ve 'Değiştir'i seçin veya buraya bırakın.",
+  "CLICK_TO_CHOOSE_COLOR": "Rengi seçmek için tıklayın",
+  "REPLACE_COLOR": "Rengi değiştir",
+  "PALETTE_COLOR_TOOLTIP": "Ana renk olarak seçmek için tıklayın. Değiştirmek için başka bir palet renginin üzerine sürükleyip bırakın.",
+  "ADD_FROM_SWATCHES": "Renk örneklerinden ekle",
+  "ADD_COLOR_TO_PALETTE": "Palete renk ekle",
+  "USE_IN_CURRENT_IMAGE": "Mevcut resimde kullan",
+  "ADD_TO_FAVORITES": "Favorilere ekle",
+  "BROWSE_PALETTES": "Paletlere göz at",
+  "LOAD_PALETTE": "Palet yükle",
+  "SAVE_PALETTE": "Paleti kaydet",
+  "FAVORITES": "Favoriler",
+  "ADD_FROM_CURRENT_PALETTE": "Mevcut paletten ekle",
+  "OPEN_PALETTES_DIR_TOOLTIP": "Paletler dizinini gezginde aç",
+  "BROWSE_ON_LOSPEC_TOOLTIP": "Lospec'te paletlere göz at",
+  "IMPORT_FROM_FILE_TOOLTIP": "Dosyadan içe aktar",
+  "TOP_LEFT": "Sol üst",
+  "TOP_CENTER": "Orta üst",
+  "TOP_RIGHT": "Sağ üst",
+  "MIDDLE_LEFT": "Sol orta",
+  "MIDDLE_CENTER": "Orta orta",
+  "MIDDLE_RIGHT": "Sağ orta",
+  "BOTTOM_LEFT": "Sol alt",
+  "BOTTOM_CENTER": "Orta alt",
+  "BOTTOM_RIGHT": "Sağ alt",
+  "CLIP_TO_BELOW": "Alttaki üyeye kırp",
+  "MOVE_UPWARDS": "Yukarı taşı",
+  "MOVE_DOWNWARDS": "Aşağı taşı",
+  "MERGE_SELECTED": "Seçilenleri birleştir",
+  "LOCK_TRANSPARENCY": "Şeffaflığı kilitle",
+  "COULD_NOT_LOAD_PALETTE": "Paletler getirilemedi",
+  "NO_PALETTES_FOUND": "Palet bulunamadı.",
+  "LOSPEC_LINK_TEXT": "Bazılarını burada bulabileceğinizi duydum: lospec.com/palette-list",
+  "PALETTE_BROWSER": "Palet Tarayıcısı",
+  "DELETE_PALETTE_CONFIRMATION": "Bu paleti silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
+  "SHORTCUTS_IMPORTED": "{0} konumundan kısayollar başarıyla içe aktarıldı.",
+  "SHORTCUT_PROVIDER_DETECTED": "{0} yüklü olduğunu tespit ettik. Kısayolları oradan içe aktarmak ister misiniz?",
+  "IMPORT_FROM_INSTALLATION": "Kurulumdan içe aktar",
+  "IMPORT_INSTALLATION_OPTION1": "Kurulumdan içe aktar",
+  "IMPORT_INSTALLATION_OPTION2": "Varsayılanları kullan",
+  "IMPORT_FROM_TEMPLATE": "Şablondan içe aktar",
+  "SHORTCUTS_IMPORTED_SUCCESS": "Kısayollar başarıyla içe aktarıldı.",
+  "WARNING_RESET_SHORTCUTS_DEFAULT": "Tüm kısayolları varsayılan değerlerine sıfırlamak istediğinizden emin misiniz?",
+  "SUCCESS": "Başarılı",
+  "WARNING": "Uyarı",
+  "ERROR_IMPORTING_IMAGE": "Resim içe aktarılırken bir hata oluştu.",
+  "SHORTCUTS_CORRUPTED_TITLE": "Bozuk kısayollar dosyası",
+  "SHORTCUTS_CORRUPTED": "Kısayollar dosyası bozulmuştu, varsayılana sıfırlanıyor.",
+  "FAILED_DOWNLOAD_PALETTE": "Palet indirilemedi",
+  "FILE_INCORRECT_FORMAT": "Dosya doğru biçimde değildi",
+  "INVALID_FILE": "Geçersiz dosya",
+  "SHORTCUTS_FILE_INCORRECT_FORMAT": "Kısayollar dosyası doğru biçimde değildi",
+  "UNSUPPORTED_FILE_FORMAT": "Bu dosya biçimi desteklenmiyor",
+  "ALREADY_ASSIGNED": "Zaten atanmış",
+  "REPLACE": "Değiştir",
+  "SWAP": "Takas Et",
+  "SHORTCUT_ALREADY_ASSIGNED_SWAP": "Bu kısayol zaten '{0}' öğesine atanmış\nMevcut kısayolu değiştirmek mi yoksa ikisini takas etmek mi istersiniz?",
+  "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Bu kısayol zaten '{0}' öğesine atanmış\nMevcut kısayolu değiştirmek ister misiniz?",
+  "UNSAVED_CHANGES": "Kaydedilmemiş değişiklikler",
+  "DOCUMENT_MODIFIED_SAVE": "Belge değiştirildi. Değişiklikleri kaydetmek ister misiniz?",
+  "SESSION_UNSAVED_DATA": "{0} kaydedilmemiş veri içeriyor. Emin misiniz?",
+  "PROJECT_MAINTAINERS": "Proje Sorumluları",
+  "OTHER_AWESOME_CONTRIBUTORS": "Ve diğer harika katkıda bulunanlar",
+  "HELP": "Yardım",
+  "STOP_IT_TEXT3": "Hayır, gerçekten, dur.",
+  "STOP_IT_TEXT4": "Yapacak daha iyi bir işin yok mu?",
+  "LINEAR_DODGE_BLEND_MODE": "Doğrusal Soldurma (Ekle)",
+  "PRESS_ANY_KEY": "Herhangi bir tuşa basın",
+  "NONE_SHORTCUT": "Yok",
+  "REFERENCE": "Referans",
+  "PUT_REFERENCE_LAYER_ABOVE": "Referans katmanını üste koy",
+  "PUT_REFERENCE_LAYER_BELOW": "Referans katmanını alta koy",
+  "TOGGLE_VERTICAL_SYMMETRY": "Dikey simetriyi aç/kapat",
+  "TOGGLE_HORIZONTAL_SYMMETRY": "Yatay simetriyi aç/kapat",
+  "RESET_VIEWPORT": "Görünüm alanını sıfırla",
+  "VIEWPORT_SETTINGS": "Görünüm alanı ayarları",
+  "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Seçili katmanlardaki pikselleri taşımak için fareyi tıklayıp basılı tutun.",
+  "CTRL_KEY": "Ctrl",
+  "SHIFT_KEY": "Shift",
+  "ALT_KEY": "Alt",
+  "RENAME": "Yeniden Adlandır",
+  "PIXEL_UNIT": "px",
+  "OPEN_LOCALIZATION_DEBUG_WINDOW": "Yerelleştirme Hata Ayıklama Penceresini Aç",
+  "FORCE_OTHER_FLOW_DIRECTION": "Diğer akış yönünü zorla",
+  "API_KEY": "API Anahtarı",
+  "LOCALIZATION_VIEW_TYPE": "Yerelleştirme Görünüm Türü",
+  "LOAD_LANGUAGE_FROM_FILE": "Dili dosyadan yükle",
+  "LOG_IN": "Giriş Yap",
+  "SYNC": "Senkronize Et",
+  "NOT_LOGGED_IN": "Giriş yapılmadı",
+  "POE_EDITOR_ERROR": "POEditor Hatası: {0} {1}",
+  "HTTP_ERROR_MESSAGE": "HTTP Hatası: {0} {1}",
+  "LOGGED_IN": "Giriş yapıldı",
+  "SYNCED_SUCCESSFULLY": "Başarıyla senkronize edildi",
+  "EXCEPTION_ERROR": "İstisna: {0}",
+  "DROP_PALETTE": "Paleti buraya bırakın",
+  "SECURITY_ERROR": "Güvenlik hatası",
+  "SECURITY_ERROR_MSG": "Belirtilen konuma yazma hakkı yok.",
+  "IO_ERROR": "G/Ç hatası",
+  "IO_ERROR_MSG": "Diske yazarken hata oluştu.",
+  "FAILED_ASSOCIATE_PIXI": ".pixi dosyası PixiEditor ile ilişkilendirilemedi.",
+  "COULD_NOT_SAVE_PALETTE": "Palet kaydedilirken bir hata oluştu.",
+  "NO_COLORS_TO_SAVE": "Kaydedilecek renk yok.",
+  "CANVAS": "Tuval",
+  "SINGLE_LAYER": "Tek Katman",
+  "CHOOSE": "Seç",
+  "REMOVE": "Kaldır",
+  "FILE_FORMAT_NOT_ASEPRITE_KEYS": "Dosya bir \".aseprite-keys\" dosyası değil",
+  "FILE_HAS_INVALID_SHORTCUT": "Dosya geçersiz bir kısayol içeriyor",
+  "FILE_EXTENSION_NOT_SUPPORTED": "'{0}' dosya türü desteklenmiyor",
+  "ERROR_READING_FILE": "Dosya okunurken hata oluştu",
+  "DISCARD_PALETTE": "Paleti at",
+  "DISCARD_PALETTE_CONFIRMATION": "Mevcut paleti atmak istediğinizden emin misiniz? Bu işlem geri alınamaz.",
+  "IMPORT_AS_NEW_LAYER": "Yeni katman olarak içe aktar",
+  "PASTE_AS_PRIMARY_COLOR": "Birincil renk olarak yapıştır",
+  "IMPORT_AS_NEW_FILE": "Yeni dosya olarak içe aktar",
+  "IMPORT_PALETTE_FILE": "Palet dosyasını içe aktar",
+  "IMPORT_MULTIPLE_PALETTE_COLORS": "Renkleri palete aktar",
+  "IMPORT_SINGLE_PALETTE_COLOR": "Rengi palete aktar",
+  "IMPORT_AS_REFERENCE_LAYER": "Referans katmanı olarak içe aktar",
+  "NAVIGATOR_PICK_ACTION_DISPLAY": "Renk seçmek için sağ tıklayın, rengi panoya kopyalamak için Shift-sağ tıklayın",
+  "OPEN_FILE_FROM_CLIPBOARD": "Panodan aç",
+  "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Panodan aç",
+  "OPEN_LOCALIZATION_DATA": "LocalizationData.json dosyasını açmak istiyor musunuz?\nGüncellenmiş tarih panoya kopyalandı.\nDeğişikliklerin yeniden başlatılana kadar uygulanmayacağını unutmayın",
+  "DOWNLOADING_LANGUAGE_FAILED": "Dil indirilemedi.\nAPI Anahtarı aşırı kullanılmış olabilir.",
+  "LOCALIZATION_DATA_NOT_FOUND": "Yerelleştirme veri yolu bulunamadı",
+  "APPLY": "Uygula",
+  "UPDATE_SOURCE": "Kaynağı güncelle",
+  "COPY_TO_CLIPBOARD": "Panoya kopyala",
+  "LANGUAGE_FILE_NOT_FOUND": "Dil dosyası bulunamadı.\nAranan: {0}",
+  "PROJECT_ROOT_NOT_FOUND": "PixiEditor Proje kökü bulunamadı.\nAranan: PixiEditor.csproj",
+  "LOCALIZATION_FOLDER_NOT_FOUND": "Yerelleştirme klasörü bulunamadı.\nAranan: /Data/Localization",
+  "SELECT_A_LANGUAGE": "Bir dil seçin",
+  "DONE": "Bitti",
+  "SOURCE_UNSET_OR_MISSING": "Kaynak eksik/ayarlanmamış",
+  "SOURCE_NEWER": "Kaynak daha yeni",
+  "SOURCE_UP_TO_DATE": "Kaynak güncel",
+  "SOURCE_OLDER": "Bulut daha yeni",
+  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Referans katmanından renkleri seçmek için tıklayın.",
+  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Tuvalden renkleri seçmek için tıklayın.",
+  "LOCALIZATION_DEBUG_WINDOW_TITLE": "Yerelleştirme Hata Ayıklama Penceresi",
+  "COMMAND_DEBUG_WINDOW_TITLE": "Komut Hata Ayıklama Penceresi",
+  "SHORTCUTS_TITLE": "Kısayollar",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Dönüşümü ölçeklemek için tutamaçları sürükleyin. Merkezden ölçeklemek için Ctrl tuşunu basılı tutun ve bir tutamacı sürükleyin. Orantılı olarak ölçeklemek için Shift tuşunu basılı tutun. Eğim vermek için Alt tuşunu basılı tutun ve bir yan tutamacı sürükleyin. Döndürmek için dış tutamaçları sürükleyin.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Dönüşümü ölçeklemek için tutamaçları sürükleyin. Merkezden ölçeklemek için Ctrl tuşunu basılı tutun ve bir tutamacı sürükleyin. Orantılı olarak ölçeklemek için Shift tuşunu basılı tutun. Eğim vermek için Alt tuşunu basılı tutun ve bir yan tutamacı sürükleyin. Döndürmek için dış tutamaçları sürükleyin.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Dönüşümü ölçeklemek için tutamaçları sürükleyin. Merkezden ölçeklemek için Ctrl tuşunu basılı tutun ve bir tutamacı sürükleyin. Orantılı olarak ölçeklemek için Shift tuşunu basılı tutun. Döndürmek için dış tutamaçları sürükleyin.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Dönüşümü ölçeklemek için tutamaçları sürükleyin. Merkezden ölçeklemek için Ctrl tuşunu basılı tutun ve bir tutamacı sürükleyin. Orantılı olarak ölçeklemek için Shift tuşunu basılı tutun.",
+  "LOCAL_PALETTE_SOURCE_NAME": "Yerel",
+  "ERROR_FORBIDDEN_UNIQUE_NAME": "Uzantı benzersiz adı 'pixieditor' ile başlayamaz.",
+  "ERROR_MISSING_METADATA": "Uzantı meta veri anahtarı '{0}' eksik.",
+  "ERROR_NO_CLASS_ENTRY": "Uzantı sınıf girişi '{0}' yolunda eksik.",
+  "ERROR_NO_ENTRY_ASSEMBLY": "Uzantı giriş derlemesi '{0}' yolunda eksik.",
+  "ERROR_MISSING_ADDITIONAL_CONTENT": "Mevcut kurulumunuz bu uzantının yüklenmesine izin vermiyor. Belki de sahip değilsiniz veya kurulu değil. Buradan satın alabilirsiniz: '{0}'.",
+  "BUY_SUPPORTER_PACK": "Destekçi Paketini Satın Al",
+  "NEWS": "Haberler",
+  "DISABLE_NEWS_PANEL": "Başlangıç penceresindeki Haberler panelini devre dışı bırak",
+  "FAILED_FETCH_NEWS": "Haberler getirilemedi",
+  "CROP_TO_SELECTION": "Seçime göre kırp",
+  "CROP_TO_SELECTION_DESCRIPTIVE": "Resmi seçime göre kırp",
+  "SHOW_CONTEXT_MENU": "Bağlam menüsünü göster",
+  "ERASE": "Sil",
+  "USE_SECONDARY_COLOR": "İkincil rengi kullan",
+  "RIGHT_CLICK_MODE": "Sağ tıklama modu",
+  "ADD_PRIMARY_COLOR_TO_PALETTE": "Birincil rengi palete ekle",
+  "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Birincil rengi mevcut palete ekle",
+  "EXPORT_SAVE_TITLE": "Resmi kaydetmek için bir konum seçin",
+  "BROWSE_DIRECTORY": "Dizine Göz At",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Tüm belgeler kurtarılamadı",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Tüm belgeler kurtarılamadı. Çalışmalarınızı kaydetmede daha iyi olun.",
+  "SEND": "Raporu Gönder",
+  "OPEN_DOCKABLE_MENU": "Sekmeyi Aç",
+  "TIMELINE_TITLE": "Zaman Çizelgesi",
+  "EXPORT_IMAGE_HEADER": "Resim",
+  "EXPORT_ANIMATION_HEADER": "Animasyon",
+  "EXPORT_SPRITESHEET_HEADER": "Spritesheet",
+  "PIXI_FILE": "PixiEditor Dosyaları",
+  "PNG_FILE": "PNG Resimleri",
+  "JPEG_FILE": "JPEG Resimleri",
+  "WEBP_FILE": "WebP Resimleri",
+  "GIF_FILE": "GIF'ler",
+  "BMP_FILE": "BMP Resimleri",
+  "IMAGE_FILES": "Resim Dosyaları",
+  "VIDEO_FILES": "Video Dosyaları",
+  "OPEN_TYPE_FONT": "OpenType Yazı Tipleri",
+  "TRUE_TYPE_FONT": "TrueType Yazı Tipleri",
+  "SVG_FILE": "Ölçeklenebilir Vektör Grafikleri",
+  "MP4_FILE": "MP4 Videoları",
+  "COLUMNS": "Sütunlar",
+  "ROWS": "Satırlar",
+  "BACKGROUND": "Arka Plan",
+  "OPACITY": "Opaklık",
+  "IS_VISIBLE": "Görünür",
+  "CLIP_TO_MEMBER_BELOW": "Alttaki üyeye kırp",
+  "BLEND_MODE": "Karıştırma modu",
+  "MASK": "Maske",
+  "MASK_IS_VISIBLE": "Maske görünür",
+  "OUTPUT": "Çıktı",
+  "INPUT": "Girdi",
+  "NODE_GRAPH_TITLE": "Düğüm Grafiği",
+  "CONTENT": "İçerik",
+  "RADIUS": "Yarıçap",
+  "STROKE_COLOR": "Kontur rengi",
+  "STROKE_WIDTH": "Kontur genişliği",
+  "FILL_COLOR": "Dolgu rengi",
+  "TOP": "Üst",
+  "BOTTOM": "Alt",
+  "CHANNELS_DOCK_TITLE": "Kanallar",
+  "RED": "Kırmızı",
+  "GREEN": "Yeşil",
+  "BLUE": "Mavi",
+  "ALPHA": "Alfa",
+  "COLOR": "Renk",
+  "COORDINATE": "Koordinat",
+  "VECTOR": "Vektör",
+  "MATRIX": "Matris",
+  "TRANSFORMED": "Dönüştürülmüş",
+  "GRAYSCALE": "Gri Tonlama",
+  "CLAMP": "Sıkıştır",
+  "SIZE": "Boyut",
+  "NOISE": "Gürültü",
+  "SCALE": "Ölçek",
+  "SEED": "Tohum",
+  "KERNEL": "Çekirdek",
+  "KERNEL_VIEW_SUM": "Toplam:",
+  "KERNEL_VIEW_SUM_TOOLTIP": "Tüm değerlerin toplamı. Muhtemelen 1 veya 0 değerini hedeflemek istersiniz",
+  "GAIN": "Kazanç",
+  "BIAS": "Eğilim",
+  "TILE_MODE": "Döşeme Modu",
+  "ON_ALPHA": "Alfa Üzerinde",
+  "PIXEL_COORDINATE": "Piksel Koordinatı",
+  "OUTPUT_NODE": "Çıktı",
+  "NOISE_NODE": "Gürültü",
+  "ELLIPSE_NODE": "Elips",
+  "CREATE_IMAGE_NODE": "Resim Oluştur",
+  "FOLDER_NODE": "Klasör",
+  "IMAGE_LAYER_NODE": "Resim Katmanı",
+  "KERNEL_FILTER_NODE": "Çekirdek Filtresi",
+  "MATH_NODE": "Matematik",
+  "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "Matris Dönüşüm Filtresi",
+  "MERGE_NODE": "Birleştir",
+  "MODIFY_IMAGE_LEFT_NODE": "Resmi Değiştirmeye Başla",
+  "MODIFY_IMAGE_RIGHT_NODE": "Resmi Değiştirmeyi Bitir",
+  "MODIFY_IMAGE_PAIR_NODE": "Resmi Değiştir",
+  "COMBINE_CHANNELS_NODE": "Kanalları Birleştir",
+  "COMBINE_COLOR_NODE": "Rengi Birleştir",
+  "COMBINE_VECD_NODE": "Vektörü Birleştir",
+  "COMBINE_VECI_NODE": "Tamsayı Vektörünü Birleştir",
+  "SEPARATE_CHANNELS_NODE": "Kanalları Ayır",
+  "SEPARATE_VECD_NODE": "Vektörü Ayır",
+  "SEPARATE_VECI_NODE": "Tamsayı Vektörünü Ayır",
+  "SEPARATE_COLOR_NODE": "Rengi Ayır",
+  "TIME_NODE": "Zaman",
+  "FILTERS": "Filtreler",
+  "PREVIOUS": "Önceki",
+  "FILL": "Doldur",
+  "MATH_MODE": "Matematik Modu",
+  "NOISE_TYPE": "Gürültü Tipi",
+  "OCTAVES": "Oktavlar",
+  "ACTIVE_FRAME": "Aktif Kare",
+  "NORMALIZED_TIME": "Normalleştirilmiş Zaman",
+  "WITHOUT_FILTERS": "Filtresiz",
+  "RAW_LAYER_OUTPUT": "Ham",
+  "EXAMPLE_FILES": "Örnek Dosyalar",
+  "PROCEDURAL_GENERATION": "Prosedürel Animasyon",
+  "POND_EXAMPLE": "Göl",
+  "TREE_EXAMPLE": "Rüzgarlı Ağaç",
+  "OUTLINE_EXAMPLE": "Otomatik Anahat",
+  "BETA_ANIMATIONS": "Animasyonlar",
+  "SLIME_EXAMPLE": "Animasyonlu Balçık",
+  "SHOW_ALL_EXAMPLES": "Tümünü göster",
+  "APPLY_FILTER_NODE": "Filtre Uygula",
+  "FILTER": "Filtre",
+  "LERP_NODE": "Lerp",
+  "GRAYSCALE_FILTER_NODE": "Gri Tonlama Filtresi",
+  "FROM": "Başlangıç",
+  "TO": "Bitiş",
+  "TIME": "Zaman",
+  "WARMING_UP": "Isınıyor",
+  "RENDERING_FRAME": "Kare Oluşturuluyor {0}/{1}",
+  "RENDERING_VIDEO": "Video Oluşturuluyor",
+  "FINISHED": "Bitti",
+  "GENERATING_SPRITE_SHEET": "Sprite Sayfası Oluşturuluyor",
+  "RENDERING_IMAGE": "Resim Oluşturuluyor",
+  "PROGRESS_POPUP_TITLE": "İlerleme",
+  "POINTS": "Noktalar",
+  "MIN_DISTANCE": "Min. Mesafe",
+  "MAX_POINTS": "Maks. Nokta",
+  "PROBABILITY": "Olasılık",
+  "DISTRIBUTE_POINTS": "Noktaları dağıt",
+  "REMOVE_CLOSE_POINTS": "Yakın noktaları kaldır",
+  "RASTERIZE_SHAPE": "Şekli Rasterleştir",
+  "MODE": "Mod",
+  "Factor": "Faktör",
+  "NORMALIZE": "Normalleştir",
+  "WEIGHT_FACTOR": "Ağırlık",
+  "STARS_EXAMPLE": "Yıldızlar",
+  "ADD_EMPTY_FRAME": "Boş kare ekle",
+  "DUPLICATE_FRAME": "Kareyi çoğalt",
+  "DELETE_FRAME": "Kareyi kaldır",
+  "DEFAULT_MEMBER_NAME": "Yeni Eleman",
+  "NO_PARSER_FOUND": "'{0}' uzantısı için dosya ayrıştırıcı bulunamadı",
+  "SELECT_FILE_FORMAT": "Dosya biçimini seç",
+  "SELECT_FILE_FORMAT_DESCRIPTION": "Aynı biçimde birden fazla dosya türü desteklenmektedir. Lütfen kullanmak istediğinizi seçin.",
+  "NEW_PALETTE_FILE": "palet",
+  "ISLAND_EXAMPLE": "Adalar",
+  "ONION_FRAMES_COUNT": "Soğan katman sayısı",
+  "ONION_OPACITY": "Soğan opaklığı",
+  "TOGGLE_ONION_SKINNING": "Soğan görünümünü aç/kapat",
+  "CHANGE_ACTIVE_FRAME_PREVIOUS": "Aktif kareyi bir öncekine değiştir",
+  "CHANGE_ACTIVE_FRAME_NEXT": "Aktif kareyi bir sonrakine değiştir",
+  "TOGGLE_ANIMATION": "Animasyonu aç/kapat",
+  "NEW_FROM_CLIPBOARD": "Panodan yeni",
+  "OFFSET": "Ofset",
+  "SHAPE": "Şekil",
+  "STRUCTURE": "Yapı",
+  "NUMBERS": "Sayılar",
+  "OPERATIONS": "İşlemler",
+  "GENERATION": "Oluşturma",
+  "NUMBER": "Sayı",
+  "ANIMATION": "Animasyon",
+  "SAMPLE_IMAGE": "Örnek Resim",
+  "POSITION": "Konum",
+  "MATH_ADD": "Topla",
+  "MATH_SUBTRACT": "Çıkar",
+  "MULTIPLY": "Çarp",
+  "DIVIDE": "Böl",
+  "SIN": "Sin",
+  "COS": "Cos",
+  "TAN": "Tan",
+  "GREATER_THAN": "Büyüktür",
+  "LESS_THAN": "Küçüktür",
+  "LESS_THAN_OR_EQUAL": "Küçüktür veya eşittir",
+  "COMPARE": "Karşılaştır",
+  "MATH_POWER": "Üs",
+  "LOGARITHM": "Logaritma",
+  "NATURAL_LOGARITHM": "Doğal logaritma",
+  "ROOT": "Kök",
+  "INVERSE_ROOT": "Ters kök",
+  "FRACTION": "Kesir",
+  "NEGATE": "Olumsuzla",
+  "FLOOR": "Taban",
+  "CEIL": "Tavan",
+  "ROUND": "Yuvarla",
+  "MODULO": "Modulo",
+  "STEP": "Adım",
+  "SMOOTH_STEP": "Yumuşak Adım",
+  "PIXEL_ART_TOOLSET": "Piksel Sanatı",
+  "VECTOR_TOOLSET": "Vektör",
+  "VECTOR_LAYER": "Vektör Katmanı",
+  "STROKE_COLOR_LABEL": "Kontur",
+  "SYNC_WITH_PRIMARY_COLOR_LABEL": "Birincil renkle senkronize et",
+  "RASTERIZE": "Rasterleştir",
+  "RASTERIZE_ACTIVE_LAYER": "Aktif katmanı rasterleştir",
+  "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "Aktif katmanı bir resim (raster) katmanına dönüştür/rasterleştir.",
+  "NEW_ELLIPSE_LAYER_NAME": "Elips",
+  "NEW_RECTANGLE_LAYER_NAME": "Dikdörtgen",
+  "NEW_LINE_LAYER_NAME": "Çizgi",
+  "RENDER_OUTPUT": "Çıktıyı Oluştur",
+  "PAINT_TOOLSET": "Boya",
+  "HARDNESS_SETTING": "Sertlik",
+  "SPACING_SETTING": "Aralık",
+  "ANTI_ALIASING_SETTING": "Kenar Yumuşatma",
+  "TOLERANCE_LABEL": "Tolerans",
+  "TOGGLE_SNAPPING": "Hizalamayı aç/kapat",
+  "HIGH_RES_PREVIEW": "Yüksek Çözünürlüklü Önizleme",
+  "LOW_RES_PREVIEW": "Belge Çözünürlüğünde Önizleme",
+  "TOGGLE_HIGH_RES_PREVIEW": "Yüksek çözünürlüklü önizlemeyi aç/kapat",
+  "FACTOR": "Faktör",
+  "PATH_TOOL": "Yol",
+  "PATH_TOOL_TOOLTIP": "Vektör yolları ve eğrileri oluşturun ({0}).",
+  "PATH_TOOL_ACTION_DISPLAY": "Bir nokta eklemek için tıklayın.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL": "Mevcut bir noktaya tıklayın ve bir eğri yapmak için sürükleyin. Seçmek için bir kontrol noktasına dokunun.",
+  "PATH_TOOL_ACTION_DISPLAY_SHIFT": "Yeni bir katman oluşturmak için tıklayın.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "Seçime eklemek için bir kontrol noktasına dokunun.",
+  "PATH_TOOL_ACTION_DISPLAY_ALT": "Eğrinin sadece bir tarafını ayarlamak için bir kontrol noktasına tıklayın ve hareket ettirin.",
+  "DEFAULT_PATH_LAYER_NAME": "Yol",
+  "DELETE_NODES": "Düğümleri sil",
+  "DELETE_NODES_DESCRIPTIVE": "Seçili düğümleri sil",
+  "DELETE_CELS": "Kareleri sil",
+  "DELETE_CELS_DESCRIPTIVE": "Seçili kareleri sil",
+  "COPY_COLOR_TO_CLIPBOARD": "Rengi panoya kopyala",
+  "VIEWPORT_ROTATION": "Görünüm alanı döndürme",
+  "NEXT_TOOL_SET": "Sonraki araç seti",
+  "PREVIOUS_TOOL_SET": "Önceki araç seti",
+  "FILL_MODE": "Doldurma modu",
+  "USE_LINEAR_SRGB_PROCESSING": "Renkleri işlemek için doğrusal sRGB kullan",
+  "USE_LINEAR_SRGB_PROCESSING_DESC": "Renkleri işlemek için sRGB harmanlama modunu kullanarak belgeyi doğrusal sRGB'ye dönüştürün. Bu, belgenin renklerini etkileyecek, ancak harmanlamayı daha doğru hale getirecektir.",
+  "FILL_TYPE_WINDING": "Sarma",
+  "FILL_TYPE_EVEN_ODD": "Çift Tek",
+  "FILL_TYPE_INVERSE_WINDING": "Ters Sarma",
+  "FILL_TYPE_INVERSE_EVEN_ODD": "Ters Çift Tek",
+  "STROKE_CAP": "Kontür Ucu",
+  "STROKE_JOIN": "Kontür Birleşimi",
+  "COPY_VISIBLE": "Görünürü kopyala",
+  "COPY_VISIBLE_DESCRIPTIVE": "Görünür pikselleri kopyala",
+  "COLOR_SAMPLE_MODE": "Örnek modu",
+  "CREATE_CEL": "Kare oluştur",
+  "CREATE_CEL_DESCRIPTIVE": "Yeni bir kare oluştur",
+  "DUPLICATE_CEL": "Kareyi çoğalt",
+  "DUPLICATE_CEL_DESCRIPTIVE": "Mevcut karede kareyi çoğalt",
+  "RENDER_PREVIEW": "Önizlemeyi oluştur",
+  "OUTPUT_NAME": "Çıktı adı",
+  "CUSTOM_OUTPUT_NODE": "Özel Çıktı",
+  "TOGGLE_HUD": "HUD'u aç/kapat",
+  "OPEN_TIMELINE": "Zaman çizelgesini aç",
+  "OPEN_NODE_GRAPH": "Düğüm grafiğini aç",
+  "TOGGLE_PLAY": "Animasyonu Oynat/Duraklat",
+  "COPY_NODES": "Düğümleri kopyala",
+  "COPY_NODES_DESCRIPTIVE": "Seçili düğümleri kopyala",
+  "PASTE_NODES": "Düğümleri yapıştır",
+  "PASTE_NODES_DESCRIPTIVE": "Kopyalanan düğümleri yapıştır",
+  "COPY_CELS": "Kareleri kopyala",
+  "COPY_CELS_DESCRIPTIVE": "Seçili kareleri kopyala",
+  "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "Soğan zarı görünümünü aç/kapat",
+  "VALUE": "Değer",
+  "TARGET": "Hedef",
+  "EPSILON": "Epsilon",
+  "PRESERVE_ALPHA": "Alfayı koru",
+  "BLUR_FILTER_NODE": "Gauss Bulanıklık Filtresi",
+  "LENGTH": "Uzunluk",
+  "GREATER_THAN_OR_EQUAL": "Büyüktür veya eşittir",
+  "COLOR_NODE": "Renk",
+  "CONVERT_TO_CURVE": "Eğriye dönüştür",
+  "CONVERT_TO_CURVE_DESCRIPTIVE": "Seçili vektör katmanını bir eğriye/yola dönüştür",
+  "FONT_FILES": "Yazı Tipi Dosyaları",
+  "UNIT_PT": "pt",
+  "FONT_LABEL": "Aile",
+  "FONT_SIZE_LABEL": "Boyut",
+  "SPACING_LABEL": "Aralık",
+  "TEXT_TOOL": "Metin",
+  "MISSING_FONT": "Eksik yazı tipi",
+  "TEXT_LAYER_NAME": "Metin",
+  "TEXT_TOOL_TOOLTIP": "Metin oluştur ({0}).",
+  "BOLD_TOOLTIP": "Kalın",
+  "ITALIC_TOOLTIP": "İtalik",
+  "CUSTOM_FONT": "Özel yazı tipi",
+  "DUMP_GPU_DIAGNOSTICS": "GPU tanılamalarını dök",
+  "USE_SRGB_PROCESSING": "Renkleri işlemek için sRGB kullan",
+  "USE_SRGB_PROCESSING_DESC": "Renkleri işlemek için doğrusal sRGB kullanarak belgeyi sRGB'ye dönüştürün. Bu, belgenin renklerini etkileyecektir.",
+  "TEXT_NODE": "Metin",
+  "TEXT_LABEL": "Metin",
+  "TEXT_ON_PATH_NODE": "Yol Üzerinde Metin",
+  "HIGH_DPI_RENDERING": "Yüksek DPI Oluşturma",
+  "THICKNESS": "Kalınlık",
+  "TYPE": "Tür",
+  "EFFECTS": "Efektler",
+  "OUTLINE_NODE": "Anahat",
+  "SHADER_CODE": "Gölgelendirici Kodu",
+  "SHADER_NODE": "Gölgelendirici",
+  "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "Dosya açılamadı",
+  "FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE": "Bu dize harici düzenleyicide düzenlenemedi. Sebep: {0}",
+  "STRING_EDIT_IN_DEFAULT_APP": "Varsayılan uygulamada düzenle",
+  "STRING_OPEN_IN_FOLDER": "Klasörde aç",
+  "DISCO_BALL_EXAMPLE": "Disko Topu",
+  "COLOR_SPACE": "Renk Uzayı",
+  "PHOTO_EXAMPLES": "Fotoğraf",
+  "MASK_EXAMPLE": "Maske",
+  "SHADOW_NODE": "Gölge Filtresi",
+  "INPUT_MATRIX": "Giriş Matrisi",
+  "OUTPUT_MATRIX": "Çıkış Matrisi",
+  "CENTER": "Merkez",
+  "CONTENT_OFFSET": "İçerik Ofseti",
+  "CANVAS_POSITION": "Tuval Konumu",
+  "CENTER_POSITION": "Merkez Konumu",
+  "TILE_MODE_X": "Döşeme Modu X",
+  "TILE_MODE_Y": "Döşeme Modu Y",
+  "TILE_NODE": "Döşeme",
+  "SKEW": "Eğme",
+  "OFFSET_NODE": "Ofset",
+  "SKEW_NODE": "Eğme",
+  "ROTATION_NODE": "Döndürme",
+  "SCALE_NODE": "Ölçek",
+  "ROTATE_NODE": "Döndür",
+  "TRANSFORM_NODE": "Dönüştür",
+  "UNIT": "Birim",
+  "ANGLE": "Açı",
+  "DOCUMENT_INFO_NODE": "Belge Bilgisi",
+  "MASK_NODE": "Maske",
+  "SEPIA_FILTER_NODE": "Sepya Filtresi",
+  "INTENSITY": "Yoğunluk",
+  "INVERT_FILTER_NODE": "Ters Çevirme Filtresi",
+  "COLOR_ADJUSTMENTS_FILTER": "Renk Ayarları Filtresi",
+  "ADJUST_BRIGHTNESS": "Parlaklığı Ayarla",
+  "ADJUST_CONTRAST": "Kontrastı Ayarla",
+  "ADJUST_SATURATION": "Doygunluğu Ayarla",
+  "ADJUST_TEMPERATURE": "Sıcaklığı Ayarla",
+  "ADJUST_TINT": "Rengi Ayarla",
+  "ADJUST_HUE": "Tonu Ayarla",
+  "HUE_VALUE": "Ton",
+  "SATURATION_VALUE": "Doygunluk",
+  "BRIGHTNESS_VALUE": "Parlaklık",
+  "CONTRAST_VALUE": "Kontrast",
+  "TEMPERATURE_VALUE": "Sıcaklık",
+  "TINT_VALUE": "Renk Tonu",
+  "FAILED_DOWNLOADING_UPDATE_TITLE": "Güncelleme indirilemedi",
+  "FAILED_DOWNLOADING_UPDATE": "Güncelleme indirilemedi. Daha sonra tekrar deneyin.",
+  "UNEXPECTED_SHUTDOWN": "Beklenmedik kapanma",
+  "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor beklenmedik bir şekilde kapandı. Dosyalarınızın en son otomatik kaydını yükledik.",
+  "OK": "Tamam",
+  "OPEN_AUTOSAVES": "Otomatik Kayıtlara Göz At",
+  "AUTOSAVE_SETTINGS_HEADER": "Otomatik Kaydetme",
+  "AUTOSAVE_SETTINGS_SAVE_STATE": "Başlangıçta son dosyaları yeniden aç",
+  "AUTOSAVE_SETTINGS_PERIOD": "Otomatik kaydetme periyodu",
+  "AUTOSAVE_ENABLED": "Otomatik kaydetme etkin",
+  "MINUTE_UNIVERSAL": "dk",
+  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Seçili dosyaya otomatik kaydet",
+  "LOAD_LAZY_FILE_MESSAGE": "Başlatma süresini iyileştirmek için, PixiEditor bu dosyayı yüklemedi. Yüklemek için aşağıdaki düğmeye tıklayın.",
+  "EASING_NODE": "Yumuşatma",
+  "EASING_TYPE": "Yumuşatma Türü",
+  "OPEN_DIRECTORY_ON_EXPORT": "Dışa aktarma sırasında dizini aç",
+  "ERROR_LOOP_DETECTED_MESSAGE": "Bu katmanı taşımak bir döngü oluşturacaktır. Düğüm Grafiği'nde düzeltin.",
+  "LINEAR_EASING_TYPE": "Doğrusal",
+  "IN_SINE_EASING_TYPE": "İçeri Sinüs",
+  "OUT_SINE_EASING_TYPE": "Dışarı Sinüs",
+  "IN_OUT_SINE_EASING_TYPE": "İçeri Dışarı Sinüs",
+  "IN_QUAD_EASING_TYPE": "İçeri Karesel",
+  "OUT_QUAD_EASING_TYPE": "Dışarı Karesel",
+  "IN_OUT_QUAD_EASING_TYPE": "İçeri Dışarı Karesel",
+  "IN_CUBIC_EASING_TYPE": "İçeri Kübik",
+  "OUT_CUBIC_EASING_TYPE": "Dışarı Kübik",
+  "IN_OUT_CUBIC_EASING_TYPE": "İçeri Dışarı Kübik",
+  "IN_QUART_EASING_TYPE": "İçeri Dördüncü Dereceden",
+  "OUT_QUART_EASING_TYPE": "Dışarı Dördüncü Dereceden",
+  "IN_OUT_QUART_EASING_TYPE": "İçeri Dışarı Dördüncü Dereceden",
+  "IN_QUINT_EASING_TYPE": "İçeri Beşinci Dereceden",
+  "OUT_QUINT_EASING_TYPE": "Dışarı Beşinci Dereceden",
+  "IN_OUT_QUINT_EASING_TYPE": "İçeri Dışarı Beşinci Dereceden",
+  "IN_EXPO_EASING_TYPE": "İçeri Üstel",
+  "OUT_EXPO_EASING_TYPE": "Dışarı Üstel",
+  "IN_OUT_EXPO_EASING_TYPE": "İçeri Dışarı Üstel",
+  "IN_CIRC_EASING_TYPE": "İçeri Dairesel",
+  "OUT_CIRC_EASING_TYPE": "Dışarı Dairesel",
+  "IN_OUT_CIRC_EASING_TYPE": "İçeri Dışarı Dairesel",
+  "IN_BACK_EASING_TYPE": "İçeri Geri",
+  "OUT_BACK_EASING_TYPE": "Dışarı Geri",
+  "IN_OUT_BACK_EASING_TYPE": "İçeri Dışarı Geri",
+  "IN_ELASTIC_EASING_TYPE": "İçeri Elastik",
+  "OUT_ELASTIC_EASING_TYPE": "Dışarı Elastik",
+  "IN_OUT_ELASTIC_EASING_TYPE": "İçeri Dışarı Elastik",
+  "IN_BOUNCE_EASING_TYPE": "İçeri Sıçrama",
+  "OUT_BOUNCE_EASING_TYPE": "Dışarı Sıçrama",
+  "IN_OUT_BOUNCE_EASING_TYPE": "İçeri Dışarı Sıçrama",
+  "CLAMP_SHADER_TILE_NODE": "Sıkıştır",
+  "REPEAT_SHADER_TILE_NODE": "Tekrarla",
+  "MIRROR_SHADER_TILE_NODE": "Ayna",
+  "DECAL_SHADER_TILE_NODE": "Çıkartma",
+  "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
+  "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
+  "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
+  "COLOR_MANAGED_COLOR_SAMPLE_MODE": "Renk Yönetimli",
+  "RAW_COLOR_SAMPLE_MODE": "Ham",
+  "FRACTAL_PERLIN_NOISE_TYPE": "Perlin",
+  "TURBULENCE_PERLIN_NOISE_TYPE": "Türbülans",
+  "INHERIT_COLOR_SPACE_TYPE": "Miras Al",
+  "SRGB_COLOR_SPACE_TYPE": "sRGB",
+  "LINEAR_SRGB_COLOR_SPACE_TYPE": "Doğrusal sRGB",
+  "SIMPLE_OUTLINE_TYPE": "Basit",
+  "GAUSSIAN_OUTLINE_TYPE": "Gauss",
+  "PIXEL_PERFECT_OUTLINE_TYPE": "Piksel Mükemmel",
+  "DEGREES_ROTATION_TYPE": "Derece",
+  "RADIANS_ROTATION_TYPE": "Radyan",
+  "WEIGHTED_GRAYSCALE_MODE": "Ağırlıklı",
+  "AVERAGE_GRAYSCALE_MODE": "Ortalama",
+  "CUSTOM_GRAYSCALE_MODE": "Özel",
+  "CLAMP_TILE_MODE": "Sıkıştır",
+  "REPEAT_TILE_MODE": "Tekrarla",
+  "MIRROR_TILE_MODE": "Ayna",
+  "DECAL_TILE_MODE": "Çıkartma",
+  "ERR_UNKNOWN_FILE_FORMAT": "Bilinmeyen dosya biçimi",
+  "ERR_EXPORT_SIZE_INVALID": "Geçersiz dışa aktarma boyutu. Değerler 0'dan büyük olmalıdır.",
+  "ERR_UNKNOWN_IMG_FORMAT": "Bilinmeyen resim biçimi '{0}'.",
+  "ERR_FAILED_GENERATE_SPRITE_SHEET": "Spritesheet oluşturulamadı",
+  "ERR_NO_RENDERER": "Animasyon oluşturucu bulunamadı.",
+  "ERR_RENDERING_FAILED": "Oluşturma başarısız oldu",
+  "ENABLE_ANALYTICS": "Anonim analiz gönder",
+  "ANALYTICS_INFO": "PixiEditor'ı geliştirmek için anonim kullanım verileri topluyoruz. Hiçbir kişisel veri toplanmaz.",
+  "LANGUAGE_INFO": "Tüm çeviriler topluluk tarafından yapılmaktadır. Daha fazla bilgi için Discord sunucumuza katılın.",
+  "UP_TO_DATE_UNKNOWN": "Güncellemeler kontrol edilemedi",
+  "UP_TO_DATE": "PixiEditor güncel",
+  "UPDATE_AVAILABLE": "{0} güncellemesi mevcut",
+  "CHECKING_UPDATES": "Güncellemeler kontrol ediliyor...",
+  "UPDATE_FAILED_DOWNLOAD": "Güncelleme indirilemedi",
+  "UPDATE_READY_TO_INSTALL": "Güncelleme hazır. {0} sürümüne geçilsin mi?",
+  "SWITCH_TO_NEW_VERSION": "Geç",
+  "DOWNLOAD_UPDATE": "İndir",
+  "DOWNLOADING_UPDATE": "Güncelleme indiriliyor...",
+  "CHECKING_FOR_UPDATES": "Güncellemeler kontrol ediliyor...",
+  "PAINT_SHAPE_SETTING": "Fırça şekli",
+  "BOOL_OPERATION_NODE": "Boole İşlemi",
+  "FIRST_SHAPE": "İlk şekil",
+  "SECOND_SHAPE": "İkinci şekil",
+  "OPERATION": "İşlem",
+  "UNION_VECTOR_PATH_OP": "Birleşim",
+  "DIFFERENCE_VECTOR_PATH_OP": "Fark",
+  "INTERSECT_VECTOR_PATH_OP": "Kesişim",
+  "XOR_VECTOR_PATH_OP": "XOR",
+  "REVERSE_DIFFERENCE_VECTOR_PATH_OP": "Ters Fark",
+  "NO_DOCUMENT_OPEN": "Burada hiçbir şey yok",
+  "EMPTY_DOCUMENT_ACTION_BTN": "Oluşturmaya başla",
+  "ONBOARDING_TITLE": "Hoş geldiniz",
+  "ONBOARDING_DESCRIPTION": "Çalışma alanınızı ayarlayalım!",
+  "ONBOARDING_SKIP_BTN": "Atla",
+  "ONBOARDING_ACTION_BTN": "Hadi başlayalım",
+  "ONB_SELECT_PRIMARY_TOOLSET": "Birincil Araç Setinizi Seçin",
+  "ONB_NEXT_BTN": "İleri",
+  "ONB_FINISH_BTN": "Bitir",
+  "ONB_BACK_BTN": "Önceki",
+  "ONB_ANALYTICS": "Anonim Analiz",
+  "ONB_ALL_SET": "Her şey hazır!",
+  "ONB_ALL_SET_BTN": "Oluşturmaya başla",
+  "ANALYTICS_INFO_DETAILED": "PixiEditor, uygulamayı geliştirmek için anonim kullanım verileri toplar. Veriler hiçbir kişisel bilgi içermez. Diğer şeylerin yanı sıra, PixiEditor şunları izler:\n- Hangi araçların nasıl kullanıldığı\n- Uygulamayı ne kadar süre kullandığınız\n- Hangi komutların kullanıldığı\n- Performans verileri\n\n Ayarlardan istediğiniz zaman analizlerden çıkabilirsiniz.",
+  "PRIVACY_POLICY": "Gizlilik Politikası",
+  "ONB_SHORTCUTS": "Kısayollarınızı Seçin",
+  "GRAPH_STATE_UNABLE_TO_CREATE_MEMBER": "Mevcut Düğüm Grafiği kurulumu, seçilenin yanında yeni bir katman oluşturulmasına izin vermiyor.",
+  "PRIMARY_TOOLSET": "Birincil Araç Seti",
+  "OPEN_ONBOARDING_WINDOW": "Alıştırma penceresini aç",
+  "USER_NOT_FOUND": "Lütfen Founder's Edition'ı satın alırken kullandığınız e-postayı girin.",
+  "SESSION_NOT_VALID": "Oturum geçerli değil, lütfen tekrar giriş yapın",
+  "SESSION_NOT_FOUND": "Oturum bulunamadı, tekrar giriş yapmayı deneyin",
+  "INTERNAL_SERVER_ERROR": "Dahili bir sunucu hatası oluştu. Lütfen daha sonra tekrar deneyin.",
+  "TOO_MANY_REQUESTS": "Çok fazla istek. {0} saniye içinde tekrar deneyin.",
+  "SESSION_EXPIRED": "Oturum süresi doldu. Lütfen tekrar giriş yapın.",
+  "CONNECTION_ERROR": "Bağlantı hatası. Lütfen internet bağlantınızı kontrol edin.",
+  "FAIL_LOAD_USER_DATA": "Kaydedilmiş kullanıcı verileri yüklenemedi",
+  "LOGOUT": "Çıkış Yap",
+  "LOGGED_IN_AS": "Merhaba",
+  "EMAIL_SENT": "E-posta gönderildi! Gelen kutunuzu kontrol edin.",
+  "RESEND_ACTIVATION": "Yeniden Gönder",
+  "INVALID_TOKEN": "Oturum geçersiz veya süresi dolmuş. Lütfen tekrar giriş yapın.",
+  "ENTER_EMAIL": "E-postanızı girin",
+  "LOGIN_LINK": "Giriş Bağlantısı Gönder",
+  "LOGIN_LINK_INFO": "Giriş yapmanız için size güvenli bir bağlantı e-postayla göndereceğiz. Şifre gerekmez.",
+  "ACCOUNT_WINDOW_TITLE": "Hesap",
+  "CONNECTION_TIMEOUT": "Bağlantı zaman aşımına uğradı. Lütfen tekrar deneyin.",
+  "OPEN_ACCOUNT_WINDOW": "Hesabı Yönet",
+  "AUTO_SCALE_BACKGROUND": "Arka planı otomatik ölçeklendir",
+  "UPDATES": "Güncellemeler",
+  "SCENE": "Sahne",
+  "CUSTOM_BACKGROUND_SCALE": "Özel arka plan ölçeği",
+  "PRIMARY_BG_COLOR": "Birincil arka plan rengi",
+  "SECONDARY_BG_COLOR": "İkincil arka plan rengi",
+  "RESET": "Sıfırla",
+  "INSTALL": "Yükle",
+  "MANAGE_ACCOUNT": "Yönet",
+  "OWNED_PRODUCTS": "Sahip Olunan İçerik",
+  "INSTALLING": "Yükleniyor",
+  "INSTALLED": "Yüklendi",
+  "ACCOUNT_PROVIDER_INFO": "Hesap şunun tarafından yönetiliyor",
+  "UPDATE": "Güncelle",
+  "AUTOSAVE_OPEN_FOLDER": "Otomatik kaydetme klasörünü aç",
+  "AUTOSAVE_OPEN_FOLDER_DESCRIPTIVE": "Otomatik kayıtların saklandığı klasörü aç",
+  "AUTOSAVE_TOGGLE_DESCRIPTIVE": "Otomatik kaydetmeyi etkinleştir/devre dışı bırak",
+  "FOUNDERS_BUNDLE": "Kurucu Paketi",
+  "FOUNDERS_BUNDLE_SUBTEXT": "PixiEditor'ı destekleyin ve üretkenliğinizi artırın!",
+  "BECOME_A_FOUNDER": "Kurucu Ol",
+  "LOGIN": "Giriş Yap",
+  "NOT_FOUNDER_YET": "Henüz Kurucu değil misiniz?",
+  "ERROR_GRAPH": "Grafik kurulumu bir hata üretti. Düğüm grafiğinde düzeltin",
+  "COLOR_MATRIX_FILTER_NODE": "Renk Matris Filtresi",
+  "WORKSPACE": "Çalışma Alanı",
+  "EXPORT_ZONE_NODE": "Dışa Aktarma Bölgesi",
+  "IS_DEFAULT_EXPORT": "Varsayılan Dışa Aktarma mı",
+  "EXPORT_OUTPUT": "Dışa Aktarma Çıktısı",
+  "RENDER_OUTPUT_SIZE": "Oluşturma Çıktı Boyutu",
+  "RENDER_OUTPUT_CENTER": "Oluşturma Çıktı Merkezi",
+  "COLOR_PICKER": "Renk Seçici",
+  "UNAUTHORIZED_ACCESS": "Yetkisiz erişim",
+  "SEPARATE_SHAPES": "Şekilleri Ayır",
+  "SEPARATE_SHAPES_DESCRIPTIVE": "Mevcut vektörden şekilleri ayrı katmanlara ayır",
+  "TEXT": "Metin",
+  "EXTRACT_SELECTED_TEXT": "Seçili metni çıkar",
+  "EXTRACT_SELECTED_TEXT_DESCRIPTIVE": "Seçili metni yeni katmana çıkar.",
+  "EXTRACT_SELECTED_CHARACTERS": "Seçili karakterleri çıkar",
+  "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE": "Seçimden bireysel karakterleri yeni katmanlara çıkar.",
+  "STEP_START": "En yakın kareye geri adım at",
+  "STEP_END": "En yakın kareye ileri adım at",
+  "STEP_FORWARD": "Bir kare ileri adım at",
+  "STEP_BACK": "Bir kare geri adım at",
+  "ANIMATION_QUALITY_PRESET": "Kalite Önayarı",
+  "VERY_LOW_QUALITY_PRESET": "Çok Düşük",
+  "LOW_QUALITY_PRESET": "Düşük",
+  "MEDIUM_QUALITY_PRESET": "Orta",
+  "HIGH_QUALITY_PRESET": "Yüksek",
+  "VERY_HIGH_QUALITY_PRESET": "Çok Yüksek",
+  "EXPORT_FRAMES": "Kareleri Dışa Aktar",
+  "NORMALIZE_OFFSET": "Ofseti Normalleştir",
+  "TANGENT": "Teğet",
+  "EVALUATE_PATH_NODE": "Yolu Değerlendir",
+  "OLD_MIN": "Eski Min",
+  "OLD_MAX": "Eski Maks",
+  "NEW_MIN": "Yeni Min",
+  "NEW_MAX": "Yeni Maks",
+  "REMAP_NODE": "Yeniden Haritala",
+  "TEXT_TOOL_ACTION_DISPLAY": "Yeni bir metin eklemek için tuvale tıklayın (boyutu ayarlamak için tıklarken sürükleyin). Düzenlemek için mevcut metne tıklayın.",
+  "PASTE_CELS": "Kareleri yapıştır",
+  "SCALE_X": "Ölçek X",
+  "SCALE_Y": "Ölçek Y",
+  "TRANSLATE_X": "Öteleme X",
+  "TRANSLATE_Y": "Öteleme Y",
+  "SKEW_X": "Eğme X",
+  "SKEW_Y": "Eğme Y",
+  "PERSPECTIVE_0": "Perspektif 0",
+  "PERSPECTIVE_1": "Perspektif 1",
+  "PERSPECTIVE_2": "Perspektif 2",
+  "COMPOSE_MATRIX": "Matris Oluştur",
+  "DECOMPOSE_MATRIX": "Matrisi Ayrıştır",
+  "NORMALIZE_COORDINATES": "Koordinatları Normalleştir",
+  "TRANSFORMED_POSITION": "Dönüştürülmüş Konum",
+  "ACCOUNT_PROVIDER_NOT_AVAILABLE": "PixiEditor'ın bu yapısı hesapları desteklemiyor. Hesabınızı yönetmek için pixieditor.net'teki resmi yapıyı kullanın.",
+  "STEAM_OFFLINE": "Hesap doğrulanamıyor. Steam çevrimdışı. Steam istemcisinin çalıştığından ve giriş yaptığınızdan emin olun.",
+  "OPEN_COMMAND_DEBUG_WINDOW": "Komut hata ayıklama penceresini aç"
+}

+ 10 - 3
src/PixiEditor/Data/Localization/LocalizationData.json

@@ -27,7 +27,7 @@
       "code": "es",
       "localeFileName": "es.json",
       "iconFileName": "es.png",
-      "lastUpdated": "2023-05-17 16:46:19"
+      "lastUpdated": "2025-09-07 02:23:00"
     },
     {
       "name": "中文",
@@ -42,7 +42,7 @@
       "code": "ru",
       "localeFileName": "ru.json",
       "iconFileName": "ru.png",
-      "lastUpdated": "2025-06-30 11:13:26"
+      "lastUpdated": "2025-07-28 08:51:44"
     },
     {
       "name": "Українська",
@@ -57,7 +57,7 @@
       "localeFileName": "ar.json",
       "iconFileName": "ar.png",
       "rightToLeft": true,
-      "lastUpdated": "2025-06-04 18:20:46"
+      "lastUpdated": "2025-09-06 22:22:52"
     },
     {
       "name": "Čeština",
@@ -79,6 +79,13 @@
       "localeFileName": "hu.json",
       "iconFileName": "hu.png",
       "lastUpdated": "2023-05-08 22:07:37"
+    },
+    {
+      "name": "Türkçe",
+      "code": "tr",
+      "localeFileName": "tr.json",
+      "iconFileName": "tr.png",
+      "lastUpdated": "2025-08-07 18:24:00"
     }
   ]
 }

+ 27 - 17
src/PixiEditor/Helpers/Converters/EnumToLocalizedStringConverter.cs

@@ -1,4 +1,6 @@
-using System.Globalization;
+using System.Diagnostics;
+using System.Globalization;
+using System.Reflection;
 using PixiEditor.Extensions.Helpers;
 using PixiEditor.UI.Common.Localization;
 
@@ -6,30 +8,38 @@ namespace PixiEditor.Helpers.Converters;
 
 internal class EnumToLocalizedStringConverter : SingleInstanceConverter<EnumToLocalizedStringConverter>
 {
+    private Dictionary<object, string> enumTranslations = new(
+        typeof(EnumToLocalizedStringConverter).Assembly
+            .GetCustomAttributes()
+            .OfType<ILocalizeEnumInfo>()
+            .Select(x => new KeyValuePair<object, string>(x.GetEnumValue(), x.LocalizationKey)));
+    
     public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
     {
-        if (value is Enum enumValue)
+        if (value is not Enum enumValue)
         {
-            if (EnumHelpers.HasDescription(enumValue))
-            {
-                return EnumHelpers.GetDescription(enumValue);
-            }
+            return value;
+        }
+
+        if (enumTranslations.TryGetValue(enumValue, out var assemblyDefinedKey))
+        {
+            return assemblyDefinedKey;
+        }
 
-            return ToLocalizedStringFormat(enumValue);
+        if (EnumHelpers.HasDescription(enumValue))
+        {
+            return EnumHelpers.GetDescription(enumValue);
         }
 
-        return value;
+        ThrowUntranslatedEnumValue(enumValue);
+        return enumValue;
     }
 
-    private string ToLocalizedStringFormat(Enum enumValue)
+    [Conditional("DEBUG")]
+    private static void ThrowUntranslatedEnumValue(object value)
     {
-        // VALUE_ENUMTYPE
-        // for example BlendMode.Normal becomes NORMAL_BLEND_MODE
-
-        string enumType = enumValue.GetType().Name;
-
-        string value = enumValue.ToString();
-
-        return $"{value.ToSnakeCase()}_{enumType.ToSnakeCase()}".ToUpper();
+        throw new ArgumentException(
+            $"Enum value '{value.GetType()}.{value}' has no value defined. Either add a Description attribute to the enum values or a LocalizeEnum attribute in EnumTranslations.cs for third party enums",
+            nameof(value));
     }
 }

+ 0 - 19
src/PixiEditor/Helpers/EnumDescriptionConverter.cs

@@ -1,19 +0,0 @@
-using System.Globalization;
-using PixiEditor.Extensions.Helpers;
-using PixiEditor.Helpers.Converters;
-using PixiEditor.UI.Common.Localization;
-
-namespace PixiEditor.Helpers;
-
-internal class EnumDescriptionConverter : SingleInstanceConverter<EnumDescriptionConverter>
-{
-    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-    {
-        if (value is Enum enumValue)
-        {
-            return EnumHelpers.GetDescription(enumValue);
-        }
-
-        return value;
-    }
-}

+ 135 - 1
src/PixiEditor/Helpers/Extensions/EnumerableExtensions.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Buffers;
+using System.Collections.Generic;
 
 namespace PixiEditor.Helpers.Extensions;
 
@@ -95,4 +96,137 @@ internal static class EnumerableExtensions
 
         return IndexOrNext(collection, predicate, index, false);
     }
+
+    public static T IndexOrNextInDirection<T>(this IEnumerable<T> collection, Predicate<T> predicate, int index, NextToDirection direction, bool overrun = true) => direction switch
+    {
+        NextToDirection.Forwards => IndexOrNext(collection, predicate, index, overrun),
+        NextToDirection.Backwards => IndexOrPrevious(collection, predicate, index, overrun),
+        _ => throw new ArgumentOutOfRangeException(nameof(direction)),
+    };
+    
+    /// <summary>
+    /// Returns the element that comes immediately after the specified <paramref name="index"/> 
+    /// in the given <paramref name="enumerable"/>, wrapping around to the first element if 
+    /// the end of the sequence is reached.
+    /// </summary>
+    /// <typeparam name="T">The type of the elements in the enumerable.</typeparam>
+    /// <param name="enumerable">The source enumerable.</param>
+    /// <param name="index">The index of the reference element. Must be non-negative.</param>
+    /// <returns>
+    /// The element immediately after the specified index, or the first element if the index 
+    /// refers to the last element. Returns <c>default</c> if the enumerable is empty.
+    /// </returns>
+    /// <remarks>
+    /// This method does not check whether the specified <paramref name="index"/> is within the 
+    /// bounds of the enumerable's size. Passing a positive out-of-range index may yield unexpected results.
+    /// </remarks>
+    /// <exception cref="ArgumentNullException">Thrown if <paramref name="enumerable"/> is <c>null</c>.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is negative.</exception>
+    public static T? WrapNextAfterIndex<T>(this IEnumerable<T> enumerable, int index)
+    {
+        ArgumentOutOfRangeException.ThrowIfNegative(index);
+        ArgumentNullException.ThrowIfNull(enumerable);
+
+        switch (enumerable)
+        {
+            case ICollection<T> collection:
+                return NextWithKnownCount(collection, index, collection.Count);
+            case IReadOnlyCollection<T> readOnlyCollection:
+                return NextWithKnownCount(readOnlyCollection, index, readOnlyCollection.Count);
+        }
+
+        using var enumerator = enumerable.GetEnumerator();
+
+        // If the enumerable is empty, return null
+        if (!enumerator.MoveNext())
+            return default;
+
+        var steps = index + 1;
+        var firstElement = enumerator.Current;
+
+        while (steps-- > 0)
+        {
+            if (!enumerator.MoveNext())
+                return firstElement;
+        }
+        
+        return enumerator.Current;
+    }
+
+    /// <summary>
+    /// Returns the element that comes immediately before the specified <paramref name="index"/> 
+    /// in the given <paramref name="enumerable"/>, wrapping around to the last element if 
+    /// the start of the sequence is reached.
+    /// </summary>
+    /// <typeparam name="T">The type of the elements in the enumerable.</typeparam>
+    /// <param name="enumerable">The source enumerable.</param>
+    /// <param name="index">The index of the reference element. Must be non-negative.</param>
+    /// <returns>
+    /// The element immediately before the specified index, or the last element if the index 
+    /// is <c>0</c>. Returns <c>default</c> if the enumerable is empty.
+    /// </returns>
+    /// <remarks>
+    /// This method does not check whether the specified <paramref name="index"/> is within the 
+    /// bounds of the enumerable's size. Passing a positive out-of-range index may yield unexpected results.
+    /// </remarks>
+    /// <exception cref="ArgumentNullException">Thrown if <paramref name="enumerable"/> is <c>null</c>.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is negative.</exception>
+    public static T? WrapPreviousBeforeIndex<T>(this IEnumerable<T> enumerable, int index)
+    {
+        ArgumentOutOfRangeException.ThrowIfNegative(index);
+        ArgumentNullException.ThrowIfNull(enumerable);
+        
+        return index == 0
+            ? enumerable.LastOrDefault()
+            : enumerable.ElementAtOrDefault(index - 1);
+    }
+
+    /// <summary>
+    /// Returns the element next to the specified <paramref name="index"/> in the given 
+    /// <paramref name="enumerable"/>, in the direction specified by <paramref name="direction"/>, 
+    /// wrapping around if necessary.
+    /// </summary>
+    /// <typeparam name="T">The type of the elements in the enumerable.</typeparam>
+    /// <param name="enumerable">The source enumerable.</param>
+    /// <param name="index">The index of the reference element. Must be non-negative.</param>
+    /// <param name="direction">
+    /// The direction in which to look for the next element (forwards or backwards).
+    /// </param>
+    /// <returns>
+    /// The element next to the specified index in the chosen direction, with wrap-around behavior.
+    /// Returns <c>default</c> if the enumerable is empty.
+    /// </returns>
+    /// <remarks>
+    /// This method does not check whether the specified <paramref name="index"/> is within the 
+    /// bounds of the enumerable's size. Passing a positive out-of-range index may yield unexpected results.
+    /// </remarks>
+    /// <exception cref="ArgumentNullException">Thrown if <paramref name="enumerable"/> is <c>null</c>.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is negative.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="direction"/> is not a valid <see cref="NextToDirection"/> value.</exception>
+    public static T? WrapInDirectionOfIndex<T>(this IEnumerable<T> enumerable, int index, NextToDirection direction) =>
+        direction switch
+        {
+            NextToDirection.Forwards => WrapNextAfterIndex(enumerable, index),
+            NextToDirection.Backwards => WrapPreviousBeforeIndex(enumerable, index),
+            _ => throw new ArgumentOutOfRangeException(nameof(direction)),
+        };
+
+    private static T? NextWithKnownCount<T>(IEnumerable<T> collection, int index, int count)
+    {
+        if (count == 0)
+            return default;
+        
+        var newIndex = index + 1;
+
+        if (newIndex < 0 || newIndex >= count)
+            newIndex = newIndex < 0 ? count - 1 : 0;
+        
+        return collection.ElementAtOrDefault(newIndex);
+    }
+}
+
+enum NextToDirection
+{
+    Forwards = 1,
+    Backwards = -1
 }

+ 18 - 0
src/PixiEditor/Helpers/LocalizeEnumAttribute.cs

@@ -0,0 +1,18 @@
+namespace PixiEditor.Helpers;
+
+public interface ILocalizeEnumInfo
+{
+    public object GetEnumValue();
+    
+    public string LocalizationKey { get; }
+}
+
+[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+public class LocalizeEnumAttribute<T>(T value, string key) : Attribute, ILocalizeEnumInfo where T : Enum
+{
+    public T Value { get; } = value;
+
+    object ILocalizeEnumInfo.GetEnumValue() => Value;
+    
+    public string LocalizationKey { get; } = key;
+}

+ 26 - 7
src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using Drawie.Backend.Core.Bridge;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Handlers;
@@ -104,7 +105,8 @@ internal class ActionAccumulator
                 queuedActions = new();
 
                 List<IChangeInfo?> changes;
-                if (AreAllPassthrough(toExecute))
+                bool allPassthrough = AreAllPassthrough(toExecute);
+                if (allPassthrough)
                 {
                     changes = toExecute.Select(a => (IChangeInfo?)a.action).ToList();
                 }
@@ -119,6 +121,8 @@ internal class ActionAccumulator
                         action.action is ChangeBoundary_Action or Redo_Action or Undo_Action);
                 bool viewportRefreshRequest =
                     toExecute.Any(static action => action.action is RefreshViewport_PassthroughAction);
+                bool refreshPreviewsRequest =
+                    toExecute.Any(static action => action.action is RefreshPreviews_PassthroughAction);
                 bool changeFrameRequest =
                     toExecute.Any(static action => action.action is SetActiveFrame_PassthroughAction);
 
@@ -130,10 +134,12 @@ internal class ActionAccumulator
                 if (undoBoundaryPassed)
                     internals.Updater.AfterUndoBoundaryPassed();
 
+
                 var affectedAreas = new AffectedAreasGatherer(document.AnimationHandler.ActiveFrameTime,
                     internals.Tracker,
-                    optimizedChanges);
-                if (DrawingBackendApi.Current.IsHardwareAccelerated)
+                    optimizedChanges, refreshPreviewsRequest);
+
+                if (DrawingBackendApi.Current.IsHardwareAccelerated && !allPassthrough)
                 {
                     canvasUpdater.UpdateGatheredChunksSync(affectedAreas,
                         undoBoundaryPassed || viewportRefreshRequest);
@@ -144,10 +150,21 @@ internal class ActionAccumulator
                         undoBoundaryPassed || viewportRefreshRequest);
                 }*/
 
-                previewUpdater.UpdatePreviews(
-                    affectedAreas.ChangedMembers,
-                    affectedAreas.ChangedMasks,
-                    affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames);
+                bool previewsDisabled = PixiEditorSettings.Performance.DisablePreviews.Value;
+
+                if (!previewsDisabled)
+                {
+                    if (undoBoundaryPassed || viewportRefreshRequest || changeFrameRequest ||
+                        document.SizeBindable.LongestAxis <= LiveUpdatePerformanceThreshold)
+                    {
+                        previewUpdater.UpdatePreviews(
+                            affectedAreas.ChangedMembers,
+                            affectedAreas.ChangedMasks,
+                            affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames,
+                            affectedAreas.IgnoreAnimationPreviews,
+                            undoBoundaryPassed || refreshPreviewsRequest);
+                    }
+                }
 
                 // force refresh viewports for better responsiveness
                 foreach (var (_, value) in internals.State.Viewports)
@@ -175,6 +192,8 @@ internal class ActionAccumulator
         executing = false;
     }
 
+    private const int LiveUpdatePerformanceThreshold = 2048;
+
     private bool AreAllPassthrough(List<(ActionSource, IAction)> actions)
     {
         foreach (var action in actions)

+ 2 - 1
src/PixiEditor/Models/DocumentModels/DocumentStructureHelper.cs

@@ -8,6 +8,7 @@ using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Layers;
 using PixiEditor.UI.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.Models.DocumentModels;
 #nullable enable
@@ -34,7 +35,7 @@ internal class DocumentStructureHelper
                     count++;
             }
 
-            return true;
+            return Traverse.Further;
         });
         return $"{name} {count}";
     }

+ 9 - 0
src/PixiEditor/Models/DocumentModels/DocumentTransformMode.cs

@@ -3,12 +3,21 @@
 namespace PixiEditor.Models.DocumentModels;
 internal enum DocumentTransformMode
 {
+    // Comments show localization in DocumentTransformViewModel.cs, comments are needed for localization key check pipeline
+    
+    // TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE
     [Description("SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE")]
     Scale_NoRotate_NoShear_NoPerspective,
+    
+    // TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE
     [Description("SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE")]
     Scale_Rotate_NoShear_NoPerspective,
+    
+    // TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE
     [Description("SCALE_ROTATE_SHEAR_NOPERSPECTIVE")]
     Scale_Rotate_Shear_NoPerspective,
+    
+    // TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE
     [Description("SCALE_ROTATE_SHEAR_PERSPECTIVE")]
     Scale_Rotate_Shear_Perspective
 }

+ 3 - 2
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -21,6 +21,7 @@ using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.Models.DocumentModels.Public;
 #nullable enable
@@ -617,10 +618,10 @@ internal class DocumentOperationsModule : IDocumentOperations
             if (!members.Contains(traversedNode.Id))
             {
                 parent = traversedNode;
-                return false;
+                return Traverse.Exit;
             }
 
-            return true;
+            return Traverse.Further;
         });
 
         if (parent is null)

+ 16 - 15
src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
+using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.Models.DocumentModels.Public;
 #nullable enable
@@ -45,10 +46,10 @@ internal class DocumentStructureModule
             if (!guids.Contains(traversedNode.Id) && traversedNode is IStructureMemberHandler)
             {
                 parent = traversedNode;
-                return false;
+                return Traverse.Exit;
             }
 
-            return true;
+            return Traverse.Further;
         });
 
         if (parent is null)
@@ -62,10 +63,10 @@ internal class DocumentStructureModule
                 if (!guids.Contains(traversedNode.Id) && traversedNode is IStructureMemberHandler)
                 {
                     parent = traversedNode;
-                    return false;
+                    return Traverse.Exit;
                 }
 
-                return true;
+                return Traverse.Further;
             });
         }
 
@@ -110,7 +111,7 @@ internal class DocumentStructureModule
         {
             if (node is IStructureMemberHandler parent && input is { PropertyName: FolderNode.ContentInternalName })
                 parents.Add(parent);
-            return true;
+            return Traverse.Further;
         });
 
         return parents;
@@ -187,7 +188,7 @@ internal class DocumentStructureModule
                 toFill.Add(strNode);
             }
 
-            return true;
+            return Traverse.Further;
         });
     }
 
@@ -197,10 +198,10 @@ internal class DocumentStructureModule
         startNode.TraverseForwards(node =>
         {
             if (node == startNode)
-                return true;
+                return Traverse.Further;
 
             result = node;
-            return false;
+            return Traverse.Exit;
         });
 
         return result;
@@ -218,13 +219,13 @@ internal class DocumentStructureModule
             if (node != member && node is IStructureMemberHandler structureMemberNode)
             {
                 if (node is IFolderHandler && !includeFolders)
-                    return true;
+                    return Traverse.Further;
 
                 result = structureMemberNode;
-                return false;
+                return Traverse.Exit;
             }
 
-            return true;
+            return Traverse.Further;
         });
 
         return result;
@@ -242,13 +243,13 @@ internal class DocumentStructureModule
             if (node != member && node is IStructureMemberHandler structureMemberNode)
             {
                 if (node is IFolderHandler && !includeFolders)
-                    return true;
+                    return Traverse.Further;
 
                 result = structureMemberNode;
-                return false;
+                return Traverse.Exit;
             }
 
-            return true;
+            return Traverse.Further;
         });
 
         return result;
@@ -268,7 +269,7 @@ internal class DocumentStructureModule
             if (node is IStructureMemberHandler structureMemberNode)
                 children.Add(structureMemberNode);
 
-            return true;
+            return Traverse.Further;
         });
 
         return children;

+ 7 - 0
src/PixiEditor/Models/DocumentPassthroughActions/RefreshPreviews_PassthroughAction.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Actions;
+using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.DocumentPassthroughActions;
+
+internal record class RefreshPreviews_PassthroughAction() : IAction, IChangeInfo;

+ 126 - 0
src/PixiEditor/Models/EnumTranslations.cs

@@ -0,0 +1,126 @@
+using Drawie.Backend.Core.Shaders.Generation;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Vector;
+using PixiEditor.AnimationRenderer.Core;
+using PixiEditor.ChangeableDocument.Changeables.Graph.ColorSpaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Effects;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Handlers.Toolbars;
+using PixiEditor.Models.Tools;
+using PixiEditor.Views.Overlays.BrushShapeOverlay;
+using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
+
+[assembly: LocalizeEnum<StrokeCap>(StrokeCap.Butt, "BUTT_STROKE_CAP")]
+[assembly: LocalizeEnum<StrokeCap>(StrokeCap.Round, "ROUND_STROKE_CAP")]
+[assembly: LocalizeEnum<StrokeCap>(StrokeCap.Square, "SQUARE_STROKE_CAP")]
+
+[assembly: LocalizeEnum<StrokeJoin>(StrokeJoin.Bevel, "BEVEL_STROKE_JOIN")]
+[assembly: LocalizeEnum<StrokeJoin>(StrokeJoin.Round, "ROUND_STROKE_JOIN")]
+[assembly: LocalizeEnum<StrokeJoin>(StrokeJoin.Miter, "MITER_STROKE_JOIN")]
+
+[assembly: LocalizeEnum<GrayscaleNode.GrayscaleMode>(GrayscaleNode.GrayscaleMode.Weighted, "WEIGHTED_GRAYSCALE_MODE")]
+[assembly: LocalizeEnum<GrayscaleNode.GrayscaleMode>(GrayscaleNode.GrayscaleMode.Average, "AVERAGE_GRAYSCALE_MODE")]
+[assembly: LocalizeEnum<GrayscaleNode.GrayscaleMode>(GrayscaleNode.GrayscaleMode.Custom, "CUSTOM_GRAYSCALE_MODE")]
+
+[assembly: LocalizeEnum<ColorSampleMode>(ColorSampleMode.ColorManaged, "COLOR_MANAGED_COLOR_SAMPLE_MODE")]
+[assembly: LocalizeEnum<ColorSampleMode>(ColorSampleMode.Raw, "RAW_COLOR_SAMPLE_MODE")]
+
+[assembly: LocalizeEnum<TileMode>(TileMode.Clamp, "CLAMP_TILE_MODE")]
+[assembly: LocalizeEnum<TileMode>(TileMode.Decal, "DECAL_TILE_MODE")]
+[assembly: LocalizeEnum<TileMode>(TileMode.Mirror, "MIRROR_TILE_MODE")]
+[assembly: LocalizeEnum<TileMode>(TileMode.Repeat, "REPEAT_TILE_MODE")]
+
+[assembly: LocalizeEnum<CombineSeparateColorMode>(CombineSeparateColorMode.RGB, "R_G_B_COMBINE_SEPARATE_COLOR_MODE")] 
+[assembly: LocalizeEnum<CombineSeparateColorMode>(CombineSeparateColorMode.HSV, "H_S_V_COMBINE_SEPARATE_COLOR_MODE")]
+[assembly: LocalizeEnum<CombineSeparateColorMode>(CombineSeparateColorMode.HSL, "H_S_L_COMBINE_SEPARATE_COLOR_MODE")]
+
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.Difference, "DIFFERENCE_VECTOR_PATH_OP")]
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.Intersect, "INTERSECT_VECTOR_PATH_OP")]
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.Union, "UNION_VECTOR_PATH_OP")]
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.Xor, "XOR_VECTOR_PATH_OP")]
+[assembly: LocalizeEnum<VectorPathOp>(VectorPathOp.ReverseDifference, "REVERSE_DIFFERENCE_VECTOR_PATH_OP")]
+
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.VeryLow, "VERY_LOW_QUALITY_PRESET")]
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.Low, "LOW_QUALITY_PRESET")]
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.Medium, "MEDIUM_QUALITY_PRESET")]
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.High, "HIGH_QUALITY_PRESET")]
+[assembly: LocalizeEnum<QualityPreset>(QualityPreset.VeryHigh, "VERY_HIGH_QUALITY_PRESET")]
+
+[assembly: LocalizeEnum<ColorSpaceType>(ColorSpaceType.Inherit, "INHERIT_COLOR_SPACE_TYPE")]
+[assembly: LocalizeEnum<ColorSpaceType>(ColorSpaceType.Srgb, "SRGB_COLOR_SPACE_TYPE")]
+[assembly: LocalizeEnum<ColorSpaceType>(ColorSpaceType.LinearSrgb, "LINEAR_SRGB_COLOR_SPACE_TYPE")]
+
+[assembly: LocalizeEnum<EasingType>(EasingType.Linear, "LINEAR_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InSine, "IN_SINE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutSine, "OUT_SINE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutSine, "IN_OUT_SINE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InQuad, "IN_QUAD_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutQuad, "OUT_QUAD_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutQuad, "IN_OUT_QUAD_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InCubic, "IN_CUBIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutCubic, "OUT_CUBIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutCubic, "IN_OUT_CUBIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InQuart, "IN_QUART_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutQuart, "OUT_QUART_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutQuart, "IN_OUT_QUART_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InQuint, "IN_QUINT_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutQuint, "OUT_QUINT_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutQuint, "IN_OUT_QUINT_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InExpo, "IN_EXPO_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutExpo, "OUT_EXPO_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutExpo, "IN_OUT_EXPO_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InCirc, "IN_CIRC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutCirc, "OUT_CIRC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutCirc, "IN_OUT_CIRC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InBack, "IN_BACK_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutBack, "OUT_BACK_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutBack, "IN_OUT_BACK_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InElastic, "IN_ELASTIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutElastic, "OUT_ELASTIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutElastic, "IN_OUT_ELASTIC_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InBounce, "IN_BOUNCE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.OutBounce, "OUT_BOUNCE_EASING_TYPE")]
+[assembly: LocalizeEnum<EasingType>(EasingType.InOutBounce, "IN_OUT_BOUNCE_EASING_TYPE")]
+
+[assembly: LocalizeEnum<RotationType>(RotationType.Degrees, "DEGREES_ROTATION_TYPE")]
+[assembly: LocalizeEnum<RotationType>(RotationType.Radians, "RADIANS_ROTATION_TYPE")]
+
+[assembly: LocalizeEnum<NoiseType>(NoiseType.TurbulencePerlin, "TURBULENCE_PERLIN_NOISE_TYPE")]
+[assembly: LocalizeEnum<NoiseType>(NoiseType.FractalPerlin, "FRACTAL_PERLIN_NOISE_TYPE")]
+[assembly: LocalizeEnum<NoiseType>(NoiseType.Voronoi, "VORONOI_NOISE_TYPE")]
+
+[assembly: LocalizeEnum<OutlineType>(OutlineType.Simple, "SIMPLE_OUTLINE_TYPE")]
+[assembly: LocalizeEnum<OutlineType>(OutlineType.Gaussian, "GAUSSIAN_OUTLINE_TYPE")]
+[assembly: LocalizeEnum<OutlineType>(OutlineType.PixelPerfect, "PIXEL_PERFECT_OUTLINE_TYPE")]
+
+[assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F1, "F1_VORONOI_FEATURE")]
+[assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2, "F2_VORONOI_FEATURE")]
+[assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2MinusF1, "F2_MINUS_F1_VORONOI_FEATURE")]
+
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Normal, "NORMAL_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Darken, "DARKEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Lighten, "LIGHTEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Multiply, "MULTIPLY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Screen, "SCREEN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Overlay, "OVERLAY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.HardLight, "HARD_LIGHT_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.SoftLight, "SOFT_LIGHT_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.ColorDodge, "COLOR_DODGE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.ColorBurn, "COLOR_BURN_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Hue, "HUE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Saturation, "SATURATION_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Color, "COLOR_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Luminosity, "LUMINOSITY_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Difference, "DIFFERENCE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Exclusion, "EXCLUSION_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.Erase, "ERASE_BLEND_MODE")]
+[assembly: LocalizeEnum<BlendMode>(BlendMode.LinearDodge, "LINEAR_DODGE_BLEND_MODE")]
+
+[assembly: LocalizeEnum<PaintBrushShape>(PaintBrushShape.Circle, "PAINT_BRUSH_SHAPE_CIRCLE")]
+[assembly: LocalizeEnum<PaintBrushShape>(PaintBrushShape.Square, "PAINT_BRUSH_SHAPE_SQUARE")]

+ 2 - 1
src/PixiEditor/Models/Handlers/IDocument.cs

@@ -33,7 +33,7 @@ internal interface IDocument : IHandler, Extensions.CommonApi.Documents.IDocumen
     public VectorPath SelectionPathBindable { get; }
     public INodeGraphHandler NodeGraphHandler { get; }
     public DocumentStructureModule StructureHelper { get; }
-    public PreviewPainter PreviewPainter { get; set; }
+    public PreviewPainter? PreviewPainter { get; set; }
     public bool AllChangesSaved { get; }
     public string CoordinatesString { get; set; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
@@ -50,6 +50,7 @@ internal interface IDocument : IHandler, Extensions.CommonApi.Documents.IDocumen
     public DocumentRenderer Renderer { get; }
     public ISnappingHandler SnappingHandler { get; }
     public IReadOnlyCollection<Guid> SelectedMembers { get; }
+    public PreviewPainter? MiniPreviewPainter { get; set; }
     public void RemoveSoftSelectedMember(IStructureMemberHandler member);
     public void ClearSoftSelectedMembers();
     public void AddSoftSelectedMember(IStructureMemberHandler member);

+ 11 - 7
src/PixiEditor/Models/Handlers/INodeHandler.cs

@@ -1,5 +1,6 @@
 using System.Collections.ObjectModel;
 using System.ComponentModel;
+using Avalonia;
 using Avalonia.Media;
 using ChunkyImageLib;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
@@ -8,6 +9,7 @@ using Drawie.Backend.Core;
 using PixiEditor.Models.Rendering;
 using PixiEditor.Models.Structures;
 using Drawie.Numerics;
+using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.Models.Handlers;
 
@@ -22,15 +24,17 @@ public interface INodeHandler : INotifyPropertyChanged, IDisposable
     public ObservableRangeCollection<INodePropertyHandler> Outputs { get; }
     public PreviewPainter? ResultPainter { get; set; }
     public VecD PositionBindable { get; set; }
+    public Rect UiSize { get; set; }
     public bool IsNodeSelected { get; set; }
     public string Icon { get; }
-    public void TraverseBackwards(Func<INodeHandler, bool> func);
-    public void TraverseBackwards(Func<INodeHandler, INodeHandler, bool> func);
-    public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);
-    public void TraverseForwards(Func<INodeHandler, bool> func);
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func);
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func);
+    public void TraverseBackwards(Func<INodeHandler, Traverse> func);
+    public void TraverseBackwards(Func<INodeHandler, INodeHandler, Traverse> func);
+    public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, Traverse> func);
+    public void TraverseForwards(Func<INodeHandler, Traverse> func);
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, Traverse> func);
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, Traverse> func);
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, Traverse> func);
+    public HashSet<NodeFrameViewModelBase> Frames { get; }
     public IReadOnlyDictionary<string, INodePropertyHandler> InputPropertyMap { get; }
     public IReadOnlyDictionary<string, INodePropertyHandler> OutputPropertyMap { get; }
 }

+ 1 - 0
src/PixiEditor/Models/Handlers/INodePropertyHandler.cs

@@ -15,6 +15,7 @@ public interface INodePropertyHandler
     public ObservableCollection<INodePropertyHandler> ConnectedInputs { get; }
 
     public event NodePropertyValueChanged ValueChanged;
+    public event EventHandler ConnectedOutputChanged;
     public INodeHandler Node { get; set; }
     public Type PropertyType { get; }
     public void UpdateComputedValue();

+ 1 - 0
src/PixiEditor/Models/Handlers/IToolsHandler.cs

@@ -19,6 +19,7 @@ internal interface IToolsHandler : IHandler
     public ICollection<IToolSetHandler> AllToolSets { get; }
     public RightClickMode RightClickMode { get; set; }
     public bool EnableSharedToolbar { get; set; }
+    public bool SelectionTintingEnabled { get; set; }
     public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
     public void SetupTools(IServiceProvider services, ToolSetsConfig toolSetConfig);
     public void SetupToolsTooltipShortcuts();

+ 5 - 1
src/PixiEditor/Models/Handlers/Toolbars/PaintBrushShape.cs

@@ -1,7 +1,11 @@
-namespace PixiEditor.Models.Handlers.Toolbars;
+using System.ComponentModel;
+
+namespace PixiEditor.Models.Handlers.Toolbars;
 
 public enum PaintBrushShape
 {
+    [Description("PAINT_BRUSH_SHAPE_CIRCLE")]
     Circle,
+    [Description("PAINT_BRUSH_SHAPE_SQUARE")]
     Square,
 }

+ 29 - 11
src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs

@@ -30,6 +30,7 @@ internal class AffectedAreasGatherer
 
     public AffectedArea MainImageArea { get; private set; } = new();
     public HashSet<Guid> ChangedMembers { get; private set; } = new();
+    public bool IgnoreAnimationPreviews { get; private set; }
     public HashSet<Guid> ChangedMasks { get; private set; } = new();
     public HashSet<Guid> ChangedKeyFrames { get; private set; } = new();
 
@@ -40,10 +41,20 @@ internal class AffectedAreasGatherer
     private bool alreadyAddedWholeCanvasToEveryImagePreview = false;
 
     public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker,
-        IReadOnlyList<IChangeInfo> changes)
+        IReadOnlyList<IChangeInfo> changes, bool refreshAllPreviews)
     {
         this.tracker = tracker;
         ActiveFrame = activeFrame;
+
+        if (refreshAllPreviews)
+        {
+            AddWholeCanvasToMainImage();
+            AddWholeCanvasToEveryImagePreview(false);
+            AddWholeCanvasToEveryMaskPreview();
+            AddAllNodesToImagePreviews();
+            return;
+        }
+
         ProcessChanges(changes);
 
         var outputNode = tracker.Document.NodeGraph.OutputNode;
@@ -102,7 +113,7 @@ internal class AffectedAreasGatherer
                     break;
                 case Size_ChangeInfo:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddWholeCanvasToEveryMaskPreview();
                     break;
                 case StructureMemberMask_ChangeInfo info:
@@ -151,24 +162,25 @@ internal class AffectedAreasGatherer
                     break;
                 case SetActiveFrame_PassthroughAction:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     AddAllNodesToImagePreviews();
+                    IgnoreAnimationPreviews = true;
                     break;
                 case KeyFrameLength_ChangeInfo:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                 case DeleteKeyFrame_ChangeInfo:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                 case KeyFrameVisibility_ChangeInfo:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(true);
                     break;
                 case ConnectProperty_ChangeInfo info:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddToNodePreviews(info.InputNodeId);
                     if (info.OutputNodeId.HasValue)
                     {
@@ -178,7 +190,7 @@ internal class AffectedAreasGatherer
                     break;
                 case PropertyValueUpdated_ChangeInfo info:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddToNodePreviews(info.NodeId);
                     break;
                 case ToggleOnionSkinning_PassthroughAction:
@@ -194,7 +206,7 @@ internal class AffectedAreasGatherer
                     break;
                 case ProcessingColorSpace_ChangeInfo:
                     AddWholeCanvasToMainImage();
-                    AddWholeCanvasToEveryImagePreview();
+                    AddWholeCanvasToEveryImagePreview(false);
                     AddWholeCanvasToEveryMaskPreview();
                     break;
             }
@@ -381,12 +393,18 @@ internal class AffectedAreasGatherer
         }
     }
 
-    private void AddWholeCanvasToEveryImagePreview()
+    private void AddWholeCanvasToEveryImagePreview(bool onlyWithKeyFrames)
     {
         if (alreadyAddedWholeCanvasToEveryImagePreview)
             return;
 
-        tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.Id));
+        tracker.Document.ForEveryReadonlyMember((member) =>
+        {
+            if (!onlyWithKeyFrames || member.KeyFrames.Count > 0)
+            {
+                AddWholeCanvasToImagePreviews(member.Id);
+            }
+        });
         alreadyAddedWholeCanvasToEveryImagePreview = true;
     }
 

+ 47 - 22
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -8,6 +8,8 @@ using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
+using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.Models.Rendering;
 
@@ -26,13 +28,15 @@ internal class MemberPreviewUpdater
     }
 
     public void UpdatePreviews(HashSet<Guid> membersToUpdate,
-        HashSet<Guid> masksToUpdate, HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate)
+        HashSet<Guid> masksToUpdate, HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate,
+        bool ignoreAnimationPreviews, bool renderMiniPreviews)
     {
         if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() &&
             !keyFramesToUpdate.Any())
             return;
 
-        UpdatePreviewPainters(membersToUpdate, masksToUpdate, nodesToUpdate, keyFramesToUpdate);
+        UpdatePreviewPainters(membersToUpdate, masksToUpdate, nodesToUpdate, keyFramesToUpdate, ignoreAnimationPreviews,
+            renderMiniPreviews);
     }
 
     /// <summary>
@@ -41,13 +45,20 @@ internal class MemberPreviewUpdater
     /// <param name="members">Members that should be rendered</param>
     /// <param name="masksToUpdate">Masks that should be rendered</param>
     private void UpdatePreviewPainters(HashSet<Guid> members, HashSet<Guid> masksToUpdate,
-        HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate)
+        HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate, bool ignoreAnimationPreviews,
+        bool renderLowPriorityPreviews)
     {
-        RenderWholeCanvasPreview();
-        RenderLayersPreview(members);
-        RenderMaskPreviews(masksToUpdate);
+        RenderWholeCanvasPreview(renderLowPriorityPreviews);
+        if (renderLowPriorityPreviews)
+        {
+            RenderLayersPreview(members);
+            RenderMaskPreviews(masksToUpdate);
+        }
 
-        RenderAnimationPreviews(members, keyFramesToUpdate);
+        if (!ignoreAnimationPreviews)
+        {
+            RenderAnimationPreviews(members, keyFramesToUpdate);
+        }
 
         RenderNodePreviews(nodesToUpdate);
     }
@@ -55,21 +66,33 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// Re-renders the preview of the whole canvas which is shown as the tab icon
     /// </summary>
-    private void RenderWholeCanvasPreview()
+    /// <param name="renderMiniPreviews">Decides whether to re-render mini previews for the document</param>
+    private void RenderWholeCanvasPreview(bool renderMiniPreviews)
     {
         var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
         //float scaling = (float)previewSize.X / doc.SizeBindable.X;
 
-        if (doc.PreviewPainter == null)
-        {
-            doc.PreviewPainter = new PreviewPainter(doc.Renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
-                doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
-        }
+        doc.PreviewPainter ??= new PreviewPainter(doc.Renderer, doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
+            doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+
+        UpdateDocPreviewPainter(doc.PreviewPainter);
 
-        doc.PreviewPainter.DocumentSize = doc.SizeBindable;
-        doc.PreviewPainter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
-        doc.PreviewPainter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
-        doc.PreviewPainter.Repaint();
+        if (!renderMiniPreviews)
+            return;
+
+        doc.MiniPreviewPainter ??= new PreviewPainter(doc.Renderer, doc.Renderer,
+            doc.AnimationHandler.ActiveFrameTime,
+            doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+
+        UpdateDocPreviewPainter(doc.MiniPreviewPainter);
+    }
+
+    private void UpdateDocPreviewPainter(PreviewPainter painter)
+    {
+        painter.DocumentSize = doc.SizeBindable;
+        painter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
+        painter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
+        painter.Repaint();
     }
 
     private void RenderLayersPreview(HashSet<Guid> memberGuids)
@@ -116,7 +139,8 @@ internal class MemberPreviewUpdater
                 {
                     if (!keyFramesGuids.Contains(childFrame.Id))
                     {
-                        if (!memberGuids.Contains(childFrame.LayerGuid) || !IsInFrame(childFrame))
+                        if (!memberGuids.Contains(childFrame.LayerGuid) || !IsInFrame(childFrame) ||
+                            !groupHandler.IsVisible)
                             continue;
                     }
 
@@ -134,7 +158,7 @@ internal class MemberPreviewUpdater
     private bool IsInFrame(ICelHandler cel)
     {
         return cel.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
-               cel.StartFrameBindable + cel.DurationBindable >= doc.AnimationHandler.ActiveFrameBindable;
+               cel.StartFrameBindable + cel.DurationBindable > doc.AnimationHandler.ActiveFrameBindable;
     }
 
     private void RenderFramePreview(ICelHandler cel)
@@ -265,16 +289,16 @@ internal class MemberPreviewUpdater
         nodeVm.TraverseForwards(next =>
         {
             if (next is not INodeHandler nextVm)
-                return true;
+                return Traverse.Further;
 
             var nextNode = allNodes.FirstOrDefault(x => x.Id == next.Id);
 
             if (nextNode is null || actualRepaintedNodes.Contains(next.Id))
-                return true;
+                return Traverse.Further;
 
             RequestRepaintNode(nextNode, nextVm);
             actualRepaintedNodes.Add(next.Id);
-            return true;
+            return Traverse.Further;
         });
     }
 
@@ -287,6 +311,7 @@ internal class MemberPreviewUpdater
                 nodeVm.ResultPainter = new PreviewPainter(doc.Renderer, renderable,
                     doc.AnimationHandler.ActiveFrameTime,
                     doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+                nodeVm.ResultPainter.AllowPartialResolutions = false;
                 nodeVm.ResultPainter.Repaint();
             }
             else

+ 40 - 3
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -23,6 +23,8 @@ public class PreviewPainter : IDisposable
     public VecI DocumentSize { get; set; }
     public DocumentRenderer Renderer { get; set; }
 
+    public bool AllowPartialResolutions { get; set; } = true;
+
     public bool CanRender => canRender;
 
     public event Action<bool>? CanRenderChanged;
@@ -136,7 +138,8 @@ public class PreviewPainter : IDisposable
     {
         var dirtyArray = dirtyTextures.ToArray();
         bool couldRender = canRender;
-        canRender = PreviewRenderable?.GetPreviewBounds(FrameTime.Frame, ElementToRenderName) != null;
+        canRender = PreviewRenderable?.GetPreviewBounds(FrameTime.Frame, ElementToRenderName) != null &&
+                    painterInstances.Count > 0;
         if (couldRender != canRender)
         {
             CanRenderChanged?.Invoke(canRender);
@@ -167,11 +170,18 @@ public class PreviewPainter : IDisposable
             renderTexture.DrawingSurface.Canvas.Save();
 
             Matrix3X3? matrix = painterInstance.RequestMatrix?.Invoke();
+            VecI bounds = painterInstance.RequestRenderBounds?.Invoke() ?? VecI.Zero;
+
+            ChunkResolution finalResolution = FindResolution(bounds);
+            SamplingOptions samplingOptions = FindSamplingOptions(matrix);
 
             renderTexture.DrawingSurface.Canvas.SetMatrix(matrix ?? Matrix3X3.Identity);
+            renderTexture.DrawingSurface.Canvas.Scale((float)finalResolution.InvertedMultiplier());
 
-            RenderContext context = new(renderTexture.DrawingSurface, FrameTime, ChunkResolution.Full, DocumentSize, DocumentSize,
-                ProcessingColorSpace);
+            RenderContext context = new(renderTexture.DrawingSurface, FrameTime, finalResolution,
+                DocumentSize,
+                DocumentSize,
+                ProcessingColorSpace, samplingOptions);
 
             dirtyTextures.Remove(texture);
             Renderer.RenderNodePreview(PreviewRenderable, renderTexture.DrawingSurface, context, ElementToRenderName)
@@ -230,6 +240,31 @@ public class PreviewPainter : IDisposable
         }
     }
 
+    private ChunkResolution FindResolution(VecI bounds)
+    {
+        if (bounds.X <= 0 || bounds.Y <= 0 || !AllowPartialResolutions)
+        {
+            return ChunkResolution.Full;
+        }
+
+        double density = DocumentSize.X / (double)bounds.X;
+        if (density > 8.01)
+            return ChunkResolution.Eighth;
+        if (density > 4.01)
+            return ChunkResolution.Quarter;
+        if (density > 2.01)
+            return ChunkResolution.Half;
+        return ChunkResolution.Full;
+    }
+
+    private SamplingOptions FindSamplingOptions(Matrix3X3? matrix)
+    {
+        Matrix3X3 mtx = matrix ?? Matrix3X3.Identity;
+        return mtx.ScaleX < 1f || mtx.ScaleY < 1f
+            ? SamplingOptions.Bilinear
+            : SamplingOptions.Default;
+    }
+
     public void Dispose()
     {
         foreach (var texture in renderTextures)
@@ -242,6 +277,8 @@ public class PreviewPainter : IDisposable
 public class PainterInstance
 {
     public int RequestId { get; set; }
+    public Func<VecI> RequestRenderBounds;
+
     public Func<Matrix3X3?>? RequestMatrix;
     public Action RequestRepaint;
 }

+ 35 - 13
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -4,6 +4,7 @@ using Drawie.Backend.Core.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
@@ -34,11 +35,12 @@ internal class SceneRenderer : IDisposable
         DocumentViewModel = documentViewModel;
     }
 
-    public void RenderScene(DrawingSurface target, ChunkResolution resolution, string? targetOutput = null)
+    public void RenderScene(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
+        string? targetOutput = null)
     {
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
-        RenderOnionSkin(target, resolution, targetOutput);
+        RenderOnionSkin(target, resolution, samplingOptions, targetOutput);
 
         string adjustedTargetOutput = targetOutput ?? "";
 
@@ -55,7 +57,7 @@ internal class SceneRenderer : IDisposable
                 cachedTextures[adjustedTargetOutput]?.Dispose();
             }
 
-            var rendered = RenderGraph(target, resolution, targetOutput, finalGraph);
+            var rendered = RenderGraph(target, resolution, samplingOptions, targetOutput, finalGraph);
             cachedTextures[adjustedTargetOutput] = rendered;
             return;
         }
@@ -64,11 +66,21 @@ internal class SceneRenderer : IDisposable
         Matrix3X3 matrixDiff = SolveMatrixDiff(target, cachedTexture);
         int saved = target.Canvas.Save();
         target.Canvas.SetMatrix(matrixDiff);
-        target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
+        if (samplingOptions == SamplingOptions.Default)
+        {
+            target.Canvas.DrawSurface(cachedTexture.DrawingSurface, 0, 0);
+        }
+        else
+        {
+            using var img = cachedTexture.DrawingSurface.Snapshot();
+            target.Canvas.DrawImage(img, 0, 0, samplingOptions);
+        }
+
         target.Canvas.RestoreToCount(saved);
     }
 
-    private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, string? targetOutput,
+    private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
+        string? targetOutput,
         IReadOnlyNodeGraph finalGraph)
     {
         DrawingSurface renderTarget = target;
@@ -104,13 +116,22 @@ internal class SceneRenderer : IDisposable
         }
 
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
-            resolution, finalSize, Document.Size, Document.ProcessingColorSpace);
+            resolution, finalSize, Document.Size, Document.ProcessingColorSpace, samplingOptions);
         context.TargetOutput = targetOutput;
         finalGraph.Execute(context);
 
         if (renderTexture != null)
         {
-            target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
+            if (samplingOptions == SamplingOptions.Default)
+            {
+                target.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
+            }
+            else
+            {
+                using var snapshot = renderTexture.DrawingSurface.Snapshot();
+                target.Canvas.DrawImage(snapshot, 0, 0, samplingOptions);
+            }
+
             target.Canvas.RestoreToCount(restoreCanvasTo);
         }
 
@@ -168,7 +189,9 @@ internal class SceneRenderer : IDisposable
         }
 
         bool renderInDocumentSize = RenderInOutputSize(finalGraph);
-        VecI compareSize = renderInDocumentSize ? (VecI)(Document.Size * resolution.Multiplier()) : target.DeviceClipBounds.Size;
+        VecI compareSize = renderInDocumentSize
+            ? (VecI)(Document.Size * resolution.Multiplier())
+            : target.DeviceClipBounds.Size;
 
         if (cachedTexture.DrawingSurface.DeviceClipBounds.Size != compareSize)
         {
@@ -243,7 +266,7 @@ internal class SceneRenderer : IDisposable
         return highDpiRenderNodePresent;
     }
 
-    private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, string? targetOutput)
+    private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, SamplingOptions sampling, string? targetOutput)
     {
         var animationData = Document.AnimationData;
         if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
@@ -268,9 +291,9 @@ internal class SceneRenderer : IDisposable
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
 
+
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace,
-                finalOpacity);
+                Document.ProcessingColorSpace, sampling, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
         }
@@ -286,8 +309,7 @@ internal class SceneRenderer : IDisposable
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size,
-                Document.ProcessingColorSpace,
-                finalOpacity);
+                Document.ProcessingColorSpace, sampling, finalOpacity);
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
         }

+ 144 - 0
src/PixiEditor/Models/Structures/ObservableHashSet.cs

@@ -0,0 +1,144 @@
+using System.Collections;
+using System.Collections.Immutable;
+using System.Collections.Specialized;
+using System.Runtime.Serialization;
+
+namespace PixiEditor.Models.Structures;
+
+public class ObservableHashSet<T> : ISet<T>, IReadOnlySet<T>, IDeserializationCallback, ISerializable, INotifyCollectionChanged
+{
+    private readonly HashSet<T> setImplementation;
+
+    public ObservableHashSet()
+    {
+        setImplementation = new HashSet<T>();
+    }
+
+    public ObservableHashSet(IEnumerable<T> collection)
+    {
+        setImplementation = new HashSet<T>(collection);
+    }
+    
+    public bool Add(T item)
+    {
+        var isAdded = setImplementation.Add(item);
+
+        if (isAdded)
+        {
+            CallCollectionChanged(NotifyCollectionChangedAction.Add, item);
+        }
+        
+        return isAdded;
+    }
+
+    void ICollection<T>.Add(T item) => Add(item);
+
+    public void Clear()
+    {
+        setImplementation.Clear();
+        CallCollectionChanged(NotifyCollectionChangedAction.Reset, Array.Empty<T>(), setImplementation.ToList());
+    }
+
+    public bool Remove(T item)
+    {
+        var isRemoved = setImplementation.Remove(item);
+
+        if (isRemoved)
+        {
+            CallCollectionChanged(NotifyCollectionChangedAction.Remove, item);
+        }
+        
+        return isRemoved;
+    }
+
+    /// <summary>
+    /// Not implemented
+    /// </summary>
+    /// <exception cref="NotSupportedException">This method is not implemented.</exception>
+    public void ExceptWith(IEnumerable<T> other)
+    {
+        throw new NotSupportedException();
+    }
+
+    /// <summary>
+    /// Not implemented
+    /// </summary>
+    /// <exception cref="NotSupportedException">This method is not implemented.</exception>
+    public void IntersectWith(IEnumerable<T> other)
+    {
+        throw new NotSupportedException();
+    }
+
+    /// <summary>
+    /// Not implemented
+    /// </summary>
+    /// <exception cref="NotSupportedException">This method is not implemented.</exception>
+    public void SymmetricExceptWith(IEnumerable<T> other)
+    {
+        throw new NotSupportedException();
+    }
+
+    public void UnionWith(IEnumerable<T> other)
+    {
+        var allOther = other.ToImmutableHashSet();
+        var addedOnly = allOther.Except(setImplementation);
+        
+        setImplementation.UnionWith(allOther);
+        CallCollectionChanged(
+            NotifyCollectionChangedAction.Reset, 
+            addedOnly.ToList());
+    }
+
+    public void ReplaceBy(IEnumerable<T> other)
+    {
+        var otherOriginal = other.ToHashSet();
+        var original = setImplementation.ToImmutableHashSet();
+        var removed = original.Except(otherOriginal);
+
+        setImplementation.Clear();
+        setImplementation.UnionWith(otherOriginal);
+        CallCollectionChanged(NotifyCollectionChangedAction.Replace, otherOriginal.ToList(), removed.ToList());
+    }
+
+    public bool IsProperSubsetOf(IEnumerable<T> other) => setImplementation.IsProperSubsetOf(other);
+
+    public bool IsProperSupersetOf(IEnumerable<T> other) => setImplementation.IsProperSupersetOf(other);
+
+    public bool IsSubsetOf(IEnumerable<T> other) => setImplementation.IsSubsetOf(other);
+
+    public bool IsSupersetOf(IEnumerable<T> other) => setImplementation.IsSupersetOf(other);
+
+    public bool Overlaps(IEnumerable<T> other) => setImplementation.Overlaps(other);
+
+    public bool SetEquals(IEnumerable<T> other) => setImplementation.SetEquals(other);
+
+    public bool Contains(T item) => setImplementation.Contains(item);
+
+    public void CopyTo(T[] array, int arrayIndex) => setImplementation.CopyTo(array, arrayIndex);
+
+    public IEnumerator<T> GetEnumerator() => setImplementation.GetEnumerator();
+
+    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)setImplementation).GetEnumerator();
+
+    public int Count => setImplementation.Count;
+
+    bool ICollection<T>.IsReadOnly => ((ISet<T>)setImplementation).IsReadOnly;
+
+    void IDeserializationCallback.OnDeserialization(object? sender) => setImplementation.OnDeserialization(sender);
+
+    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) => setImplementation.GetObjectData(info, context);
+
+    private void CallCollectionChanged(NotifyCollectionChangedAction action) =>
+        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action));
+    
+    private void CallCollectionChanged(NotifyCollectionChangedAction action, IList added, IList removed) =>
+        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, added, removed));
+
+    private void CallCollectionChanged(NotifyCollectionChangedAction action, IList changed) =>
+        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, changed));
+
+    private void CallCollectionChanged(NotifyCollectionChangedAction action, T item) =>
+        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action, item));
+
+    public event NotifyCollectionChangedEventHandler? CollectionChanged;
+}

+ 5 - 1
src/PixiEditor/Models/Tools/BrightnessMode.cs

@@ -1,7 +1,11 @@
-namespace PixiEditor.Models.Tools;
+using System.ComponentModel;
+
+namespace PixiEditor.Models.Tools;
 
 public enum BrightnessMode
 {
+    [Description("BRIGHTNESS_MODE_DEFAULT")]
     Default,
+    [Description("BRIGHTNESS_MODE_REPEAT")]
     Repeat
 }

+ 2 - 2
src/PixiEditor/Properties/AssemblyInfo.cs

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

+ 4 - 7
src/PixiEditor/Styles/Templates/NodeFrameView.axaml

@@ -7,13 +7,10 @@
         <Setter Property="Template">
             <Setter.Value>
                 <ControlTemplate>
-                    <Grid Width="{Binding Size.X, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeFrameView}}">
-                        <Rectangle Fill="{TemplateBinding Background}"
-                                   Stroke="{TemplateBinding BorderBrush}"
-                                   StrokeThickness="2" RadiusX="10" RadiusY="10"
-                                   Width="{Binding Size.X, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeFrameView}}"
-                                   Height="{Binding Size.Y, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeFrameView}}" />
-                    </Grid>
+                    <Path Data="{Binding Geometry, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeFrameView}}"
+                          Fill="{TemplateBinding Background}"
+                          Stroke="{TemplateBinding BorderBrush}"
+                          StrokeThickness="2" />
                 </ControlTemplate>
             </Setter.Value>
         </Setter>

+ 4 - 10
src/PixiEditor/Styles/Templates/NodeGraphView.axaml

@@ -59,7 +59,8 @@
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
                                     SocketDropCommand="{Binding SocketDropCommand,
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                    ResultPreview="{Binding ResultPainter}" />
+                                    ResultPreview="{Binding ResultPainter}"
+                                    Bounds="{Binding UiSize, Mode=OneWayToSource}" />
                             </DataTemplate>
                         </ItemsControl.ItemTemplate>
                         <ItemsControl.ItemContainerTheme>
@@ -109,9 +110,8 @@
                         <ItemsControl.ItemTemplate>
                             <DataTemplate>
                                 <nodes:NodeFrameView
-                                    TopLeft="{Binding TopLeft}"
-                                    BottomRight="{Binding BottomRight}"
-                                    Size="{Binding Size}">
+                                    Geometry="{Binding Geometry}"
+                                    ClipToBounds="False">
                                     <nodes:NodeFrameView.Background>
                                         <MultiBinding Converter="{converters:UnsetSkipMultiConverter}">
                                             <Binding Path="InternalName"
@@ -131,12 +131,6 @@
                                 </nodes:NodeFrameView>
                             </DataTemplate>
                         </ItemsControl.ItemTemplate>
-                        <ItemsControl.ItemContainerTheme>
-                            <ControlTheme TargetType="ContentPresenter">
-                                <Setter Property="Canvas.Left" Value="{Binding TopLeft.X}" />
-                                <Setter Property="Canvas.Top" Value="{Binding TopLeft.Y}" />
-                            </ControlTheme>
-                        </ItemsControl.ItemContainerTheme>
                     </ItemsControl>
                 </Grid>
             </ControlTemplate>

+ 15 - 1
src/PixiEditor/Styles/Templates/NodePicker.axaml

@@ -4,7 +4,8 @@
                     xmlns:visuals="clr-namespace:PixiEditor.Views.Visuals"
                     xmlns:ui="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
                     xmlns:input="clr-namespace:PixiEditor.Views.Input"
-                    xmlns:nodes1="clr-namespace:PixiEditor.ViewModels.Nodes">
+                    xmlns:nodes1="clr-namespace:PixiEditor.ViewModels.Nodes"
+                    xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters">
     <ControlTheme TargetType="nodes:NodePicker" x:Key="{x:Type nodes:NodePicker}">
         <Setter Property="Template">
             <ControlTemplate>
@@ -55,6 +56,19 @@
                                                         CommandParameter="{Binding}"
                                                         HorizontalContentAlignment="Left"
                                                         IsVisible="{Binding !Hidden}">
+                                                        <Classes.KeyboardSelected>
+                                                            <MultiBinding Converter="{converters:AreEqualConverter}">
+                                                                <Binding />
+                                                                <Binding Path="SelectedNode" RelativeSource="{RelativeSource FindAncestor, AncestorType=nodes:NodePicker}" Mode="OneWay"/>
+                                                            </MultiBinding>
+                                                        </Classes.KeyboardSelected>
+                                                        
+                                                        <Button.Styles>
+                                                            <Style Selector="Button.KeyboardSelected">
+                                                                <Setter Property="Background" Value="{DynamicResource ThemeControlHighlightBrush}" />
+                                                            </Style>
+                                                        </Button.Styles>
+                                                        
                                                         <TextBlock Margin="10 0 0 0">
                                                             <Run Classes="pixi-icon"
                                                                  BaselineAlignment="Center"

+ 15 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -35,6 +35,7 @@ using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.Models.IO;
+using PixiEditor.Models.Position;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Skia;
 using PixiEditor.UI.Common.Localization;
@@ -184,6 +185,16 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
     public IStructureMemberHandler? SelectedStructureMember { get; private set; } = null;
 
+    private PreviewPainter miniPreviewPainter;
+
+    public PreviewPainter MiniPreviewPainter
+    {
+        get => miniPreviewPainter;
+        set
+        {
+            SetProperty(ref miniPreviewPainter, value);
+        }
+    }
 
     private PreviewPainter previewSurface;
 
@@ -374,11 +385,15 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             viewModel.MarkAsSaved();
         }));
 
+        acc.AddActions(new SetActiveFrame_PassthroughAction(1), new RefreshPreviews_PassthroughAction());
+
         foreach (var factory in allFactories)
         {
             factory.ResourceLocator = null;
         }
 
+        viewModel.NodeGraph.FinalizeCreation();
+
         return viewModel;
 
 

+ 81 - 0
src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs

@@ -19,6 +19,8 @@ namespace PixiEditor.ViewModels.Document;
 
 internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposable
 {
+    private bool isFullyCreated;
+    
     public DocumentViewModel DocumentViewModel { get; }
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
@@ -108,6 +110,9 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
         connection.OutputProperty.ConnectedInputs.Add(connection.InputProperty);
 
         Connections.Add(connection);
+        
+        UpdatesFramesPartOf(connection.InputNode);
+        UpdatesFramesPartOf(connection.OutputNode);
 
         StructureTree.Update(this);
     }
@@ -123,6 +128,9 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
             Connections.Remove(connection);
         }
 
+        UpdatesFramesPartOf(connection.InputNode);
+        UpdatesFramesPartOf(connection.OutputNode);
+        
         var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
         if (node != null)
         {
@@ -136,6 +144,79 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
         StructureTree.Update(this);
     }
 
+    public void UpdatesFramesPartOf(INodeHandler node)
+    {
+        if (!isFullyCreated)
+            return;
+
+        var lastKnownFramesPartOf = node.Frames.OfType<NodeZoneViewModel>().ToHashSet();
+        var startLookup = Frames.OfType<NodeZoneViewModel>().ToDictionary(x => x.Start);
+        var currentlyPartOf = new HashSet<NodeZoneViewModel>();
+        
+        node.TraverseBackwards(x =>
+        {
+            if (x is IPairNodeEndViewModel)
+                return Traverse.NoFurther;
+
+            if (x is not IPairNodeStartViewModel)
+                return Traverse.Further;
+
+            var zone = startLookup[x];
+            currentlyPartOf.Add(zone);
+
+            return Traverse.Further;
+        });
+
+        foreach (var frame in currentlyPartOf)
+        {
+            frame.Nodes.Add(node);
+            node.Frames.Add(frame);
+        }
+
+        lastKnownFramesPartOf.ExceptWith(currentlyPartOf);
+        foreach (var removedFrom in lastKnownFramesPartOf)
+        {
+            removedFrom.Nodes.Remove(node);
+            node.Frames.Remove(removedFrom);
+        }
+    }
+
+    public void FinalizeCreation()
+    {
+        if (isFullyCreated)
+            return;
+        
+        isFullyCreated = true;
+        
+        foreach (var nodeZoneViewModel in Frames.OfType<NodeZoneViewModel>())
+        {
+            UpdateNodesPartOf(nodeZoneViewModel);
+        }
+    }
+
+    private static void UpdateNodesPartOf(NodeZoneViewModel zone)
+    {
+        var currentlyPartOf = new HashSet<INodeHandler>([zone.Start, zone.End]);
+
+        foreach (var node in zone.Start
+                     .Outputs
+                     .SelectMany(x => x.ConnectedInputs)
+                     .Select(x => x.Node))
+        {
+            node.TraverseForwards((x) =>
+            {
+                if (x is IPairNodeEndViewModel)
+                    return Traverse.NoFurther;
+
+                currentlyPartOf.Add(x);
+
+                return Traverse.Further;
+            });
+        }
+
+        zone.Nodes.ReplaceBy(currentlyPartOf);
+    }
+
     public void RemoveConnections(Guid nodeId)
     {
         var connections = Connections

+ 21 - 1
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/ApplyFilterNodeViewModel.cs

@@ -1,3 +1,4 @@
+using System.Collections.Specialized;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.Nodes;
@@ -5,4 +6,23 @@ using PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
 
 [NodeViewModel("APPLY_FILTER_NODE", "FILTERS", PixiPerfectIcons.Magic)]
-internal class ApplyFilterNodeViewModel : NodeViewModel<ApplyFilterNode>;
+internal class ApplyFilterNodeViewModel : NodeViewModel<ApplyFilterNode>
+{
+    private NodePropertyViewModel MaskInput { get; set; }
+    
+    private NodePropertyViewModel MaskInvertInput { get; set; }
+
+    public override void OnInitialized()
+    {
+        MaskInput = FindInputProperty("Mask");
+        MaskInvertInput = FindInputProperty("InvertMask");
+        
+        UpdateInvertVisible();
+        MaskInput.ConnectedOutputChanged += (_, _) => UpdateInvertVisible();
+    }
+
+    private void UpdateInvertVisible()
+    {
+        MaskInvertInput.IsVisible = MaskInput.ConnectedOutput != null;
+    }
+}

+ 1 - 1
src/PixiEditor/ViewModels/Document/Nodes/ModifyImageLeftNodeViewModel.cs

@@ -5,4 +5,4 @@ using PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes;
 
 [NodeViewModel("MODIFY_IMAGE_LEFT_NODE", "IMAGE", PixiPerfectIcons.PutImage)]
-internal class ModifyImageLeftNodeViewModel : NodeViewModel<ModifyImageLeftNode>;
+internal class ModifyImageLeftNodeViewModel : NodeViewModel<ModifyImageLeftNode>, IPairNodeStartViewModel;

+ 1 - 1
src/PixiEditor/ViewModels/Document/Nodes/ModifyImageRightNodeViewModel.cs

@@ -4,4 +4,4 @@ using PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes;
 
 [NodeViewModel("MODIFY_IMAGE_RIGHT_NODE", "IMAGE", null)]
-internal class ModifyImageRightNodeViewModel : NodeViewModel<ModifyImageRightNode>;
+internal class ModifyImageRightNodeViewModel : NodeViewModel<ModifyImageRightNode>, IPairNodeEndViewModel;

+ 29 - 1
src/PixiEditor/ViewModels/Document/Nodes/NoiseNodeViewModel.cs

@@ -1,8 +1,36 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ViewModels.Nodes.Properties;
 
 namespace PixiEditor.ViewModels.Document.Nodes;
 
 [NodeViewModel("NOISE_NODE", "IMAGE", PixiPerfectIcons.Noise)]
-internal class NoiseNodeViewModel : NodeViewModel<NoiseNode>;
+internal class NoiseNodeViewModel : NodeViewModel<NoiseNode>
+{
+    private GenericEnumPropertyViewModel Type { get; set; }
+    private GenericEnumPropertyViewModel VoronoiFeature { get; set; }
+    private NodePropertyViewModel Randomness { get; set; }
+    private NodePropertyViewModel AngleOffset { get; set; }
+
+    public override void OnInitialized()
+    {
+        Type = FindInputProperty("NoiseType") as GenericEnumPropertyViewModel;
+        VoronoiFeature = FindInputProperty("VoronoiFeature") as GenericEnumPropertyViewModel;
+        Randomness = FindInputProperty("Randomness");
+        AngleOffset = FindInputProperty("AngleOffset");
+
+        Type.ValueChanged += (_, _) => UpdateInputVisibility();
+        UpdateInputVisibility();
+    }
+
+    private void UpdateInputVisibility()
+    {
+        if (Type.Value is not NoiseType type)
+            return;
+        
+        Randomness.IsVisible = type == NoiseType.Voronoi;
+        VoronoiFeature.IsVisible = type == NoiseType.Voronoi;
+        AngleOffset.IsVisible = type == NoiseType.Voronoi;
+    }
+}

+ 9 - 0
src/PixiEditor/ViewModels/Document/Nodes/Shapes/LineNodeViewModel.cs

@@ -0,0 +1,9 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Shapes;
+
+[NodeViewModel("LINE_NODE", "SHAPE", PixiPerfectIcons.Line)]
+internal class LineNodeViewModel : NodeViewModel<LineNode>
+{
+}

+ 9 - 0
src/PixiEditor/ViewModels/Document/Nodes/Shapes/RectangleNodeViewModel.cs

@@ -0,0 +1,9 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Shapes;
+
+[NodeViewModel("RECTANGLE_NODE", "SHAPE", PixiPerfectIcons.Square)]
+internal class RectangleNodeViewModel : NodeViewModel<RectangleNode>
+{
+}

+ 2 - 2
src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs

@@ -60,10 +60,10 @@ internal abstract class StructureMemberViewModel<T> : NodeViewModel<T>, IStructu
                 if (node is IFolderHandler parent && input is { PropertyName: FolderNode.ContentInternalName })
                 {
                     visible = parent.IsVisibleBindable;
-                    return visible;
+                    return visible ? Traverse.Further : Traverse.Exit;
                 }
 
-                return true;
+                return Traverse.Further;
             });
 
             return visible;

+ 2 - 1
src/PixiEditor/ViewModels/Document/StructureTree.cs

@@ -1,5 +1,6 @@
 using System.Collections.ObjectModel;
 using PixiEditor.Models.Handlers;
+using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.ViewModels.Document;
 
@@ -60,7 +61,7 @@ internal class StructureTree
 
             _memberMap.TryAdd(node, lastRoot);
 
-            return true;
+            return Traverse.Further;
         });
 
         List<IStructureMemberHandler> toRemove = new();

+ 6 - 0
src/PixiEditor/ViewModels/Nodes/IPairNodeEndViewModel.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ViewModels.Nodes;
+
+public interface IPairNodeEndViewModel
+{
+    
+}

+ 6 - 0
src/PixiEditor/ViewModels/Nodes/IPairNodeStartViewModel.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ViewModels.Nodes;
+
+public interface IPairNodeStartViewModel
+{
+    
+}

+ 2 - 28
src/PixiEditor/ViewModels/Nodes/NodeFrameViewModel.cs

@@ -1,9 +1,4 @@
-using System.Collections.ObjectModel;
-using System.Collections.Specialized;
-using System.ComponentModel;
-using CommunityToolkit.Mvvm.ComponentModel;
-using PixiEditor.Models.Handlers;
-using Drawie.Numerics;
+using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.ViewModels.Nodes;
 
@@ -16,27 +11,6 @@ internal sealed class NodeFrameViewModel : NodeFrameViewModelBase
 
     protected override void CalculateBounds()
     {
-        
-        // TODO: Use the GetBounds like in NodeZoneViewModel
-        if (Nodes.Count == 0)
-        {
-            if (TopLeft == BottomRight)
-            {
-                BottomRight = TopLeft + new VecD(100, 100);
-            }
-            
-            return;
-        }
-        
-        var minX = Nodes.Min(n => n.PositionBindable.X) - 30;
-        var minY = Nodes.Min(n => n.PositionBindable.Y) - 45;
-        
-        var maxX = Nodes.Max(n => n.PositionBindable.X) + 130;
-        var maxY = Nodes.Max(n => n.PositionBindable.Y) + 130;
-
-        TopLeft = new VecD(minX, minY);
-        BottomRight = new VecD(maxX, maxY);
-
-        Size = BottomRight - TopLeft;
+        throw new NotImplementedException();
     }
 }

+ 23 - 21
src/PixiEditor/ViewModels/Nodes/NodeFrameViewModelBase.cs

@@ -1,20 +1,23 @@
 using System.Collections.ObjectModel;
 using System.Collections.Specialized;
 using System.ComponentModel;
+using Avalonia.Media;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
+using PixiEditor.Models.Structures;
 
 namespace PixiEditor.ViewModels.Nodes;
 
 public abstract class NodeFrameViewModelBase : ObservableObject
 {
     private Guid id;
+    private StreamGeometry geometry;
     private VecD topLeft;
     private VecD bottomRight;
     private VecD size;
     
-    public ObservableCollection<INodeHandler> Nodes { get; }
+    public ObservableHashSet<INodeHandler> Nodes { get; }
 
     public string InternalName { get; init; }
     
@@ -24,28 +27,16 @@ public abstract class NodeFrameViewModelBase : ObservableObject
         set => SetProperty(ref id, value);
     }
     
-    public VecD TopLeft
+    public StreamGeometry Geometry
     {
-        get => topLeft;
-        set => SetProperty(ref topLeft, value);
-    }
-
-    public VecD BottomRight
-    {
-        get => bottomRight;
-        set => SetProperty(ref bottomRight, value);
-    }
-
-    public VecD Size
-    {
-        get => size;
-        set => SetProperty(ref size, value);
+        get => geometry;
+        set => SetProperty(ref geometry, value);
     }
 
     public NodeFrameViewModelBase(Guid id, IEnumerable<INodeHandler> nodes)
     {
         Id = id;
-        Nodes = new ObservableCollection<INodeHandler>(nodes);
+        Nodes = new(nodes);
 
         Nodes.CollectionChanged += OnCollectionChanged;
         AddHandlers(Nodes);
@@ -54,13 +45,22 @@ public abstract class NodeFrameViewModelBase : ObservableObject
     private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
     {
         var action = e.Action;
-        if (action != NotifyCollectionChangedAction.Add && action != NotifyCollectionChangedAction.Remove && action != NotifyCollectionChangedAction.Replace && action != NotifyCollectionChangedAction.Reset)
+        if (action is
+            not NotifyCollectionChangedAction.Add and
+            not NotifyCollectionChangedAction.Remove and
+            not NotifyCollectionChangedAction.Replace and
+            not NotifyCollectionChangedAction.Reset)
         {
             return;
         }
+
+        CalculateBounds();
+        
+        if (e.NewItems != null)
+            AddHandlers(e.NewItems.Cast<INodeHandler>());
         
-        AddHandlers((IEnumerable<NodeViewModel>)e.NewItems);
-        RemoveHandlers((IEnumerable<NodeViewModel>)e.OldItems);
+        if (e.OldItems != null)
+            RemoveHandlers(e.OldItems.Cast<INodeHandler>());
     }
 
     private void AddHandlers(IEnumerable<INodeHandler> nodes)
@@ -81,7 +81,9 @@ public abstract class NodeFrameViewModelBase : ObservableObject
 
     private void NodePropertyChanged(object? sender, PropertyChangedEventArgs e)
     {
-        if (e.PropertyName != nameof(INodeHandler.PositionBindable))
+        if (e.PropertyName is
+            not nameof(INodeHandler.PositionBindable) and
+            not nameof(INodeHandler.UiSize))
         {
             return;
         }

+ 2 - 0
src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs

@@ -27,6 +27,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
     private INodePropertyHandler? connectedOutput;
 
     public event NodePropertyValueChanged? ValueChanged;
+    public event EventHandler? ConnectedOutputChanged;
 
     public string DisplayName
     {
@@ -115,6 +116,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
             if (SetProperty(ref connectedOutput, value))
             {
                 OnPropertyChanged(nameof(ShowInputField));
+                ConnectedOutputChanged?.Invoke(this, EventArgs.Empty);
             }
         }
     }

+ 99 - 24
src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs

@@ -1,6 +1,7 @@
 using System.Collections.ObjectModel;
 using System.Collections.Specialized;
 using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using Avalonia;
 using Avalonia.Media;
@@ -25,6 +26,7 @@ namespace PixiEditor.ViewModels.Nodes;
 internal abstract class NodeViewModel : ObservableObject, INodeHandler
 {
     private LocalizedString displayName;
+    private Rect size;
     private IBrush? categoryBrush;
     private string? nodeNameBindable;
     private VecD position;
@@ -111,6 +113,12 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
+    public Rect UiSize
+    {
+        get => size;
+        set => SetProperty(ref size, value);
+    }
+
     public ObservableRangeCollection<INodePropertyHandler> Inputs
     {
         get => inputs;
@@ -218,7 +226,11 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
 
     public string Icon => icon ??= GetType().GetCustomAttribute<NodeViewModelAttribute>().Icon;
 
-    public void TraverseBackwards(Func<INodeHandler, bool> func)
+    [DoesNotReturn]
+    private void ThrowInvalidTraverseResult(Traverse traverse) =>
+        throw new IndexOutOfRangeException($"Invalid Traverse Option '{traverse}'");
+
+    public void TraverseBackwards(Func<INodeHandler, Traverse> func)
     {
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<INodeHandler>();
@@ -233,9 +245,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 continue;
             }
 
-            if (!func(node))
+            var result = func(node);
+            switch (result)
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
 
             foreach (var inputProperty in node.Inputs)
@@ -248,7 +269,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
-    public void TraverseBackwards(Func<INodeHandler, INodeHandler, bool> func)
+    public void TraverseBackwards(Func<INodeHandler, INodeHandler, Traverse> func)
     {
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler)>();
@@ -263,9 +284,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 continue;
             }
 
-            if (!func(node.Item1, node.Item2))
+            var result = func(node.Item1, node.Item2);
+            switch (result)
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
 
             foreach (var inputProperty in node.Item1.Inputs)
@@ -278,7 +308,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
-    public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func)
+    public void TraverseBackwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, Traverse> func)
     {
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler)>();
@@ -292,10 +322,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
                 continue;
             }
-
-            if (!func(node.Item1, node.Item2, node.Item3))
+            var result = func(node.Item1, node.Item2, node.Item3);
+            switch (result)
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
 
             foreach (var inputProperty in node.Item1.Inputs)
@@ -308,7 +346,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
-    public void TraverseForwards(Func<INodeHandler, bool> func)
+    public void TraverseForwards(Func<INodeHandler, Traverse> func)
     {
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<INodeHandler>();
@@ -322,10 +360,19 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
                 continue;
             }
-
-            if (!func(node))
+            
+            var result = func(node);
+            switch (result)
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
 
             foreach (var outputProperty in node.Outputs)
@@ -338,7 +385,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func)
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, Traverse> func)
     {
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler)>();
@@ -352,10 +399,19 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
                 continue;
             }
-
-            if (!func(node.Item1, node.Item2))
+            
+            var result = func(node.Item1, node.Item2);
+            switch (result)
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
 
             foreach (var outputProperty in node.Item1.Outputs)
@@ -368,7 +424,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func)
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, Traverse> func)
     {
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler)>();
@@ -383,9 +439,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 continue;
             }
 
-            if (!func(node.Item1, node.Item2, node.Item3))
+            var result = func(node.Item1, node.Item2, node.Item3);
+            switch (result)
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
 
             foreach (var outputProperty in node.Item1.Outputs)
@@ -399,7 +464,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
     }
 
     public void TraverseForwards(
-        Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func)
+        Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, Traverse> func)
     {
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler)>();
@@ -414,9 +479,18 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 continue;
             }
 
-            if (!func(node.Item1, node.Item2, node.Item3, node.Item4))
+            var result = func(node.Item1, node.Item2, node.Item3, node.Item4);
+            switch (result)
             {
-                return;
+                case Traverse.NoFurther:
+                    continue;
+                case Traverse.Exit:
+                    return;
+                case Traverse.Further:
+                    break;
+                default:
+                    ThrowInvalidTraverseResult(result);
+                    break;
             }
 
             foreach (var outputProperty in node.Item1.Outputs)
@@ -429,6 +503,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
+    public HashSet<NodeFrameViewModelBase> Frames { get; } = [];
 
     public virtual void Dispose()
     {

+ 222 - 35
src/PixiEditor/ViewModels/Nodes/NodeZoneViewModel.cs

@@ -1,20 +1,26 @@
-using PixiEditor.Models.Handlers;
+using System.Buffers;
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Media;
+using PixiEditor.Models.Handlers;
 using Drawie.Numerics;
 
 namespace PixiEditor.ViewModels.Nodes;
 
 public sealed class NodeZoneViewModel : NodeFrameViewModelBase
 {
-    private INodeHandler start;
-    private INodeHandler end;
+    public INodeHandler Start { get; }
     
-    public NodeZoneViewModel(Guid id, string internalName, INodeHandler start, INodeHandler end) : base(id, [start, end])
+    public INodeHandler End { get; }
+
+    public NodeZoneViewModel(Guid id, string internalName, INodeHandler start, INodeHandler end) : base(id,
+        [start, end])
     {
         InternalName = internalName;
-        
-        this.start = start.Metadata.IsPairNodeStart ? start : end;
-        this.end = start.Metadata.IsPairNodeStart ? end : start;
-        
+
+        this.Start = start.Metadata.IsPairNodeStart ? start : end;
+        this.End = start.Metadata.IsPairNodeStart ? end : start;
+
         CalculateBounds();
     }
 
@@ -22,53 +28,234 @@ public sealed class NodeZoneViewModel : NodeFrameViewModelBase
     {
         if (Nodes.Count == 0)
         {
-            if (TopLeft == BottomRight)
+            return;
+        }
+
+        var points = GetBoundPoints();
+
+        Geometry = BuildRoundedHullGeometry(points, 25);
+    }
+
+    private static StreamGeometry BuildRoundedHullGeometry(List<VecD> points, double cornerRadius)
+    {
+        const double startBoostDeg = 100;
+        const double maxBoost = 2.5;
+
+        var span = CollectionsMarshal.AsSpan(points);
+
+        var pool = ArrayPool<VecD>.Shared;
+        var hullBuf = pool.Rent(Math.Max(3, span.Length));
+
+        try
+        {
+            var hullCount = ConvexHull(span, hullBuf.AsSpan());
+            var hull = hullBuf.AsSpan(0, hullCount);
+
+            var geometry = new StreamGeometry();
+            if (hull.IsEmpty) return geometry;
+
+            using var ctx = geometry.Open();
+            
+            if (hull.Length <= 2 || cornerRadius <= 0)
             {
-                BottomRight = TopLeft + new VecD(100, 100);
+                ctx.BeginFigure(new Point(hull[0].X, hull[0].Y), isFilled: true);
+                for (var i = 1; i < hull.Length; i++)
+                    ctx.LineTo(new Point(hull[i].X, hull[i].Y));
+                ctx.EndFigure(isClosed: true);
+                return geometry;
             }
-            
-            return;
+
+            var n = hull.Length;
+
+            var enter = n <= 256 ? stackalloc VecD[n] : pool.Rent(n).AsSpan(0, n);
+            var exit = n <= 256 ? stackalloc VecD[n] : pool.Rent(n).AsSpan(0, n);
+            var rented = n > 256;
+
+            try
+            {
+                for (var i = 0; i < n; i++)
+                {
+                    var prev = hull[(i - 1 + n) % n];
+                    var current = hull[i];
+                    var next = hull[(i + 1) % n];
+
+                    var directionIn = (current - prev).Normalize();
+                    var directionOut = (next - current).Normalize();
+                    var lenIn = (prev - current).Length;
+                    var lenOut = (current - next).Length;
+
+                    var a = (prev - current).Normalize();
+                    var b = (next - current).Normalize();
+                    var dot = Math.Clamp(a.X * b.X + a.Y * b.Y, -1, 1);
+                    var theta = Math.Acos(dot); // radians
+
+                    // Boost wide angles a bit (same curve, fewer ops)
+                    var thetaDeg = theta * (180.0 / Math.PI);
+                    var tNorm = Math.Clamp((thetaDeg - startBoostDeg) / (180.0 - startBoostDeg), 0, 1);
+                    var s = tNorm * tNorm * (3 - 2 * tNorm); // smoothstep
+                    var radiusHere = cornerRadius * (1 + (maxBoost - 1) * s);
+
+                    var t = (theta > 1e-6) ? radiusHere / Math.Tan(theta / 2.0) : 0;
+                    var tMax = Math.Min(lenIn, lenOut) * 0.5;
+                    t = Math.Min(t, tMax);
+
+                    if (t <= 1e-6)
+                    {
+                        enter[i] = current;
+                        exit[i] = current;
+                    }
+                    else
+                    {
+                        enter[i] = current + directionIn * -t;
+                        exit[i] = current + directionOut * t;
+                    }
+                }
+
+                ctx.BeginFigure(new Point(enter[0].X, enter[0].Y), isFilled: true);
+
+                for (var i = 0; i < n; i++)
+                {
+                    ctx.QuadraticBezierTo(
+                        new Point(hull[i].X, hull[i].Y),
+                        new Point(exit[i].X, exit[i].Y));
+
+                    var nextEnter = enter[(i + 1) % n];
+                    ctx.LineTo(new Point(nextEnter.X, nextEnter.Y));
+                }
+
+                ctx.EndFigure(isClosed: true);
+            }
+            finally
+            {
+                if (rented)
+                {
+                    pool.Return(
+                        MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(enter), n).ToArray());
+
+                    pool.Return(MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(exit), n)
+                        .ToArray());
+                }
+            }
+
+            return geometry;
+        }
+        finally
+        {
+            pool.Return(hullBuf);
         }
+    }
+
+    private static int ConvexHull(ReadOnlySpan<VecD> input, Span<VecD> hull)
+    {
+        var n = input.Length;
+        if (n <= 1)
+        {
+            if (n == 1) hull[0] = input[0];
+            return n;
+        }
+
+        var pool = ArrayPool<VecD>.Shared;
+        var pts = pool.Rent(n);
+
+        try
+        {
+            input.CopyTo(pts);
+            Array.Sort(pts, 0, n, VecDComparer.Instance);
+
+            var m = 0;
+            for (var i = 0; i < n; i++)
+            {
+                if (m == 0 || !pts[i].Equals(pts[m - 1]))
+                    pts[m++] = pts[i];
+            }
+
+            if (m <= 1)
+            {
+                if (m == 1) hull[0] = pts[0];
+                return m;
+            }
+
+            var k = 0;
+
+            for (var i = 0; i < m; i++)
+            {
+                while (k >= 2 && (hull[k - 1] - hull[k - 2]).Cross(pts[i] - hull[k - 2]) <= 0)
+                    k--;
+                hull[k++] = pts[i];
+            }
+
+            var t = k + 1;
+            for (var i = m - 2; i >= 0; i--)
+            {
+                while (k >= t && (hull[k - 1] - hull[k - 2]).Cross(pts[i] - hull[k - 2]) <= 0)
+                    k--;
+                hull[k++] = pts[i];
+            }
 
-        var bounds = GetBounds();
-        
-        var minX = bounds.Min(n => n.X);
-        var minY = bounds.Min(n => n.Y);
-        
-        var maxX = bounds.Max(n => n.Right);
-        var maxY = bounds.Max(n => n.Bottom);
+            return k - 1;
+        }
+        finally
+        {
+            pool.Return(pts);
+        }
+    }
 
-        TopLeft = new VecD(minX, minY);
-        BottomRight = new VecD(maxX, maxY);
+    private sealed class VecDComparer : IComparer<VecD>
+    {
+        public static readonly VecDComparer Instance = new();
 
-        Size = BottomRight - TopLeft;
+        public int Compare(VecD a, VecD b)
+        {
+            var cx = a.X.CompareTo(b.X);
+            return cx != 0 ? cx : a.Y.CompareTo(b.Y);
+        }
     }
 
-    private List<RectD> GetBounds()
+    private List<VecD> GetBoundPoints()
     {
-        var list = new List<RectD>();
+        var list = new List<VecD>(Nodes.Count * 4);
+
+        const int defaultXOffset = 30;
+        const int defaultYOffset = 45;
 
-        const int defaultXOffset = -30;
-        const int defaultYOffset = -45;
-        
-        // TODO: Use the actual node height
         foreach (var node in Nodes)
         {
-            if (node == start)
+            var pos = node.PositionBindable;
+            var size = new VecD(node.UiSize.Size.Width, node.UiSize.Size.Height);
+
+            if (node == Start)
             {
-                list.Add(new RectD(node.PositionBindable + new VecD(100, defaultYOffset), new VecD(100, 400)));
+                var twoThirdsX = size.X * (2.0 / 3.0);
+
+                list.Add(pos + new VecD(twoThirdsX, -defaultYOffset));
+                list.Add(pos + new VecD(twoThirdsX, defaultYOffset + size.Y));
+
+                list.Add(pos + new VecD(size.X + defaultXOffset, -defaultYOffset));
+                list.Add(pos + new VecD(size.X + defaultXOffset, defaultYOffset + size.Y));
                 continue;
             }
 
-            if (node == end)
+            if (node == End)
             {
-                list.Add(new RectD(node.PositionBindable + new VecD(defaultXOffset, defaultYOffset), new VecD(100, 400)));
+                var oneThirdX = size.X / 3.0;
+
+                list.Add(pos + new VecD(oneThirdX, -defaultYOffset));
+                list.Add(pos + new VecD(oneThirdX, defaultYOffset + size.Y));
+
+                list.Add(pos + new VecD(-defaultXOffset, -defaultYOffset));
+                list.Add(pos + new VecD(-defaultXOffset, defaultYOffset + size.Y));
                 continue;
             }
-            
-            list.Add(new RectD(node.PositionBindable + new VecD(defaultXOffset, defaultYOffset), new VecD(200, 400)));
+
+            var right = defaultXOffset + size.X;
+            var bottom = defaultYOffset + size.Y;
+
+            list.Add(pos + new VecD(-defaultXOffset, -defaultYOffset));
+            list.Add(pos + new VecD(right, -defaultYOffset));
+            list.Add(pos + new VecD(-defaultXOffset, bottom));
+            list.Add(pos + new VecD(right, bottom));
         }
-        
+
         return list;
     }
 }

+ 19 - 0
src/PixiEditor/ViewModels/Nodes/Traverse.cs

@@ -0,0 +1,19 @@
+namespace PixiEditor.ViewModels.Nodes;
+
+public enum Traverse
+{
+    /// <summary>
+    /// Go further in this direction, meaning any further child connections will not be enqueued.
+    /// </summary>
+    Further,
+    
+    /// <summary>
+    /// Don't go further in this direction, meaning all further child connections will be enqueued.
+    /// </summary>
+    NoFurther,
+    
+    /// <summary>
+    /// Completely stop traversing in any direction, meaning this will drop all enqueued child connections.
+    /// </summary>
+    Exit
+}

+ 4 - 0
src/PixiEditor/ViewModels/PixiObservableObject.cs

@@ -1,5 +1,6 @@
 using System.ComponentModel;
 using CommunityToolkit.Mvvm.ComponentModel;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings;
 using PixiEditor.Models.Commands;
 
 namespace PixiEditor.ViewModels;
@@ -11,4 +12,7 @@ public class PixiObservableObject : ObservableObject
         base.OnPropertyChanged(e);
         CommandController.Current.NotifyPropertyChanged(e.PropertyName);
     }
+
+    protected void SubscribeSettingsValueChanged<T>(Setting<T> settingStore, string propertyName) =>
+        settingStore.ValueChanged += (_, _) => OnPropertyChanged(propertyName);
 }

+ 16 - 3
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -170,11 +170,23 @@ internal partial class SettingsWindowViewModel : ViewModelBase
         {
             Patterns = fileTypes.SelectMany(a => a.Patterns).ToList(),
         });
-        
+
+        IStorageFolder? suggestedLocation = null;
+        try
+        {
+            suggestedLocation =
+                await MainWindow.Current!.StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents);
+        }
+        catch (Exception)
+        {
+            // If we can't get the documents folder, we will just use the default location
+            // This is not a critical error, so we can ignore it
+        }
+
         IReadOnlyList<IStorageFile> files = await MainWindow.Current!.StorageProvider.OpenFilePickerAsync(new()
         {
             AllowMultiple = false,
-            SuggestedStartLocation = await MainWindow.Current!.StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents),
+            SuggestedStartLocation = suggestedLocation,
             FileTypeFilter = fileTypes,
         });
         
@@ -272,7 +284,8 @@ internal partial class SettingsWindowViewModel : ViewModelBase
             new("KEY_BINDINGS"),
             new SettingsPage("UPDATES"),
             new("EXPORT"),
-            new SettingsPage("SCENE")
+            new SettingsPage("SCENE"),
+            new("PERFORMANCE")
         };
 
         ILocalizationProvider.Current.OnLanguageChanged += OnLanguageChanged;

+ 17 - 0
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -63,6 +63,19 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         }
     }
 
+    public bool SelectionTintingEnabled
+    {
+        get => PixiEditorSettings.Tools.SelectionTintingEnabled.Value;
+        set
+        {
+            if (SelectionTintingEnabled == value)
+                return;
+
+            PixiEditorSettings.Tools.SelectionTintingEnabled.Value = value;
+            OnPropertyChanged();
+        }
+    }
+
     private Cursor? toolCursor;
 
     public Cursor? ToolCursor
@@ -118,6 +131,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
     {
         owner.DocumentManagerSubViewModel.ActiveDocumentChanged += ActiveDocumentChanged;
         PixiEditorSettings.Tools.PrimaryToolset.ValueChanged += PrimaryToolsetOnValueChanged;
+        SubscribeSettingsValueChanged(PixiEditorSettings.Tools.SelectionTintingEnabled, nameof(SelectionTintingEnabled));
     }
 
     private void PrimaryToolsetOnValueChanged(Setting<string> setting, string? newPrimaryToolset)
@@ -174,6 +188,9 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         OnPropertyChanged(nameof(NonSelectedToolSets));
     }
 
+    [Command.Basic("PixiEditor.Tools.ToggleSelectionTinting", "TOGGLE_TINTING_SELECTION", "TOGGLE_TINTING_SELECTION_DESCRIPTIVE", AnalyticsTrack = true)]
+    public void ToggleTintSelection() => SelectionTintingEnabled = !SelectionTintingEnabled;
+
     public void SetupToolsTooltipShortcuts()
     {
         foreach (IToolHandler tool in allTools)

+ 19 - 0
src/PixiEditor/ViewModels/SubViewModels/ViewOptionsViewModel.cs

@@ -1,6 +1,9 @@
 using Avalonia.Input;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Models.Preferences;
 using PixiEditor.UI.Common.Fonts;
+using PixiEditor.ViewModels.UserPreferences.Settings;
 
 namespace PixiEditor.ViewModels.SubViewModels;
 #nullable enable
@@ -36,9 +39,25 @@ internal class ViewOptionsViewModel : SubViewModel<ViewModelMain>
         }
     }
 
+    private int maxBilinearSampleSize = 4096;
+    public int MaxBilinearSampleSize
+    {
+        get => maxBilinearSampleSize;
+        set
+        {
+            SetProperty(ref maxBilinearSampleSize, value);
+        }
+    }
+
     public ViewOptionsViewModel(ViewModelMain owner)
         : base(owner)
     {
+        MaxBilinearSampleSize = PixiEditorSettings.Performance.MaxBilinearSampleSize.Value;
+
+        PixiEditorSettings.Performance.MaxBilinearSampleSize.ValueChanged += (s, e) =>
+        {
+            MaxBilinearSampleSize = PixiEditorSettings.Performance.MaxBilinearSampleSize.Value;
+        };
     }
 
     [Command.Basic("PixiEditor.View.ToggleGrid", "TOGGLE_GRIDLINES", "TOGGLE_GRIDLINES", Key = Key.OemTilde,

+ 4 - 3
src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs

@@ -175,7 +175,8 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         PixiEditorSettings.Scene.PrimaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
         PixiEditorSettings.Scene.SecondaryBackgroundColor.ValueChanged += UpdateBackgroundBitmap;
 
-        previewPainterControl = new PreviewPainterControl(Document.PreviewPainter,
+        previewPainterControl = new PreviewPainterControl(
+            Document.MiniPreviewPainter,
             Document.AnimationDataViewModel.ActiveFrameTime.Frame);
         TabCustomizationSettings.Icon = previewPainterControl;
     }
@@ -187,9 +188,9 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         {
             OnPropertyChanged(nameof(Title));
         }
-        else if (e.PropertyName == nameof(DocumentViewModel.PreviewPainter))
+        else if (e.PropertyName == nameof(DocumentViewModel.MiniPreviewPainter))
         {
-            previewPainterControl.PreviewPainter = Document.PreviewPainter;
+            previewPainterControl.PreviewPainter = Document.MiniPreviewPainter;
             previewPainterControl.FrameToRender = Document.AnimationDataViewModel.ActiveFrameTime.Frame;
         }
         else if (e.PropertyName == nameof(DocumentViewModel.AllChangesSaved))

+ 28 - 0
src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs

@@ -2,6 +2,7 @@
 using System.Drawing;
 using System.Linq;
 using System.Threading.Tasks;
+using Avalonia.Controls;
 using Avalonia.Input;
 using CommunityToolkit.Mvvm.Input;
 using Drawie.Numerics;
@@ -303,4 +304,31 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>, IWindowHandler
         popup.Show();
         return popup;
     }
+
+    /// <summary>
+    /// Used to save the WindowState before toggling to FullScreen-Mode.
+    /// </summary>
+    private WindowState LastWindowState { get; set; }
+
+    /// <summary>
+    /// Method used to toggle to FullScreen-Mode.
+    /// </summary>
+    [Commands_Command.Basic("PixiEditor.Window.ToggleFullscreen", "TOGGLE_FULLSCREEN", "TOGGLE_FULLSCREEN_DESCRIPTIVE",
+        Key = Key.F11,
+        Icon = PixiPerfectIcons.Fullscreen,
+        AnalyticsTrack = true)]
+    public void ToggleFullscreen()
+    {
+        var window = Owner.AttachedWindow;
+
+        if (window is null) return;
+
+        if (window.WindowState != WindowState.FullScreen) LastWindowState = window.WindowState;
+
+        window.WindowState = window.WindowState switch
+        {
+            WindowState.FullScreen => LastWindowState,
+            _ => WindowState.FullScreen
+        };
+    }
 }

+ 21 - 0
src/PixiEditor/ViewModels/UserPreferences/Settings/PerformanceSettings.cs

@@ -0,0 +1,21 @@
+using PixiEditor.Extensions.CommonApi.UserPreferences;
+
+namespace PixiEditor.ViewModels.UserPreferences.Settings;
+
+internal class PerformanceSettings : SettingsGroup
+{
+    private bool disablePreviews = GetPreference(PreferencesConstants.DisablePreviews, PreferencesConstants.DisablePreviewsDefault);
+    private int maxBilinearSize = GetPreference(PreferencesConstants.MaxBilinearSampleSize, PreferencesConstants.MaxBilinearSampleSizeDefault);
+
+    public bool DisablePreviews
+    {
+        get => disablePreviews;
+        set => RaiseAndUpdatePreference(ref disablePreviews, value, PreferencesConstants.DisablePreviews);
+    }
+
+    public int MaxBilinearSize
+    {
+        get => maxBilinearSize;
+        set => RaiseAndUpdatePreference(ref maxBilinearSize, value, PreferencesConstants.MaxBilinearSampleSize);
+    }
+}

+ 9 - 0
src/PixiEditor/ViewModels/UserPreferences/Settings/SceneSettings.cs

@@ -3,6 +3,7 @@ using Avalonia.Media;
 using CommunityToolkit.Mvvm.Input;
 using Drawie.Numerics;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Helpers.Extensions;
 
 namespace PixiEditor.ViewModels.UserPreferences.Settings;
@@ -44,6 +45,12 @@ internal class SceneSettings : SettingsGroup
         set => RaiseAndUpdatePreference(ref _secondaryBackgroundColorHex, value, PreferencesConstants.SecondaryBackgroundColor);
     }
 
+    public bool SelectionTintingEnabled
+    {
+        get => PixiEditorSettings.Tools.SelectionTintingEnabled.Value;
+        set => RaiseAndUpdatePreference(PixiEditorSettings.Tools.SelectionTintingEnabled, value);
+    }
+
     public Color PrimaryBackgroundColor
     {
         get => Color.Parse(PrimaryBackgroundColorHex);
@@ -65,5 +72,7 @@ internal class SceneSettings : SettingsGroup
             PrimaryBackgroundColorHex = PreferencesConstants.PrimaryBackgroundColorDefault;
             SecondaryBackgroundColorHex = PreferencesConstants.SecondaryBackgroundColorDefault;
         });
+        
+        SubscribeValueChanged(PixiEditorSettings.Tools.SelectionTintingEnabled, nameof(SelectionTintingEnabled));
     }
 }

+ 16 - 1
src/PixiEditor/ViewModels/UserPreferences/SettingsGroup.cs

@@ -1,10 +1,11 @@
 using System.Runtime.CompilerServices;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
+using PixiEditor.Extensions.CommonApi.UserPreferences.Settings;
 
 namespace PixiEditor.ViewModels.UserPreferences;
 
-internal class SettingsGroup : ObservableObject
+internal class SettingsGroup : PixiObservableObject
 {
     protected static T GetPreference<T>(string name)
     {
@@ -27,4 +28,18 @@ internal class SettingsGroup : ObservableObject
         SetProperty(ref backingStore, value, propertyName: name);
         IPreferences.Current.UpdatePreference(name, value);
     }
+    
+    protected void RaiseAndUpdatePreference<T>(Setting<T> settingStore, T value, [CallerMemberName] string name = "")
+    {
+        if (EqualityComparer<T>.Default.Equals(settingStore.Value, value))
+            return;
+
+        settingStore.Value = value;
+        OnPropertyChanged(name);
+    }
+
+    protected void SubscribeValueChanged<T>(Setting<T> settingStore, string propertyName)
+    {
+        settingStore.ValueChanged += (_, _) => OnPropertyChanged(propertyName);
+    }
 }

+ 2 - 0
src/PixiEditor/ViewModels/UserPreferences/SettingsViewModel.cs

@@ -17,6 +17,8 @@ internal class SettingsViewModel : SubViewModel<SettingsWindowViewModel>
 
     public SceneSettings Scene { get; set; } = new();
 
+    public PerformanceSettings Performance { get; set; } = new();
+
     public SettingsViewModel(SettingsWindowViewModel owner)
         : base(owner)
     {

+ 1 - 0
src/PixiEditor/Views/Dock/DocumentTemplate.axaml

@@ -45,6 +45,7 @@
         CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, Mode=OneWay}"
         BackgroundBitmap="{Binding BackgroundBitmap, Mode=OneWay}"
         HudVisible="{Binding HudVisible}"
+        MaxBilinearSamplingSize="{Binding ViewportSubViewModel.MaxBilinearSampleSize, Source={viewModels1:MainVM}}"
         Document="{Binding Document}">
     </viewportControls:Viewport>
 </UserControl>

+ 5 - 9
src/PixiEditor/Views/Main/CommandSearch/CommandSearchControl.axaml.cs

@@ -184,11 +184,11 @@ internal partial class CommandSearchControl : UserControl, INotifyPropertyChange
         }
         else if (e.Key is Key.Down or Key.PageDown)
         {
-            MoveSelection(1);
+            MoveSelection(NextToDirection.Forwards);
         }
         else if (e.Key is Key.Up or Key.PageUp)
         {
-            MoveSelection(-1);
+            MoveSelection(NextToDirection.Backwards);
         }
         else if (e.Key == Key.Escape ||
                  CommandController.Current.Commands["PixiEditor.Search.Toggle"].Shortcut
@@ -266,22 +266,18 @@ internal partial class CommandSearchControl : UserControl, INotifyPropertyChange
         }
     }
 
-    private void MoveSelection(int delta)
+    private void MoveSelection(NextToDirection direction)
     {
-        if (delta == 0)
-            return;
         if (SelectedResult is null)
         {
             SelectedResult = Results.FirstOrDefault(x => x.CanExecute);
             return;
         }
 
-        int newIndex = Results.IndexOf(SelectedResult) + delta;
+        var newIndex = Results.IndexOf(SelectedResult) + (int)direction;
         newIndex = (newIndex % Results.Count + Results.Count) % Results.Count;
 
-        SelectedResult = delta > 0
-            ? Results.IndexOrNext(x => x.CanExecute, newIndex)
-            : Results.IndexOrPrevious(x => x.CanExecute, newIndex);
+        SelectedResult = Results.IndexOrNextInDirection(x => x.CanExecute, newIndex, direction);
 
         newIndex = Results.IndexOf(SelectedResult);
         itemscontrol.ContainerFromIndex(newIndex)?.BringIntoView();

+ 1 - 0
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -257,6 +257,7 @@
             CustomBackgroundScaleX="{Binding CustomBackgroundScaleX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             BackgroundBitmap="{Binding BackgroundBitmap, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
+            MaxBilinearSamplingSize="{Binding MaxBilinearSamplingSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             PointerPressed="Scene_OnContextMenuOpening"
             ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">
             <rendering:Scene.ContextFlyout>

+ 8 - 0
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -382,6 +382,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
     private MouseUpdateController? mouseUpdateController;
     private ViewportOverlays builtInOverlays = new();
+    public static readonly StyledProperty<int> MaxBilinearSamplingSizeProperty
+        = AvaloniaProperty.Register<Viewport, int>("MaxBilinearSamplingSize", 4096);
 
     public static readonly StyledProperty<bool> SnappingEnabledProperty =
         AvaloniaProperty.Register<Viewport, bool>("SnappingEnabled");
@@ -445,6 +447,12 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         set { SetValue(AvailableRenderOutputsProperty, value); }
     }
 
+    public int MaxBilinearSamplingSize
+    {
+        get { return (int)GetValue(MaxBilinearSamplingSizeProperty); }
+        set { SetValue(MaxBilinearSamplingSizeProperty, value); }
+    }
+
     private void ForceRefreshFinalImage()
     {
         Scene.InvalidateVisual();

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно