Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Build Release Packages

on:
push:
branches:
- master
tags:
- 'v*'
pull_request:
branches:
- master
workflow_dispatch:

jobs:
Comment thread
ManlyMarco marked this conversation as resolved.
build:
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2

- name: Restore solution
run: msbuild XUnity.AutoTranslator.sln /t:Restore /p:Configuration=Release

- name: Build solution (Release)
run: msbuild XUnity.AutoTranslator.sln /p:Configuration=Release /m

- name: Upload release packages
uses: actions/upload-artifact@v4
with:
name: XUnity.AutoTranslator-packages
path: dist/*.zip
if-no-files-found: error

- name: Create GitHub release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
files: dist/*.zip
generate_release_notes: true
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* [Introduction](#introduction)
* [Plugin Frameworks](#plugin-frameworks)
* [Installation](#installation)
* [Building from Source](#building-from-source)
* [Key Mapping](#key-mapping)
* [Translators](#translators)
* [Text Frameworks](#text-frameworks)
Expand All @@ -23,6 +24,15 @@ This is an advanced translator plugin that can be used to translate Unity-based

It does (obviously) go to the internet, in order to provide the automated translation, so if you are not comfortable with that, don't use it.

The plugin also supports a **bilingual / dual-subtitles mode**, which displays both the original text and its translation at the same time instead of replacing it, e.g.:

```
日本語のテキスト
[This is the English translation]
```

See [`BilingualMode` and `BilingualFormat`](#other-options) under [Configuration](#configuration) for details on enabling and customizing this.

If you intend on redistributing this plugin as part of a translation suite for a game, please read [this section](#regarding-redistribution) and the section regarding [manual translations](#manual-translations) so you understand how the plugin operates.

## Plugin Frameworks
Expand Down Expand Up @@ -175,7 +185,24 @@ The file structure should like like this
```

**NOTE:** MonoMod hooks are not supported with this installation method because an outdated version of `Mono.Cecil.dll` is being used with Sybaris.


## Building from Source
The repository can be built on Windows using Visual Studio 2022 (or MSBuild) by opening `XUnity.AutoTranslator.sln` (or `XUnity.AutoTranslator.Koikatsu.sln` for a smaller, Koikatsu-focused build) and building in `Release` configuration. All required reference assemblies are included in the `libs` folder.

Building in `Release` runs each plugin project's `PostBuild` step, which assembles the correct folder layout for each mod loader and zips it via `tools/xzip.exe`, producing the same packages found on the [releases](../../releases) page in the `dist` folder:
* `XUnity.AutoTranslator-BepInEx-{VERSION}.zip`
* `XUnity.AutoTranslator-BepInEx-IL2CPP-{VERSION}.zip`
* `XUnity.AutoTranslator-MelonMod-{VERSION}.zip`
* `XUnity.AutoTranslator-MelonMod-IL2CPP-{VERSION}.zip`
* `XUnity.AutoTranslator-IPA-{VERSION}.zip`
* `XUnity.AutoTranslator-UnityInjector-{VERSION}.zip`
* `XUnity.AutoTranslator-ReiPatcher-{VERSION}.zip`
* `XUnity.AutoTranslator-Developer-{VERSION}.zip` and `XUnity.AutoTranslator-Developer-IL2CPP-{VERSION}.zip`

The `{VERSION}` in each package name comes from the `Version` property in `Directory.Build.props`.

A GitHub Actions workflow (`.github/workflows/build-release.yml`) automates this on `windows-latest`: it builds the solution in `Release` on every push/PR to `master`, uploads the `dist/*.zip` files as a workflow artifact, and additionally creates a GitHub release with these zips attached when a tag matching `v*` is pushed.

## Key Mapping
The following key inputs are mapped:
* ALT + 0: Toggle XUnity AutoTranslator UI. (That's a zero, not an O)
Expand Down Expand Up @@ -363,6 +390,8 @@ ForceMonoModHooks=False ;Indicates that the plugin must use MonoMod hoo
InitializeHarmonyDetourBridge=False ;Indicates the plugin should initial harmony detour bridge which allows harmony hooks to work in an environment where System.Reflection.Emit does not exist (usually such settings are handled by plugin managers, so don't use when using a plugin manager)
RedirectedResourceDetectionStrategy=AppendMongolianVowelSeparatorAndRemoveAll ;Indicates if and how the plugin should attempt to recognize redirected resources in order to prevent double translations. Can be ["None", "AppendMongolianVowelSeparator", "AppendMongolianVowelSeparatorAndRemoveAppended", "AppendMongolianVowelSeparatorAndRemoveAll"]
OutputTooLongText=False ;Indicates if the plugin should output text that exceeds 'MaxCharactersPerTranslation' without translating it
BilingualMode=False ;If True, displays both the original text and the translation simultaneously, formatted according to 'BilingualFormat'
BilingualFormat={original}\n[{translation}] ;Format string used to combine the original and translated text when 'BilingualMode' is enabled. Use '{original}' and '{translation}' as placeholders

[Texture]
TextureDirectory=Translation\{Lang}\Texture ;Directory to dump textures to, and root of directories to load images from. Can use placeholder: {GameExeName}, {Lang}
Expand Down Expand Up @@ -478,6 +507,8 @@ Resizing of a UI component does not refer to changing of it's dimensions, but ra

The configuratiaon `EnableUIResizing` and `ForceUIResizing` also control whether or not manual UI resize behaviour is enabled. See [this section](#ui-font-resizing) for more information.

Note: When `BilingualMode=True` is enabled, `ForceUIResizing` is automatically enabled as well, since text components will need to display both the original and translated text.

#### Font overriding
When translating to languages that use non-ASCII letters the game's default font might not be able to display some of those characters. This is the most common when translating to Chinese. To fix this you can supply your own ccustom font that will be used to display the missing characters (or all text in the game).

Expand Down Expand Up @@ -542,6 +573,8 @@ If MonoMod hooks are not forced they are only used if available and a given meth
* `IgnoreVirtualTextSetterCallingRules`: Indicates that rules for virtual method calls should be ignored when trying to set the text of a text component. May in some cases help setting the text of stubborn components.
* `RedirectedResourceDetectionStrategy`: Indicates if and how the plugin should attempt to recognize redirected resources in order to prevent double translations. Can be ["None", "AppendMongolianVowelSeparator", "AppendMongolianVowelSeparatorAndRemoveAppended", "AppendMongolianVowelSeparatorAndRemoveAll"]
* `OutputTooLongText`: Indicates if the plugin should output text that exceeds 'MaxCharactersPerTranslation' without translating it
* `BilingualMode`: If enabled, displays both the original text and its translation at the same time, instead of replacing the original. The combined text is formatted according to `BilingualFormat`. Enabling this also forces `ForceUIResizing` on, since text components will need to fit more content.
* `BilingualFormat`: Controls how the original and translated text are combined when `BilingualMode` is enabled. Use `{original}` and `{translation}` as placeholders, e.g. `{original}\n[{translation}]` (translation on a new line, in brackets) or `{original} ({translation})` (inline).

## IL2CPP Support
While this plugin offers some level of IL2CPP support, it is by no means complete. The following differences can be observed/features are missing:
Expand Down
46 changes: 44 additions & 2 deletions src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public class AutoTranslationPlugin :

private bool _isInTranslatedMode = true;
private bool _textHooksEnabled = true;
private readonly HashSet<string> _knownBilingualStrings = new HashSet<string>();
Comment thread
ManlyMarco marked this conversation as resolved.

private float _batchOperationSecondCounter = 0;

Expand Down Expand Up @@ -892,10 +893,36 @@ internal void SetTranslatedText( object ui, string translatedText, string origin

if( _isInTranslatedMode && !CallOrigin.ExpectsTextToBeReturned )
{
SetText( ui, translatedText, true, originalText, info );
var textToSet = ComposeBilingualText( originalText, translatedText );
SetText( ui, textToSet, true, originalText, info );
}
}

private string ComposeBilingualText( string originalText, string translatedText )
{
if( !Settings.BilingualMode )
return translatedText;

if( originalText == null || translatedText == null )
return translatedText;

if( string.Equals( originalText, translatedText, StringComparison.Ordinal ) )
return translatedText;

var composed = Settings.BilingualFormat
.Replace( "{original}", originalText )
.Replace( "{translation}", translatedText );
Comment thread
ManlyMarco marked this conversation as resolved.

_knownBilingualStrings.Add( composed );

return composed;
}

private bool IsKnownBilingualComposite( string text )
{
return Settings.BilingualMode && _knownBilingualStrings.Contains( text );
}

/// <summary>
/// Sets the text of a UI text, while ensuring this will not fire a text changed event.
/// </summary>
Expand Down Expand Up @@ -1409,6 +1436,13 @@ private string TranslateImmediate( object ui, string text, TextTranslationInfo i
return null;
}

// in bilingual mode, the text read back from the component may be the composed
// bilingual string instead of the raw translation; treat that the same way
if( IsKnownBilingualComposite( originalText ) )
{
return null;
}

bool shouldIgnore = false;
if( info != null )
{
Expand Down Expand Up @@ -2030,6 +2064,13 @@ private string TranslateOrQueueWebJobImmediate(
return null;
}

// in bilingual mode, the text read back from the component may be the composed
// bilingual string instead of the raw translation; treat that the same way
if( IsKnownBilingualComposite( text ) )
{
return null;
}

bool shouldIgnore = false;
if( info != null )
{
Expand Down Expand Up @@ -3384,7 +3425,8 @@ private void ToggleTranslation()
{
if( tti != null && tti.IsTranslated )
{
SetText( ui, tti.TranslatedText, true, null, tti );
var composed = ComposeBilingualText( tti.OriginalText, tti.TranslatedText );
SetText( ui, composed, true, null, tti );
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ internal static class Settings
public static int MaxClipboardCopyCharacters;
public static float ClipboardDebounceTime;

public static bool BilingualMode = false;
public static string BilingualFormat = "{original}\n[{translation}]";

public static void Configure()
{
try
Expand Down Expand Up @@ -290,6 +293,13 @@ public static void Configure()
ReloadTranslationsOnFileChange = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "ReloadTranslationsOnFileChange", false );
DisableTextMeshProScrollInEffects = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "DisableTextMeshProScrollInEffects", ApplicationName.Equals( "SamuraiVandalism", StringComparison.OrdinalIgnoreCase ) || UnityTypes.UguiNovelText != null );
CacheParsedTranslations = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "CacheParsedTranslations", false );
BilingualMode = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "BilingualMode", false );
BilingualFormat = JsonHelper.Unescape( PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "BilingualFormat", "{original}\\n[{translation}]" ) );

if( BilingualMode )
{
ForceUIResizing = true;
}

TextureDirectory = PluginEnvironment.Current.Preferences.GetOrDefault( "Texture", "TextureDirectory", Path.Combine( "Translation", Path.Combine( "{Lang}", "Texture" ) ) );
TexturesPath = Path.Combine( PluginEnvironment.Current.TranslationPath, Settings.TextureDirectory ).Parameterize();
Expand Down
3 changes: 3 additions & 0 deletions src/XUnity.RuntimeHooker/XUnity.RuntimeHooker.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

<ItemGroup>
<ProjectReference Include="..\XUnity.RuntimeHooker.Core\XUnity.RuntimeHooker.Core.csproj" />
<ProjectReference Include="..\XUnity.RuntimeHooker.Trampolines\XUnity.RuntimeHooker.Trampolines.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>

<ItemGroup>
Expand Down