From 7d1b2d643e26b5e568a9317ac26a2325d3b74140 Mon Sep 17 00:00:00 2001 From: hvboq Date: Sat, 30 May 2026 12:55:25 +0900 Subject: [PATCH 1/2] Add LM Studio translator endpoint --- README.md | 12 ++-- .../ConfigTests.cs | 32 ++++++++++ .../Config/Configuration.cs | 2 +- .../ILRepack.targets | 16 +++++ .../LmStudioTranslatorEndpoint.cs | 59 +++++++++++++++++++ .../SampleConfig/LmStudio.yaml | 58 ++++++++++++++++++ ...Unity.AutoTranslator.LlmTranslators.csproj | 32 +++++----- 7 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 XUnity.AutoTranslator.LlmTranslators/ILRepack.targets create mode 100644 XUnity.AutoTranslator.LlmTranslators/LmStudioTranslatorEndpoint.cs create mode 100644 XUnity.AutoTranslator.LlmTranslators/SampleConfig/LmStudio.yaml diff --git a/README.md b/README.md index 659bbd1..5b2009e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Current supported plugins include: - Probably the most popular LLM that has the highest quality but is not Free - [Ollama Models](https://ollama.com/) - Ollama is a local hosting option for LLMs. You are able to run one or more llms on your local machine of varying size. This option is free but will require you to engineer your prompts dependant on the model and/or language. +- [LM Studio](https://lmstudio.ai/) + - LM Studio is a local hosting option for OpenAI-compatible chat completion APIs. Start the local server in LM Studio and use the model id shown by LM Studio in `LmStudio.yaml`. # Why use this instead of the [Custom] endpoint? @@ -33,6 +35,7 @@ To configure your LLM you will need to follow the following steps: 2. Open the config for the LLMTranslator you wish to use - If OpenaI: `OpenAi.Yml` - If a local Olama LLM: `Ollama.Yml` + - If a local LM Studio LLM: `LmStudio.Yml` 3. Update your config with any API keys, custom urls, glossaries and system prompts. 4. Finally update your AutoTranslator INI file with your translate service - ``` @@ -42,6 +45,7 @@ To configure your LLM you will need to follow the following steps: ``` - If OpenAi: `OpenAiTranslate` - If a local Olama LLM: `OllamaTranslate` + - If a local LM Studio LLM: `LmStudioTranslate` ## Global API Key @@ -55,11 +59,11 @@ We also use global environment variables so you can just set your API Key once a We have seperate files that can be override any config you have loaded in your config file. This makes it easier to publish game specific prompts, glossaries or just make it easier to use multi line prompts without having to worry about YAML formatting. These files are: - - `OpenAi-SystemPrompt.txt` or `Ollama-SystemPrompt.txt` + - `OpenAi-SystemPrompt.txt`, `Ollama-SystemPrompt.txt` or `LmStudio-SystemPrompt.txt` - Use this file to update your system prompt - - `OpenAi-GlossaryPrompt.txt` or `Ollama-GlossaryPrompt.txt` + - `OpenAi-GlossaryPrompt.txt`, `Ollama-GlossaryPrompt.txt` or `LmStudio-GlossaryPrompt.txt` - Use this file to update your glossary prompt - - `OpenAi-ApiKey.txt` or `Ollama-ApiKey.txt` + - `OpenAi-ApiKey.txt`, `Ollama-ApiKey.txt` or `LmStudio-ApiKey.txt` - Use this file to update your API Key # Glossary @@ -98,4 +102,4 @@ A test project is included with the project. The [PromptTests](./XUnity.AutoTran # Packages -The assemblies included are the Dev versions of XUnity.AutoTranslator. Feel free to star/fork this repo however you like. \ No newline at end of file +The assemblies included are the Dev versions of XUnity.AutoTranslator. Feel free to star/fork this repo however you like. diff --git a/XUnity.AutoTranslator.LlmTranslators.Tests/ConfigTests.cs b/XUnity.AutoTranslator.LlmTranslators.Tests/ConfigTests.cs index 11ea2c4..b824552 100644 --- a/XUnity.AutoTranslator.LlmTranslators.Tests/ConfigTests.cs +++ b/XUnity.AutoTranslator.LlmTranslators.Tests/ConfigTests.cs @@ -14,4 +14,36 @@ public void TestDefaultConfig() Assert.True(config.SystemPrompt!.Split("\n").Length > 1); } + + [Fact] + public void TestLmStudioConfig() + { + var config = Configuration.GetConfiguration($"{sampleDirectory}/LmStudio.yaml"); + + Assert.Equal("http://localhost:1234/v1/chat/completions", config.Url); + Assert.Equal("model-identifier", config.Model); + Assert.False(config.ApiKeyRequired); + Assert.True(config.SystemPrompt!.Split("\n").Length > 1); + } + + [Fact] + public void TestGlossaryPromptOverride() + { + var config = new LlmConfig { SystemPrompt = "System prompt", GlossaryPrompt = "Glossary prompt" }; + var file = Path.GetTempFileName(); + + try + { + File.WriteAllText(file, "Override glossary prompt"); + + Configuration.LoadGlossaryPrompt(config, file); + + Assert.Equal("System prompt", config.SystemPrompt); + Assert.Equal("Override glossary prompt", config.GlossaryPrompt); + } + finally + { + File.Delete(file); + } + } } diff --git a/XUnity.AutoTranslator.LlmTranslators/Config/Configuration.cs b/XUnity.AutoTranslator.LlmTranslators/Config/Configuration.cs index 63d08ec..9109123 100644 --- a/XUnity.AutoTranslator.LlmTranslators/Config/Configuration.cs +++ b/XUnity.AutoTranslator.LlmTranslators/Config/Configuration.cs @@ -88,7 +88,7 @@ public static void LoadGlossaryPrompt(LlmConfig config, string file) if (!File.Exists(file)) return; - config.SystemPrompt = File.ReadAllText(file, Encoding.UTF8); + config.GlossaryPrompt = File.ReadAllText(file, Encoding.UTF8); } public static void LoadApiKey(LlmConfig config, string file) diff --git a/XUnity.AutoTranslator.LlmTranslators/ILRepack.targets b/XUnity.AutoTranslator.LlmTranslators/ILRepack.targets new file mode 100644 index 0000000..0334c03 --- /dev/null +++ b/XUnity.AutoTranslator.LlmTranslators/ILRepack.targets @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/XUnity.AutoTranslator.LlmTranslators/LmStudioTranslatorEndpoint.cs b/XUnity.AutoTranslator.LlmTranslators/LmStudioTranslatorEndpoint.cs new file mode 100644 index 0000000..c65ead7 --- /dev/null +++ b/XUnity.AutoTranslator.LlmTranslators/LmStudioTranslatorEndpoint.cs @@ -0,0 +1,59 @@ +using SimpleJSON; +using System.Net; +using XUnity.AutoTranslator.LlmTranslators.Behavior; +using XUnity.AutoTranslator.LlmTranslators.Config; +using XUnity.AutoTranslator.Plugin.Core.Endpoints; +using XUnity.AutoTranslator.Plugin.Core.Endpoints.Http; +using XUnity.AutoTranslator.Plugin.Core.Web; + +public class LmStudioTranslatorEndpoint : HttpEndpoint +{ + public override string Id => "LmStudioTranslate"; + public override string FriendlyName => "LM Studio Translate"; + public override int MaxTranslationsPerRequest => 1; + + // Local models can saturate quickly; keep this closer to Ollama's default. + public override int MaxConcurrency => 5; + + private LlmConfig _config = new(); + + public override void Initialize(IInitializationContext context) + { + string folder = Configuration.CalculateConfigFolder(); + var file = Path.Combine(folder, "LmStudio.yaml"); + _config = Configuration.GetConfiguration(file); + Configuration.LoadGlossary(_config, "LmStudio-Glossary.yaml"); + + // Remove artificial delays + context.SetTranslationDelay(0.1f); + context.DisableSpamChecks(); + + if (string.IsNullOrEmpty(_config.ApiKey) && _config.ApiKeyRequired) + throw new Exception("The endpoint requires an API key which has not been provided."); + } + + public override void OnCreateRequest(IHttpRequestCreationContext context) + { + var requestData = BaseEndpointBehavior.GetRequestData(_config, context.UntranslatedText); + + var request = new XUnityWebRequest("POST", _config.Url, requestData); + request.Headers[HttpRequestHeader.ContentType] = "application/json"; + + if (_config.ApiKeyRequired) + request.Headers[HttpRequestHeader.Authorization] = $"Bearer {_config.ApiKey}"; + + context.Complete(request); + } + + public override void OnExtractTranslation(IHttpTranslationExtractionContext context) + { + var data = context.Response.Data; + + var jsonResponse = JSON.Parse(data); + var result = jsonResponse["choices"]?[0]?["message"]?["content"]?.ToString() ?? string.Empty; + result = BaseEndpointBehavior.ValidateAndCleanupTranslation(context.UntranslatedText, result, _config); + + if (MaxTranslationsPerRequest == 1) + context.Complete(result); + } +} diff --git a/XUnity.AutoTranslator.LlmTranslators/SampleConfig/LmStudio.yaml b/XUnity.AutoTranslator.LlmTranslators/SampleConfig/LmStudio.yaml new file mode 100644 index 0000000..3772b37 --- /dev/null +++ b/XUnity.AutoTranslator.LlmTranslators/SampleConfig/LmStudio.yaml @@ -0,0 +1,58 @@ +apiKey: "None" +apiKeyRequired: false +url: "http://localhost:1234/v1/chat/completions" +model: "model-identifier" +modelParams: + temperature: 0.2 + max_tokens: 4096 + top_p: 0.9 + top_k: 40 + frequency_penalty: 0 + presence_penalty: 0 +systemPrompt: | + Your task is to accurately translate Chinese strings provided into fluent English, maintaining the original tone and meaning without adding explanations or embellishments. + + #### Guidelines: + + **Output** + - Do not include additional HTML or markdown. + + **Gender-Neutral Language:** + - Default to gender-neutral language and pronouns ("they/them"), unless specified by the text. + + **Character References:** + - Refer to characters by their names or roles, avoiding gendered descriptors. + - Use gender-specific terms only if clearly identified in the text or necessary for clarity. + + **Names:** + - Use Pinyin for Chinese names. + + **Nicknames:** + - Translate into culturally appropriate English equivalents, maintaining meaning and sentiment. + + **Title Conversion:** + - Convert titles such as "Doctor," "Lord," or "Master" to their English equivalents. + - Precede names with these titles (e.g., "Brother Li"). + - Use glossary terms for titles as provided, with flexible capitalization but no changes to wording based on context + - Preserve the intended meaning and connotations of each title. + + **Cultural Sensitivity:** + - Incorporate relevant Wuxia or traditional terms as needed. + + **Idioms:** + - Convey the meaning, cultural context, and implications in English. + - Avoid direct transliteration; use literal translations within the text to maintain intended message and tone. + + **Contextual Adaptations:** + - Resolve ambiguities using broader context for clarity. + - Select translations fitting the context and guidelines. + + **Language Refinement:** + - Ensure clarity and accuracy with minimal grammatical changes. + + **Capitalization:** + - Follow standard English grammar rules. +glossaryPrompt: | + #Glossary for Consistent Translations + Prioritise and use the translation when an exact match with the original text is found. + ## Terms diff --git a/XUnity.AutoTranslator.LlmTranslators/XUnity.AutoTranslator.LlmTranslators.csproj b/XUnity.AutoTranslator.LlmTranslators/XUnity.AutoTranslator.LlmTranslators.csproj index 7f0a612..6404ccf 100644 --- a/XUnity.AutoTranslator.LlmTranslators/XUnity.AutoTranslator.LlmTranslators.csproj +++ b/XUnity.AutoTranslator.LlmTranslators/XUnity.AutoTranslator.LlmTranslators.csproj @@ -9,6 +9,7 @@ G:\SteamLibrary\steamapps\common\下一站江湖Ⅱ\下一站江湖Ⅱ\下一站江湖Ⅱ_Data\Managed\Translators C:\Program Files (x86)\Steam\steamapps\common\LegendOfMortal\Mortal_Data\Managed\Translators \..\..\..\ + false Debug;Release f11951a3-bacd-43f2-a9fa-0187cf947674 @@ -30,31 +31,28 @@ - + - + - $(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework) + $(MSBuildThisFileDirectory)..\Release\ + $(ReleaseFolder)Translators\ + $(ReleaseFolder)SampleConfig\ + - + - - - - - - - - - - - + + + - - + + + + From 0178686a59757a63007d5952d2853eac2c33ac93 Mon Sep 17 00:00:00 2001 From: DongMin Yeo Date: Sat, 30 May 2026 13:06:53 +0900 Subject: [PATCH 2/2] Document LM Studio configuration --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 5b2009e..9935f5d 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,31 @@ To configure your LLM you will need to follow the following steps: - If a local Olama LLM: `OllamaTranslate` - If a local LM Studio LLM: `LmStudioTranslate` +## LM Studio + +To use LM Studio, start the local server in LM Studio and load the model you want to use for translation. The default LM Studio OpenAI-compatible chat completion URL is: + +```yaml +url: "http://localhost:1234/v1/chat/completions" +``` + +Copy `LmStudio.yaml` into your AutoTranslator config folder, then update the `model` value to the model identifier shown by LM Studio: + +```yaml +apiKey: "None" +apiKeyRequired: false +url: "http://localhost:1234/v1/chat/completions" +model: "model-identifier" +``` + +Finally, set the translator endpoint in `Config.ini`: + +```ini +[Service] +Endpoint=LmStudioTranslate +FallbackEndpoint= +``` + ## Global API Key We also use global environment variables so you can just set your API Key once and never have to think about it again.