Bläddra i källkod

Localization API

flabbet 1 år sedan
förälder
incheckning
8329f38a94

+ 4 - 0
samples/LocalizationSample/Localization/en.json

@@ -0,0 +1,4 @@
+{
+  "LOC_SAM:HELLO_WORLD": "Hello World!",
+  "LOC_SAM:HELLO_WORLD_ARGS": "Hello World {0}!"
+}

+ 35 - 0
samples/LocalizationSample/LocalizationSample.csproj

@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
+        <OutputType>Exe</OutputType>
+        <PublishTrimmed>true</PublishTrimmed>
+        <WasmSingleFileBundle>true</WasmSingleFileBundle>
+        <GenerateExtensionPackage>true</GenerateExtensionPackage>
+        <PixiExtOutputPath>..\..\src\PixiEditor.AvaloniaUI.Desktop\bin\Debug\net8.0\win-x64\Extensions</PixiExtOutputPath>
+        <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Remove="extension.json" />
+        <Content Include="extension.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+    
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\PixiEditor.Extensions.Wasm\PixiEditor.Extensions.Wasm.csproj" />
+    </ItemGroup>
+
+    <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
+    <Import Project="..\..\src\PixiEditor.Extensions.Wasm\build\PixiEditor.Extensions.Wasm.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Wasm\build\PixiEditor.Extensions.Wasm.targets" />
+
+</Project>

+ 25 - 0
samples/LocalizationSample/LocalizationSampleExtension.cs

@@ -0,0 +1,25 @@
+using PixiEditor.Extensions.Wasm.Api.Localization;
+
+namespace HelloWorld;
+
+using PixiEditor.Extensions.Wasm;
+
+public class LocalizationSampleExtension : WasmExtension
+{
+    /// <summary>
+    ///     This method is called when extension is loaded.
+    ///  All extensions are first loaded and then initialized. This method is called before <see cref="OnInitialized"/>.
+    /// </summary>
+    public override void OnLoaded()
+    {
+    }
+
+    /// <summary>
+    ///     This method is called when extension is initialized. After this method is called, you can use Api property to access PixiEditor API.
+    /// </summary>
+    public override void OnInitialized()
+    {
+        Api.Logger.Log(new LocalizedString("LOC_SAM:HELLO_WORLD"));
+        Api.Logger.Log(new LocalizedString("LOC_SAM:HELLO_WORLD_ARGS", "John Doe"));
+    }
+}

+ 14 - 0
samples/LocalizationSample/Program.cs

@@ -0,0 +1,14 @@
+namespace HelloWorld;
+
+public static class Program
+{
+    /// <summary>
+    ///     The entry point of the application. This will be executed when extension is loaded.
+    /// You can use this method, but there are special methods that are used for initialization. You won't be able to access PixiEditor Api at this point.
+    /// See <see cref="LocalizationSampleExtension"/> for more information.
+    /// </summary>
+    public static void Main()
+    {
+
+    }
+}

+ 13 - 0
samples/LocalizationSample/README.md

@@ -0,0 +1,13 @@
+This sample shows how to create translations for you extension and how to 
+use it in your extension.
+
+Localization data is automatically loaded from the `Localization` folder in
+your project, make sure to copy it to output directory with
+
+```xml
+    <ItemGroup>
+        <Content Include="Localization\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
+    </ItemGroup>
+```

+ 36 - 0
samples/LocalizationSample/extension.json

@@ -0,0 +1,36 @@
+{
+  "displayName": "Sample Extension - Localization",
+  "uniqueName": "yourCompany.Samples.Localization",
+  "description": "Sample localization extension for PixiEditor",
+  "localization": {
+    "languages": [
+      {
+        "name": "English",
+        "code": "en",
+        "localeFileName": "Localization/en.json"
+      }
+    ]
+  },
+  "version": "1.0.0",
+  "author": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "publisher": {
+    "name": "PixiEditor",
+    "email": "[email protected]",
+    "website": "https://pixieditor.net"
+  },
+  "contributors": [
+    {
+      "name": "flabbet",
+      "email": "[email protected]",
+      "website": "https://github.com/flabbet"
+    }
+  ],
+  "license": "MIT",
+  "categories": [
+    "Extension"
+  ]
+}

+ 6 - 0
samples/PixiEditorExtensionSamples.sln

@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.Extensions.Wasm"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloWorld", "HelloWorld\HelloWorld.csproj", "{82A85041-A666-42DB-8F84-7D91EF9A5C9D}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalizationSample", "LocalizationSample\LocalizationSample.csproj", "{3201A287-5103-48F1-9005-E27B5025E6FA}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -24,5 +26,9 @@ Global
 		{82A85041-A666-42DB-8F84-7D91EF9A5C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{82A85041-A666-42DB-8F84-7D91EF9A5C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{82A85041-A666-42DB-8F84-7D91EF9A5C9D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3201A287-5103-48F1-9005-E27B5025E6FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3201A287-5103-48F1-9005-E27B5025E6FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3201A287-5103-48F1-9005-E27B5025E6FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3201A287-5103-48F1-9005-E27B5025E6FA}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 EndGlobal

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Localization/LocalizationProvider.cs

@@ -90,7 +90,7 @@ internal class LocalizationProvider : ILocalizationProvider
                 continue;
             }
 
-            localizationData.MergeWith(extension.Metadata.Localization.Languages, Path.GetDirectoryName(extension.Assembly.Location));
+            localizationData.MergeWith(extension.Metadata.Localization.Languages, Path.GetDirectoryName(extension.Location));
         }
     }
 

+ 83 - 0
src/PixiEditor.Extensions.Wasm/Api/Localization/LocalizedString.cs

@@ -0,0 +1,83 @@
+namespace PixiEditor.Extensions.Wasm.Api.Localization;
+
+public struct LocalizedString
+{
+    private string key;
+    public string Key
+    {
+        get => key;
+        set
+        {
+            key = value;
+            Value = GetValue(value);
+        }
+    }
+    
+    public string Value { get; private set; }
+    public object[]? Parameters { get; set; }
+    
+    public LocalizedString(string key)
+    {
+        Key = key;
+    }
+
+    public LocalizedString(string key, params object[]? parameters)
+    {
+        Parameters = parameters;
+        Key = key;
+    }
+    
+    public override string ToString()
+    {
+        return Value;
+    }
+    
+    private string GetValue(string localizationKey)
+    {
+        if (string.IsNullOrEmpty(localizationKey))
+        {
+            return localizationKey;
+        }
+        
+        string translated = Interop.translate_key(localizationKey);
+        if (translated == null)
+        {
+            return localizationKey;
+        }
+
+        return ApplyParameters(translated);
+    }
+    
+    private string ApplyParameters(string value)
+    {
+        if (Parameters == null || Parameters.Length == 0)
+        {
+            return value;
+        }
+
+        try
+        {
+            var executedParameters = new object[Parameters.Length];
+            for (var i = 0; i < Parameters.Length; i++)
+            {
+                var parameter = Parameters[i];
+                object objToExecute = parameter;
+                if (parameter is LocalizedString str)
+                {
+                    objToExecute = new LocalizedString(str.Key, str.Parameters).Value;
+                }
+
+                executedParameters[i] = objToExecute;
+            }
+
+            return string.Format(value, executedParameters);
+        }
+        catch (FormatException)
+        {
+            return value;
+        }
+    }
+
+    public static implicit operator LocalizedString(string key) => new(key);
+    public static implicit operator string(LocalizedString localizedString) => localizedString.Value;
+}

+ 9 - 0
src/PixiEditor.Extensions.Wasm/Interop.Localization.cs

@@ -0,0 +1,9 @@
+using System.Runtime.CompilerServices;
+
+namespace PixiEditor.Extensions.Wasm;
+
+internal static partial class Interop
+{
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    internal static extern string translate_key(string key);
+}

+ 13 - 0
src/PixiEditor.Extensions.WasmRuntime/Api/LocalizationApi.cs

@@ -0,0 +1,13 @@
+using PixiEditor.Extensions.Common.Localization;
+
+namespace PixiEditor.Extensions.WasmRuntime.Api;
+
+internal class LocalizationApi : ApiGroupHandler
+{
+    [ApiFunction("translate_key")]
+    public static string TranslateKey(string key)
+    {
+        LocalizedString localizedString = new LocalizedString(key);
+        return localizedString.Value;
+    }
+}

+ 2 - 1
src/PixiEditor.Extensions.WasmRuntime/Api/WindowingApi.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.FlyUI.Elements;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Extensions.FlyUI.Elements;
 using PixiEditor.Extensions.WasmRuntime.Utilities;
 using PixiEditor.Extensions.Windowing;
 

+ 6 - 1
src/PixiEditor.Extensions.WasmRuntime/WasmExtensionInstance.cs

@@ -24,13 +24,18 @@ public partial class WasmExtensionInstance : Extension
     private AsyncCallsManager AsyncHandleManager { get; set; }
     private WasmMemoryUtility WasmMemoryUtility { get; set; }
 
+    private string modulePath;
+    
+    public override string Location => modulePath;
+
     partial void LinkApiFunctions();
 
-    public WasmExtensionInstance(Linker linker, Store store, Module module)
+    public WasmExtensionInstance(Linker linker, Store store, Module module, string path)
     {
         Linker = linker;
         Store = store;
         Module = module;
+        modulePath = path;
     }
 
     public void Instantiate()

+ 1 - 1
src/PixiEditor.Extensions.WasmRuntime/WasmRuntime.cs

@@ -31,6 +31,6 @@ public class WasmRuntime
         var store = new Store(engine);
         store.SetWasiConfiguration(wasiConfig);
         linker.DefineWasi();
-        return new WasmExtensionInstance(linker, store, module);
+        return new WasmExtensionInstance(linker, store, module, path);
     }
 }

+ 1 - 0
src/PixiEditor.Extensions/Extension.cs

@@ -12,6 +12,7 @@ public abstract class Extension
     public ExtensionServices Api { get; private set; }
     public ExtensionMetadata Metadata { get; private set; }
     public Assembly Assembly => GetType().Assembly;
+    public virtual string Location => Assembly.Location;
 
     public void ProvideMetadata(ExtensionMetadata metadata)
     {

+ 1 - 2
src/PixiEditor.WasmApi.Gen/MethodBodyRewriter.cs

@@ -13,12 +13,11 @@ public class MethodBodyRewriter : CSharpSyntaxRewriter
         MethodSemanticModel = methodSemanticModel;
     }
 
-
     public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
     {
         var symbol = MethodSemanticModel.GetSymbolInfo(node).Symbol;
 
-        if (symbol is not INamedTypeSymbol { Kind: SymbolKind.NamedType, TypeKind: TypeKind.Class or TypeKind.Enum } namedTypeSymbol)
+        if (symbol is not INamedTypeSymbol { Kind: SymbolKind.NamedType } namedTypeSymbol)
         {
             return base.VisitIdentifierName(node);
         }