├── .github └── workflows │ └── main.yml ├── .gitignore ├── .readme ├── InGameTranslation.png └── TranslationFolder.png ├── DSPTranslationPlugin.sln ├── DSPTranslationPlugin.sln.DotSettings ├── DSPTranslationPlugin ├── DSPTranslationPlugin.csproj ├── DSPTranslationPlugin.csproj.DotSettings ├── GameHarmony │ ├── GameOption_Apply_Harmony.cs │ ├── GlobalObject_Initialize_Harmony.cs │ ├── GlobalObject_SaveLastLanguage_Harmony.cs │ ├── Localizer_Refresh_Harmony.cs │ ├── ManualBehaviour_All_Harmony.cs │ ├── StringTranslate_Translate_Harmony.cs │ ├── TranslationFix │ │ └── UIReplicatorWindow_OnUpdate_Harmony.cs │ ├── UIOptionWindow_TempOptionToUI_Harmony.cs │ ├── UIOptionWindow_UIToTempOption_Harmony.cs │ └── VFPreload_PreloadThread_Harmony.cs ├── TranslationPlugin.cs └── UnityHarmony │ ├── Text_Font_Getter_Harmony.cs │ ├── UIBehaviour_Awake.cs │ └── UIBehaviour_OnDestroy.cs ├── LICENSE ├── NuGet.Config ├── README.md ├── Tests ├── ReflectionTests.cs ├── SimpleJSON │ ├── ComplexDataTypes.cs │ ├── SerializeFirstAsObjectTypes.cs │ ├── SimpleJSONTests.cs │ └── UnityTypes.cs ├── Tests.csproj └── UIFixes │ ├── FromJsonTests.cs │ ├── RangeValueTests.cs │ ├── SimpleUIFixesTests.cs │ └── TestableUIBehaviourComponent.cs ├── TranslationCommon ├── ConsoleLogger.cs ├── Fonts │ ├── CustomFontData.cs │ ├── Fixes │ │ ├── IBehaviourComponent.cs │ │ ├── IFix.cs │ │ ├── RangeValue.cs │ │ ├── RectFix.cs │ │ ├── RelativeValue.cs │ │ ├── TextFix.cs │ │ ├── UIActionFixes.cs │ │ ├── UIBehaviourCache.cs │ │ ├── UIBehaviourComponent.cs │ │ ├── UIFix.cs │ │ ├── UIFixesData.cs │ │ └── UIPathTarget.cs │ ├── TextDefaultFont.cs │ └── TextFontManager.cs ├── GameObjectsUtils.cs ├── SimpleJSON │ ├── Attributes.cs │ ├── Changelog.txt │ ├── LICENSE │ ├── README │ ├── ReflectionUtils.cs │ ├── SimpleJSON.cs │ ├── SimpleJSONBinary.cs │ ├── SimpleJSONBuilder.cs │ ├── SimpleJSONDotNetTypes.cs │ ├── SimpleJSONUnity.cs │ └── SimpleJSONUtils.cs ├── Translation │ ├── LanguageContainer.cs │ ├── LanguageData.cs │ ├── LanguageSettings.cs │ ├── TranslationManager.cs │ └── TranslationProto.cs ├── TranslationCommon.csproj ├── Utils.cs └── WildcardMatch.cs ├── icon.png ├── manifest.json └── thunderstore.toml /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: .NET Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*.*.*" 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: windows-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Setup .NET 19 | uses: actions/setup-dotnet@v1 20 | with: 21 | dotnet-version: 6.0.x 22 | include-prerelease: true 23 | 24 | # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild 25 | - name: Setup MSBuild.exe 26 | uses: microsoft/setup-msbuild@v1.0.3 27 | 28 | - name: Setup .NET Framework 29 | run: choco install netfx-4.8-devpack 30 | 31 | - name: Setup .NET SDK 32 | run: choco install dotnetcore-sdk 33 | 34 | - name: Restore dependencies 35 | run: dotnet restore 36 | 37 | - name: Build 38 | run: dotnet build DSPTranslationPlugin/DSPTranslationPlugin.csproj -c Release 39 | 40 | #- name: Test 41 | # run: dotnet test --no-build --verbosity normal 42 | 43 | - name: Archive Release 44 | uses: thedoctor0/zip-release@master 45 | with: 46 | type: 'zip' 47 | filename: 'DSPTranslationPlugin.zip' 48 | directory: 'DSPTranslationPlugin/bin/Release/net472/' 49 | 50 | - name: Publish to Github Releases 51 | uses: ncipollo/release-action@v1 52 | with: 53 | artifacts: "DSPTranslationPlugin/bin/Release/net472/DSPTranslationPlugin.zip" 54 | token: ${{ secrets.GITHUB_TOKEN }} 55 | 56 | - name: Publish to Thunderstore 57 | run: | 58 | powershell -Command "(New-Object Net.WebClient).DownloadFile('https://github.com/thunderstore-io/thunderstore-cli/releases/download/0.1.1/tcli-0.1.1-win-x64.zip', 'tcli.zip')" 59 | unzip -o tcli.zip 60 | ./tcli.exe publish --token ${{ secrets.THUNDERSTORE }} --file DSPTranslationPlugin/bin/Release/net472/DSPTranslationPlugin.zip -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | #Rider IDE 353 | *.idea/* -------------------------------------------------------------------------------- /.readme/InGameTranslation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muchaszewski/DSP_TranslationMod/bc74e3ae3500ce3c4fb86b7191a9cc5b9e56ee2f/.readme/InGameTranslation.png -------------------------------------------------------------------------------- /.readme/TranslationFolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muchaszewski/DSP_TranslationMod/bc74e3ae3500ce3c4fb86b7191a9cc5b9e56ee2f/.readme/TranslationFolder.png -------------------------------------------------------------------------------- /DSPTranslationPlugin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DSPTranslationPlugin", "DSPTranslationPlugin\DSPTranslationPlugin.csproj", "{31B05D90-5EFD-4902-827A-27CFD10D296D}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TranslationCommon", "TranslationCommon\TranslationCommon.csproj", "{B89B56B6-D8B9-40E7-8D8B-B2501E138B5E}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{5025C0ED-5171-45CD-B631-34359778587D}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {31B05D90-5EFD-4902-827A-27CFD10D296D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {31B05D90-5EFD-4902-827A-27CFD10D296D}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {31B05D90-5EFD-4902-827A-27CFD10D296D}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {31B05D90-5EFD-4902-827A-27CFD10D296D}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {B89B56B6-D8B9-40E7-8D8B-B2501E138B5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {B89B56B6-D8B9-40E7-8D8B-B2501E138B5E}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {B89B56B6-D8B9-40E7-8D8B-B2501E138B5E}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {B89B56B6-D8B9-40E7-8D8B-B2501E138B5E}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {5025C0ED-5171-45CD-B631-34359778587D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {5025C0ED-5171-45CD-B631-34359778587D}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {5025C0ED-5171-45CD-B631-34359778587D}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {5025C0ED-5171-45CD-B631-34359778587D}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /DSPTranslationPlugin.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /DSPTranslationPlugin/DSPTranslationPlugin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | DSPTranslationPlugin 6 | DSPTranslationPlugin 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | icon.png 16 | PreserveNewest 17 | 18 | 19 | LICENSE 20 | PreserveNewest 21 | 22 | 23 | manifest.json 24 | PreserveNewest 25 | 26 | 27 | README.md 28 | PreserveNewest 29 | 30 | 31 | thunderstore.toml 32 | PreserveNewest 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DSPTranslationPlugin/DSPTranslationPlugin.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/GameOption_Apply_Harmony.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using TranslationCommon.Translation; 3 | 4 | namespace DSPTranslationPlugin.GameHarmony 5 | { 6 | 7 | [HarmonyPatch(typeof(GameOption), nameof(GameOption.Apply))] 8 | public static class GameOption_Apply_Harmony 9 | { 10 | /// 11 | /// Load current language after pressing "Apply" button 12 | /// 13 | [HarmonyPrefix] 14 | public static void Prefix(GameOption __instance) 15 | { 16 | TranslationManager.LoadCurrentLanguage(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/GlobalObject_Initialize_Harmony.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using HarmonyLib; 3 | using TranslationCommon.Translation; 4 | using UnityEngine; 5 | 6 | namespace DSPTranslationPlugin.GameHarmony 7 | { 8 | [HarmonyPatch(typeof(GlobalObject), "Initialize")] 9 | public static class GlobalObject_Initialize_Harmony 10 | { 11 | /// 12 | /// Try settings previously saved language 13 | /// 14 | [HarmonyPrefix] 15 | public static void Prefix() 16 | { 17 | var result = PlayerPrefs.GetString(TranslationManager.PlayerPrefsCode); 18 | if (!String.IsNullOrEmpty(result)) 19 | { 20 | TranslationManager.SelectedLanguage = result; 21 | TranslationManager.LoadCurrentLanguage(); 22 | } 23 | } 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/GlobalObject_SaveLastLanguage_Harmony.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using TranslationCommon.Translation; 3 | using UnityEngine; 4 | 5 | namespace DSPTranslationPlugin.GameHarmony 6 | { 7 | [HarmonyPatch(typeof(GlobalObject), nameof(GlobalObject.SaveLastLanguage))] 8 | public static class GlobalObject_SaveLastLanguage_Harmony 9 | { 10 | /// 11 | /// Save last used language 12 | /// 13 | [HarmonyPrefix] 14 | public static void Prefix() 15 | { 16 | if (!TranslationManager.IsInitialized) 17 | { 18 | TranslationManager.LoadCurrentLanguage(); 19 | } 20 | else 21 | { 22 | if (TranslationManager.CurrentLanguage == null) 23 | { 24 | PlayerPrefs.DeleteKey(TranslationManager.PlayerPrefsCode); 25 | } 26 | else 27 | { 28 | PlayerPrefs.SetString(TranslationManager.PlayerPrefsCode, TranslationManager.CurrentLanguage.Settings.LanguageDisplayName); 29 | } 30 | PlayerPrefs.Save(); 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/Localizer_Refresh_Harmony.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using HarmonyLib; 4 | using UnityEngine; 5 | using UnityEngine.Networking; 6 | using UnityEngine.UI; 7 | 8 | namespace DSPTranslationPlugin.GameHarmony 9 | { 10 | /// 11 | /// Localizer postfix for expanding translation box to match new credits text 12 | /// 13 | [HarmonyPatch(typeof(Localizer), "Refresh")] 14 | public static class Localizer_Refresh_Harmony 15 | { 16 | [HarmonyPrefix] 17 | public static bool Prefix(Localizer __instance) 18 | { 19 | var maskableGraphics = __instance.GetComponents(); 20 | __instance.translation = __instance.stringKey.Translate(); 21 | foreach (var graphic in maskableGraphics) 22 | { 23 | if (graphic is Text text) 24 | { 25 | text.text = __instance.translation; 26 | } 27 | else if (graphic is Image image) 28 | { 29 | if (IsUri(__instance.translation)) 30 | { 31 | __instance.StartCoroutine(GetRequest(__instance.translation, image)); 32 | } 33 | else 34 | { 35 | image.sprite = Resources.Load(__instance.translation); 36 | } 37 | } 38 | else if (graphic is RawImage rawImage) 39 | { 40 | if (IsUri(__instance.translation)) 41 | { 42 | __instance.StartCoroutine(GetRequest(__instance.translation, rawImage)); 43 | } 44 | else 45 | { 46 | rawImage.texture = Resources.Load(__instance.translation); 47 | } 48 | } 49 | } 50 | 51 | return false; 52 | } 53 | 54 | private static bool IsUri(string translation) 55 | { 56 | return translation.StartsWith("http") || translation.StartsWith("file"); 57 | } 58 | 59 | /// 60 | /// Expand in game credits box 61 | /// 62 | /// 63 | [HarmonyPostfix] 64 | public static void Postfix(Localizer __instance) 65 | { 66 | ExpandGameCreditsBox(__instance); 67 | } 68 | 69 | private static void ExpandGameCreditsBox(Localizer __instance) 70 | { 71 | if (__instance.name == "tip" && __instance.transform.parent.name == "language") 72 | { 73 | var rect = __instance.GetComponent(); 74 | var sizeDelta = rect.sizeDelta; 75 | sizeDelta.x = 600; 76 | sizeDelta.y = 90; 77 | rect.sizeDelta = sizeDelta; 78 | } 79 | } 80 | 81 | private static IEnumerator GetRequest(string uri, RawImage image) 82 | { 83 | var www = UnityWebRequestTexture.GetTexture(uri); 84 | yield return www.SendWebRequest(); 85 | 86 | if (www.isNetworkError || www.isHttpError) 87 | { 88 | Debug.LogError(www.error); 89 | } 90 | else 91 | { 92 | var myTexture = ((DownloadHandlerTexture) www.downloadHandler).texture; 93 | image.texture = myTexture; 94 | } 95 | } 96 | 97 | private static IEnumerator GetRequest(string uri, Image image) 98 | { 99 | var www = UnityWebRequestTexture.GetTexture(uri); 100 | yield return www.SendWebRequest(); 101 | 102 | if (www.isNetworkError || www.isHttpError) 103 | { 104 | Debug.LogError(www.error); 105 | } 106 | else 107 | { 108 | var myTexture = ((DownloadHandlerTexture) www.downloadHandler).texture; 109 | var sprite = Sprite.Create(myTexture, new Rect(0, 0, myTexture.width, myTexture.height), Vector2.zero); 110 | image.sprite = sprite; 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/ManualBehaviour_All_Harmony.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using HarmonyLib; 3 | using TranslationCommon.Fonts; 4 | 5 | namespace DSPTranslationPlugin.GameHarmony 6 | { 7 | [HarmonyPatch(typeof(ManualBehaviour))] 8 | public static class ManualBehaviour_All_Harmony 9 | { 10 | public static Dictionary BehaviourComponents = 11 | new Dictionary(); 12 | 13 | [HarmonyPostfix] 14 | [HarmonyPatch("_Create")] 15 | public static void Postfix_OnCreate(ManualBehaviour __instance) 16 | { 17 | if (!BehaviourComponents.ContainsKey(__instance)) 18 | { 19 | BehaviourComponents.Add(__instance, new UIBehaviourComponent(__instance)); 20 | } 21 | BehaviourComponents[__instance].OnCreate(); 22 | } 23 | 24 | [HarmonyPostfix] 25 | [HarmonyPatch("_Destroy")] 26 | public static void Postfix_OnDestroy(ManualBehaviour __instance) 27 | { 28 | BehaviourComponents.Remove(__instance); 29 | } 30 | 31 | [HarmonyPostfix] 32 | [HarmonyPatch("_Init")] 33 | public static void Postfix_OnInit(ManualBehaviour __instance) 34 | { 35 | if (!BehaviourComponents.ContainsKey(__instance)) 36 | { 37 | BehaviourComponents.Add(__instance, new UIBehaviourComponent(__instance)); 38 | } 39 | BehaviourComponents[__instance].OnInit(); 40 | } 41 | 42 | /*[HarmonyPostfix] 43 | [HarmonyPatch("_Open")] 44 | public static void Postfix_OnOpen(ManualBehaviour __instance) 45 | { 46 | if (!BehaviourComponents.ContainsKey(__instance)) 47 | { 48 | BehaviourComponents.Add(__instance, new UIBehaviourComponent(__instance)); 49 | } 50 | 51 | BehaviourComponents[__instance].OnOpen(); 52 | } 53 | 54 | [HarmonyPostfix] 55 | [HarmonyPatch("_Update")] 56 | public static void Postfix_OnUpdate(ManualBehaviour __instance) 57 | { 58 | if (__instance.isActiveAndEnabled) 59 | { 60 | if (!BehaviourComponents.ContainsKey(__instance)) 61 | { 62 | BehaviourComponents.Add(__instance, new UIBehaviourComponent(__instance)); 63 | } 64 | 65 | BehaviourComponents[__instance].OnUpdate(); 66 | } 67 | } 68 | 69 | [HarmonyPostfix] 70 | [HarmonyPatch("_LateUpdate")] 71 | public static void Postfix_OnLateUpdate(ManualBehaviour __instance) 72 | { 73 | if (__instance.isActiveAndEnabled) 74 | { 75 | if (!BehaviourComponents.ContainsKey(__instance)) 76 | { 77 | BehaviourComponents.Add(__instance, new UIBehaviourComponent(__instance)); 78 | } 79 | BehaviourComponents[__instance].OnLateUpdate(); 80 | } 81 | }*/ 82 | } 83 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/StringTranslate_Translate_Harmony.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using HarmonyLib; 3 | using TranslationCommon.Translation; 4 | 5 | namespace DSPTranslationPlugin.GameHarmony 6 | { 7 | [HarmonyPatch(typeof(StringTranslate), nameof(StringTranslate.Translate), typeof(string))] 8 | public static class StringTranslate_Translate_Harmony 9 | { 10 | /// 11 | /// Translate requested text 12 | /// 13 | /// Returned result 14 | /// Input NAME text 15 | /// 16 | [HarmonyPrefix] 17 | public static bool Prefix(ref string __result, string s) 18 | { 19 | if (s == null) 20 | { 21 | return true; 22 | } 23 | 24 | if (TranslationManager.CurrentLanguage != null) 25 | { 26 | if (TranslationManager.TranslationDictionary.ContainsKey(s)) 27 | { 28 | __result = TranslationManager.TranslationDictionary[s].Translation; 29 | return false; 30 | } 31 | 32 | return true; 33 | } 34 | 35 | return true; 36 | } 37 | 38 | /// 39 | /// Add in game credits 40 | /// 41 | /// 42 | /// 43 | [HarmonyPostfix] 44 | public static void Postfix(ref string __result, string s) 45 | { 46 | if (s == "需要重启完全生效") 47 | { 48 | __result += 49 | "\nTranslation tool made by Muchaszewski with the help of community" + 50 | "\nhttps://github.com/Muchaszewski/DSP_TranslationMod"; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/TranslationFix/UIReplicatorWindow_OnUpdate_Harmony.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using System.Reflection.Emit; 4 | using HarmonyLib; 5 | using UnityEngine.UI; 6 | 7 | namespace DSPTranslationPlugin.GameHarmony.TranslationFix 8 | { 9 | public class UIReplicatorWindow_OnUpdate_Harmony 10 | { 11 | [HarmonyPatch(typeof(UIReplicatorWindow), "_OnUpdate")] 12 | public static class UIReplicatorWindow_OnUpdate_Prefix 13 | { 14 | private static bool isPatched = false; 15 | 16 | /// 17 | /// Fixed "Replicating Queue" text 18 | /// 19 | /// 20 | [HarmonyPrefix] 21 | public static void Prefix(UIReplicatorWindow __instance) 22 | { 23 | if (!isPatched) 24 | { 25 | var _tmp_text0 = AccessTools.Field(typeof(UIReplicatorWindow), "_tmp_text0"); 26 | // 制造队列 == "Replicating Queue" 27 | _tmp_text0.SetValue(__instance, "制造队列".Translate()); 28 | isPatched = true; 29 | } 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/UIOptionWindow_TempOptionToUI_Harmony.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using DSPTranslationPlugin.UnityHarmony; 6 | using HarmonyLib; 7 | using TranslationCommon; 8 | using TranslationCommon.Translation; 9 | using UnityEngine; 10 | using UnityEngine.UI; 11 | using Object = UnityEngine.Object; 12 | 13 | namespace DSPTranslationPlugin.GameHarmony 14 | { 15 | [HarmonyPatch(typeof(UIOptionWindow), "TempOptionToUI")] 16 | public static class UIOptionWindow_TempOptionToUI_Harmony 17 | { 18 | private static int? _originalUICount; 19 | 20 | private static UIComboBox[] languageComboBoxes; 21 | 22 | private static string[] InGameFonts = new[] 23 | { 24 | "Default", 25 | }; 26 | 27 | [HarmonyPrefix] 28 | public static void Prefix(UIOptionWindow __instance) 29 | { 30 | AddComboBoxLanguage(__instance); 31 | } 32 | 33 | [HarmonyPostfix] 34 | public static void Postfix(UIOptionWindow __instance) 35 | { 36 | ApplyComboBoxLanguage(__instance); 37 | CreateFontCompoBox(__instance); 38 | } 39 | 40 | /// 41 | /// Create option combobox with selection of font 42 | /// 43 | private static void CreateFontCompoBox(UIOptionWindow __instance) 44 | { 45 | var parent = __instance.languageComp.transform.parent.parent; 46 | if (_originalUICount == null) 47 | { 48 | _originalUICount = parent.childCount; 49 | } 50 | 51 | var genericComboBox = __instance.tipLevelComp.transform.parent; 52 | 53 | if (languageComboBoxes == null) 54 | languageComboBoxes = new UIComboBox[InGameFonts.Length]; 55 | 56 | // Needs to initialize UI 57 | if (_originalUICount == parent.childCount) 58 | { 59 | Localization.OnLanguageChange += ReloadFontsComboBox; 60 | 61 | for (int i = 0; i < InGameFonts.Length; i++) 62 | { 63 | var fontName = InGameFonts[i]; 64 | // Add new combobox 65 | var root = Object.Instantiate(genericComboBox, 66 | genericComboBox.parent); 67 | 68 | root.gameObject.SetActive(true); 69 | Object.Destroy(root.GetComponent()); 70 | root.GetComponent().text = $"Font - {fontName}"; 71 | 72 | languageComboBoxes[i] = root.GetComponentInChildren(); 73 | UIComboBox languageComboBox = languageComboBoxes[i]; 74 | languageComboBox.Items.Clear(); 75 | languageComboBox.Items.Add(fontName); 76 | foreach (var installedFont in TextFontManager.InstalledFonts) 77 | { 78 | languageComboBox.Items.Add(installedFont); 79 | } 80 | 81 | 82 | languageComboBox.text = fontName; 83 | 84 | var settingsIndex = TextFontManager.GetSettingIndex(fontName); 85 | ConsoleLogger.LogWarning("TextFontManager.GetSettingIndex " + settingsIndex); 86 | languageComboBox.itemIndex = settingsIndex; 87 | 88 | languageComboBox.onItemIndexChange.AddListener(() => 89 | { 90 | var selectedFontName = languageComboBox.Items[languageComboBox.itemIndex]; 91 | if (selectedFontName == fontName) 92 | { 93 | TextFontManager.RestoreDefaultFont(fontName); 94 | } 95 | else 96 | { 97 | Font font; 98 | try 99 | { 100 | if (TranslationManager.CurrentLanguage == null || TranslationManager.CurrentLanguage.Fonts == null) 101 | throw new InvalidOperationException(); 102 | 103 | font = TranslationManager.CurrentLanguage.Fonts.First(font1 => font1.name == selectedFontName); 104 | } 105 | catch (InvalidOperationException) 106 | { 107 | font = Font.CreateDynamicFontFromOSFont(selectedFontName, 12); 108 | } 109 | 110 | TextFontManager.ApplyCustomFont(fontName, font); 111 | } 112 | }); 113 | 114 | // Set Option position 115 | var rectTransform = root.GetComponent(); 116 | var childCountWithoutRestore = __instance.languageComp.transform.parent.parent.childCount - 2; 117 | var position = __instance.languageComp.transform.parent.GetComponent().anchoredPosition; 118 | var offset = 40; 119 | rectTransform.anchoredPosition = new Vector2(position.x, position.y - offset * childCountWithoutRestore); 120 | } 121 | } 122 | } 123 | 124 | public static void ReloadFontsComboBox(Language lang) 125 | { 126 | for (int index = 0; index < InGameFonts.Length; index++) 127 | { 128 | UIComboBox languageComboBox = languageComboBoxes[index]; 129 | if (languageComboBox == null) 130 | { 131 | return; 132 | } 133 | var fontName = InGameFonts[index]; 134 | languageComboBox.Items.Clear(); 135 | languageComboBox.Items.Add(fontName); 136 | foreach (var installedFont in TextFontManager.InstalledFonts) 137 | { 138 | languageComboBox.Items.Add(installedFont); 139 | } 140 | } 141 | } 142 | 143 | private static void AddComboBoxLanguage(UIOptionWindow __instance) 144 | { 145 | if (!__instance.languageComp.Items.Contains("(Original) Française")) 146 | { 147 | __instance.languageComp.Items.Add("(Original) Française"); 148 | } 149 | 150 | foreach (var langauge in TranslationManager.Langauges) 151 | { 152 | if (!__instance.languageComp.Items.Contains(langauge.Settings.LanguageDisplayName)) 153 | { 154 | __instance.languageComp.Items.Add(langauge.Settings.LanguageDisplayName); 155 | } 156 | } 157 | } 158 | 159 | private static void ApplyComboBoxLanguage(UIOptionWindow __instance) 160 | { 161 | if (TranslationManager.CurrentLanguage != null) 162 | { 163 | for (int i = 0; i < TranslationManager.Langauges.Count; i++) 164 | { 165 | if (TranslationManager.CurrentLanguage.Settings.LanguageDisplayName == 166 | TranslationManager.Langauges[i].Settings.LanguageDisplayName) 167 | __instance.languageComp.itemIndex = 3 + i; 168 | } 169 | } 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/UIOptionWindow_UIToTempOption_Harmony.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using HarmonyLib; 3 | using TranslationCommon.Translation; 4 | using UnityEngine; 5 | 6 | namespace DSPTranslationPlugin.GameHarmony 7 | { 8 | [HarmonyPatch(typeof(UIOptionWindow), "UIToTempOption")] 9 | public static class UIOptionWindow_UIToTempOption_Harmony 10 | { 11 | [HarmonyPrefix] 12 | public static void Prefix(UIOptionWindow __instance) 13 | { 14 | UIComboBox comboBox = __instance.languageComp; 15 | var item = comboBox.itemIndex != -1 16 | ? comboBox.Items[comboBox.itemIndex] 17 | : "English"; 18 | TranslationManager.SelectedLanguage = item; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/GameHarmony/VFPreload_PreloadThread_Harmony.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using HarmonyLib; 4 | using TranslationCommon; 5 | using TranslationCommon.SimpleJSON; 6 | using TranslationCommon.Translation; 7 | 8 | namespace DSPTranslationPlugin.GameHarmony 9 | { 10 | [HarmonyPatch(typeof(VFPreload), "PreloadThread")] 11 | public class VFPreload_PreloadThread 12 | { 13 | [HarmonyPostfix] 14 | public static void Postfix(VFPreload __instance) 15 | { 16 | var stringProtoSet = LDB.strings; 17 | foreach (var languageContainer in TranslationManager.Langauges) 18 | { 19 | var templateLanguageData = new LanguageData(languageContainer.Settings, stringProtoSet); 20 | languageContainer.LoadTranslation(templateLanguageData); 21 | } 22 | } 23 | 24 | private static ProtoSet GetProtoSet() 25 | where T : Proto 26 | { 27 | var properties = typeof(LDB).GetProperties(BindingFlags.Static | BindingFlags.Public); 28 | foreach (var property in properties) 29 | { 30 | var value = property.GetValue(null, null); 31 | var type = value.GetType(); 32 | var dataArray = (Proto[])type.GetField("dataArray").GetValue(value); 33 | var arrayType = dataArray.GetType().GetElementType(); 34 | 35 | if (arrayType != typeof(T)) 36 | { 37 | continue; 38 | } 39 | 40 | return (ProtoSet)value; 41 | } 42 | 43 | return null; 44 | } 45 | 46 | private static void ProtoJsonDump() 47 | { 48 | var properties = typeof(LDB).GetProperties(BindingFlags.Static | BindingFlags.Public); 49 | foreach (var property in properties) 50 | { 51 | var value = property.GetValue(null, null); 52 | var type = value.GetType(); 53 | var result = JSON.ToJson(value, true); 54 | var instanceGenericName = type.FullName; 55 | TextWriter writer = new StreamWriter($"{Utils.ConfigPath}/JsonDump/{instanceGenericName}.json"); 56 | writer.Write(result); 57 | writer.Flush(); 58 | writer.Close(); 59 | ConsoleLogger.LogInfo("JsonProtoDumpSet: " + value + " of " + instanceGenericName); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/TranslationPlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using BepInEx; 7 | using DSPTranslationPlugin.UnityHarmony; 8 | using HarmonyLib; 9 | using UnityEngine; 10 | 11 | namespace DSPTranslationPlugin 12 | { 13 | [BepInPlugin("com.muchaszewski.dsp_translationPlugin", "DSP Community Translation", "0.5.2")] 14 | public class TranslationPlugin : BaseUnityPlugin 15 | { 16 | public static MonoBehaviour StaticMonoBehaviour { get; private set; } 17 | 18 | private void Awake() 19 | { 20 | StaticMonoBehaviour = this; 21 | Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/UnityHarmony/Text_Font_Getter_Harmony.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine.UI; 3 | 4 | namespace DSPTranslationPlugin.UnityHarmony 5 | { 6 | /// 7 | /// Text harmony patcher to apply font to all requesting elements 8 | /// 9 | [HarmonyPatch(typeof(Text), nameof(Text.font), MethodType.Getter)] 10 | public static class Text_Font_Getter_Harmony 11 | { 12 | [HarmonyPrefix] 13 | public static void Prefix(Text __instance) 14 | { 15 | TextFontManager.Get(__instance)?.OnGetFont(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/UnityHarmony/UIBehaviour_Awake.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine.EventSystems; 3 | using UnityEngine.UI; 4 | 5 | namespace DSPTranslationPlugin.UnityHarmony 6 | { 7 | [HarmonyPatch(typeof(UIBehaviour), "Awake")] 8 | public static class UIBehaviour_Awake 9 | { 10 | /// 11 | /// Awake method for Text to add TextFontManger 12 | /// 13 | /// 14 | [HarmonyPrefix] 15 | public static void Prefix(UIBehaviour __instance) 16 | { 17 | if (__instance is Text text) 18 | { 19 | TextFontManager.Add(text); 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /DSPTranslationPlugin/UnityHarmony/UIBehaviour_OnDestroy.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine.EventSystems; 3 | using UnityEngine.UI; 4 | 5 | namespace DSPTranslationPlugin.UnityHarmony 6 | { 7 | [HarmonyPatch(typeof(UIBehaviour), "OnDestroy")] 8 | public static class UIBehaviour_OnDestroy 9 | { 10 | /// 11 | /// OnDestroy method for Text to remove text from TextFontManger 12 | /// 13 | /// 14 | [HarmonyPrefix] 15 | public static void Prefix(UIBehaviour __instance) 16 | { 17 | if (__instance is Text text) 18 | { 19 | TextFontManager.Remove(text); 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-ShareAlike 4.0 International License 2 | This is a human-readable summary of the full license below. 3 | Under this license, you are free to: 4 | 5 | Share — copy and redistribute the material in any medium or format 6 | Adapt — remix, transform, and build upon the material for any purpose, even commercially. 7 | The licensor cannot revoke these freedoms as long as you follow the license terms. 8 | 9 | License terms: 10 | 11 | Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 12 | ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 13 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 14 | Notices: 15 | 16 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 17 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 18 | 19 | Licence holder: https://github.com/Muchaszewski 20 | 21 | Creative Commons Attribution 4.0 International Public License - (Read Online) https://creativecommons.org/licenses/by/4.0/legalcode 22 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 23 | 24 | Section 1 – Definitions. 25 | 26 | Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 27 | Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 28 | Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 29 | Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 30 | Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 31 | Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 32 | Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 33 | Licensor means the individual(s) or entity(ies) granting rights under this Public License. 34 | Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 35 | Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 36 | You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 37 | Section 2 – Scope. 38 | 39 | License grant. 40 | Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 41 | reproduce and Share the Licensed Material, in whole or in part; and 42 | produce, reproduce, and Share Adapted Material. 43 | Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 44 | Term. The term of this Public License is specified in Section 6(a). 45 | Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 46 | Downstream recipients. 47 | Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 48 | No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 49 | No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 50 | Other rights. 51 | 52 | Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 53 | Patent and trademark rights are not licensed under this Public License. 54 | To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. 55 | Section 3 – License Conditions. 56 | 57 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 58 | 59 | Attribution. 60 | 61 | If You Share the Licensed Material (including in modified form), You must: 62 | 63 | retain the following if it is supplied by the Licensor with the Licensed Material: 64 | identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 65 | a copyright notice; 66 | a notice that refers to this Public License; 67 | a notice that refers to the disclaimer of warranties; 68 | a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 69 | indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 70 | indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 71 | You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 72 | If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 73 | If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. 74 | Section 4 – Sui Generis Database Rights. 75 | 76 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 77 | 78 | for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; 79 | if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 80 | You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 81 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 82 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 83 | 84 | Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. 85 | To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. 86 | The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 87 | Section 6 – Term and Termination. 88 | 89 | This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 90 | Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 91 | 92 | automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 93 | upon express reinstatement by the Licensor. 94 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 95 | For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 96 | Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 97 | Section 7 – Other Terms and Conditions. 98 | 99 | The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 100 | Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 101 | Section 8 – Interpretation. 102 | 103 | For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 104 | To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 105 | No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 106 | Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Support my work on Patreon](https://www.patreon.com/muchaszewski?fan_landing=true) 2 | # Dyson sphere translation plugin! 3 | 4 | [Licence: Project is under CC Attribution 4.0](https://raw.githubusercontent.com/Muchaszewski/DSP_TranslationMod/main/LICENSE) 5 | 6 | # BREAKING CHANGE 7 | New translation folder location for translation: `{Game Directory}\DSPGAME_Data\Translation\{LanguageName}\...` 8 | 9 | # Features 10 | - Adds possibility to add custom languages 11 | - Adds (currently hidden) French language 12 | - Adds the possibility to change Font in game (WORK IN PROGRESS) 13 | - Use custom logo for custom translation 14 | 15 | # Roadmap 16 | - Refactor code and add documentation 17 | - Add support for adjusting content size of in game UI to fit new text 18 | 19 | ## Installation via Mod manager 20 | 21 | [Download mod manager](https://dsp.thunderstore.io/package/ebkr/r2modman_dsp/) 22 | 23 | 1. Press Install with Mod Manager at https://dsp.thunderstore.io/package/Muchaszewski/DSPTranslationPlugin/ 24 | 2. Add translations to 25 | `{Game Directory}\DSPGAME_Data\Translation\{LanguageName}\translation_DysonSphereProgram.json`. 26 | You can find [translations at Crowdin](https://crowdin.com/translate/dyson-sphere-program) 27 | 6. Select new translation in Menu of the Game 28 | 7. Enjoy 29 | 30 | ## Installation Manual 31 | 1. Download and unpack [BepInEx](https://github.com/BepInEx/BepInEx/releases) into game root directory 32 | 2. Download [mod files](https://github.com/Muchaszewski/DSP_TranslationMod/releases) 33 | 3. Extract zip file 34 | 4. Paste DLLs under `Dyson Sphere Program\BepInEx\plugins\DSPTranslationPlugin` 35 | 5. Add translations to 36 | `{Game Directory}\DSPGAME_Data\Translation\{LanguageName}\translation_DysonSphereProgram.json`. 37 | You can find [translations at Crowdin](https://crowdin.com/translate/dyson-sphere-program) 38 | 39 | 6. Select new translation in Menu of the Game 40 | 7. Enjoy 41 | 42 | ![InGameTranslation](https://raw.githubusercontent.com/Muchaszewski/DSP_TranslationMod/main/.readme/InGameTranslation.png) 43 | 44 | ## How to add new translations 45 | 1. Create folder under `Dyson Sphere Program\BepInEx\plugins\DSPTranslationPlugin\Translation` with the name of your translation eg: `Polish` 46 | 47 | ![TranslationFolder](https://raw.githubusercontent.com/Muchaszewski/DSP_TranslationMod/main/.readme/TranslationFolder.png) 48 | 49 | 2. Run game once - New file settings and translations files will be created. 50 | 3. Translate 51 | 52 | ### Translation file structure: 53 | ``` 54 | { #CROWDIN 55 | "点击鼠标建造": "Click to build", 56 | "无法在此建造": "Cannot build here", 57 | "{NAME}: "{TRANSLATION}", 58 | (...) 59 | } 60 | ``` 61 | 62 | 63 | ``` 64 | { #LEGACY 65 | "TranslationTable": [ 66 | { 67 | "IsValid": true, # Does translation exists in the game 68 | "Name": "点击鼠标建造", # Name used by the game for translation (READ ONLY) 69 | "Original": "Click to build", # Translation in English 70 | "Translation": "Kliknij, aby zbudować" # Your translation here 71 | }, 72 | (...) 73 | ] 74 | } 75 | ``` 76 | 77 | ### Settings file structure: 78 | ``` 79 | { 80 | "Version": "0.1.0.0", # Plugin version 81 | "GameVersion": "0.6.15.5678", # Game version 82 | "OriginalLanguage": "ENUS", # Language in which empty new translation files will be generated, possible values: "ENUS", "FRFR", "ZHCN" 83 | "LanguageDisplayName": "Polish", # Language display name in the game 84 | "ImportFromLegacy": false, # Generate Legacy json format (defualt is Crowdin format) 85 | "CreateAndUpdateFromPlainTextDumpUnsafe": true # Should create and import dump file (more below) 86 | } 87 | ``` 88 | 89 | ### Custom images: 90 | Currently in the game there are 2 images that can be changed, they look like a path - `UI/Textures/dsp-logo-en`. You can use a valid path from resources or valid URL. 91 | URL needs to be a direct png file. Eg: 92 | ``` 93 | Internet URI -- "ImageLogo0": "https://wiki.factorio.com/images/thumb/Factorio-logo.png/461px-Factorio-logo.png", 94 | Local file URI -- "ImageLogo0": "file://C:/Users/Muchaszewski/Documents/Icon.png" 95 | ``` 96 | 97 | #### Specific images description: 98 | 99 | - `ImageLogo0` and `ImageLogo1` needs to have aspect ratio that corresponds to 800x300 pixels, otherwise they will be stretched 100 | 101 | # Build from source 102 | 103 | 1. Download repository 104 | 2. Edit csproj reference to point to your game location 105 | 3. 106 | 107 | ## Run from DSPGAME.exe without steam 108 | In order to start the game without steam launcher create steam_appid.txt file in root game folder with `1366540` in it. 109 | (Steam launcher might be required to run in the background) 110 | 111 | 112 | # Special Thanks to 113 | [BepInEx](https://github.com/BepInEx/BepInEx/releases) - for mod support 114 | 115 | Modified hard fork of [SimpleJSON](https://github.com/Bunny83/SimpleJSON) - for simple json parser 116 | -------------------------------------------------------------------------------- /Tests/ReflectionTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using TranslationCommon.Translation; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; 5 | 6 | 7 | namespace Tests 8 | { 9 | public class ReflectionTests 10 | { 11 | public class StringProto 12 | { 13 | public string ZHCN; 14 | public string ENUS; 15 | public string FRFR; 16 | } 17 | 18 | [Test] 19 | public void GetTextFromStringProtoTest() 20 | { 21 | var settings = new LanguageSettings(); 22 | settings.OriginalLanguage = "ENUS"; 23 | var val = new StringProto() 24 | { 25 | ENUS = "ENUS", 26 | FRFR = "FRFR", 27 | ZHCN = "ZHCN", 28 | }; 29 | 30 | var translationDelegate = LanguageData.GetOriginalTextDelegate(settings); 31 | Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEqual("ENUS", translationDelegate(val)); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Tests/SimpleJSON/ComplexDataTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Tests 6 | { 7 | public class ComplexDataTypes 8 | { 9 | public bool BoolField; 10 | public byte ByteField; 11 | public short ShortField; 12 | public ushort UshortField; 13 | public int IntField; 14 | public uint UintField; 15 | public char CharField; 16 | public float FloatField; 17 | public double DoubleField; 18 | public long LongField; 19 | public ulong UlongField; 20 | public decimal DecimalField; 21 | public sbyte SbyteField; 22 | 23 | public string StringField; 24 | 25 | public object UnknownTypeField; 26 | public ComplexDataTypes ParentField; 27 | public ComplexDataTypes ChildField; 28 | 29 | public List ListTypeField; 30 | public List ListStringField; 31 | public List ListIntField; 32 | 33 | public Dictionary StringStringDictionaryField; 34 | public Dictionary IntIntDictionaryField; 35 | public Dictionary ComplexComplexDictionaryField; 36 | 37 | public static ComplexDataTypes GenerateRandomValues(ComplexDataTypes parent = null, bool generateLoop = true) 38 | { 39 | ComplexDataTypes value = new ComplexDataTypes(); 40 | var random = new Random(0); 41 | value.BoolField = random.NextDouble() > 0.5 ? true : false; 42 | value.ByteField = (byte) random.Next(); 43 | value.CharField = (char) random.Next(); 44 | value.ShortField = (short) random.Next(); 45 | value.UshortField = (ushort) random.Next(); 46 | value.IntField = random.Next(); 47 | value.UintField = (uint) random.Next(); 48 | value.FloatField = (float) random.NextDouble(); 49 | value.DoubleField = random.NextDouble(); 50 | value.LongField = (long) random.Next(); 51 | value.UlongField = (ulong) random.Next(); 52 | value.DecimalField = (decimal) random.NextDouble(); 53 | value.SbyteField = (sbyte) random.Next(); 54 | 55 | value.StringField = RandomStringGenerator.RandomString(8); 56 | 57 | value.UnknownTypeField = new object(); 58 | 59 | if (generateLoop) 60 | { 61 | value.ParentField = parent; 62 | } 63 | value.ChildField = parent == null ? GenerateRandomValues(value, generateLoop) : null; 64 | 65 | if (parent == null) 66 | { 67 | value.ListTypeField = new List(); 68 | for (int i = 0; i < 3; i++) 69 | { 70 | value.ListTypeField.Add(GenerateRandomValues(value, generateLoop)); 71 | } 72 | } 73 | value.ListStringField = new List(); 74 | for (int i = 0; i < 3; i++) 75 | { 76 | value.ListStringField.Add(RandomStringGenerator.RandomString(8)); 77 | } 78 | value.ListIntField = new List(); 79 | for (int i = 0; i < 3; i++) 80 | { 81 | value.ListIntField.Add(random.Next()); 82 | } 83 | 84 | return value; 85 | } 86 | 87 | internal static class RandomStringGenerator 88 | { 89 | /// 90 | /// Generate and return a random number 91 | /// 92 | /// random number 93 | public static int RandomNumber() 94 | { 95 | Random random = new Random(); 96 | return random.Next(1000, 5000); 97 | } 98 | 99 | /// 100 | /// Generate and return a random string 101 | /// 102 | /// length of the string 103 | /// random string 104 | public static string RandomString(int length) 105 | { 106 | StringBuilder strbuilder = new StringBuilder(); 107 | Random random = new Random(); 108 | for (int i = 0; i < length; i++) 109 | { 110 | // Generate floating point numbers 111 | double myFloat = random.NextDouble(); 112 | // Generate the char 113 | var myChar = Convert.ToChar(Convert.ToInt32(Math.Floor(25 * myFloat) + 65)); 114 | strbuilder.Append(myChar); 115 | } 116 | return strbuilder.ToString().ToLower(); 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /Tests/SimpleJSON/SerializeFirstAsObjectTypes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using TranslationCommon.SimpleJSON; 3 | using UnityEngine; 4 | 5 | namespace Tests 6 | { 7 | public class SerializeFirstAsObjectTypes 8 | { 9 | [SerializeField] 10 | [SerializeFirstAsObject] 11 | private Dictionary _dictionary = new Dictionary(); 12 | 13 | public void Add(string key, string value) 14 | { 15 | _dictionary.Add(key, value); 16 | } 17 | 18 | public Dictionary Get() 19 | { 20 | return _dictionary; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Tests/SimpleJSON/SimpleJSONTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentAssertions; 3 | using Newtonsoft.Json; 4 | using NUnit.Framework; 5 | using TranslationCommon.SimpleJSON; 6 | using TranslationCommon.Translation; 7 | using Assert = NUnit.Framework.Assert; 8 | 9 | namespace Tests 10 | { 11 | public class Tests 12 | { 13 | [Test] 14 | public void Test() 15 | { 16 | var a = new Dictionary(); 17 | var t = a.GetType(); 18 | } 19 | 20 | [Test] 21 | public void LanguageDataTest() 22 | { 23 | LanguageData data = new LanguageData(); 24 | data.TranslationTable = new List(); 25 | var list = data.TranslationTable; 26 | for (int i = 0; i < 10; i++) 27 | { 28 | list.Add(new TranslationProto($"Test {i}", i, $"Org {i}", $"Trans {i}")); 29 | } 30 | 31 | var truth = JsonConvert.SerializeObject(data, Formatting.Indented); 32 | NUnit.Framework.Assert.AreEqual(truth, JSON.ToJson(data, true)); 33 | var test = JSON.FromJson(truth); 34 | test.Should().BeEquivalentTo(data); 35 | } 36 | 37 | [Test] 38 | public void ComplexDataTypesTest() 39 | { 40 | ComplexDataTypes data = ComplexDataTypes.GenerateRandomValues(null, false); 41 | 42 | var truth = JsonConvert.SerializeObject(data, Formatting.Indented); 43 | NUnit.Framework.Assert.AreEqual(truth, JSON.ToJson(data, true)); 44 | var jsonNet = JsonConvert.DeserializeObject(truth); 45 | var test = JSON.FromJson(truth); 46 | jsonNet.Should().BeEquivalentTo(data); 47 | test.Should().BeEquivalentTo(data); 48 | } 49 | 50 | [Test] 51 | public void ComplexDataTypesTestNoIndent() 52 | { 53 | ComplexDataTypes data = ComplexDataTypes.GenerateRandomValues(null, false); 54 | 55 | var truth = JsonConvert.SerializeObject(data, Formatting.None); 56 | NUnit.Framework.Assert.AreEqual(truth, JSON.ToJson(data, false)); 57 | var test = JSON.FromJson(truth); 58 | test.Should().BeEquivalentTo(data); 59 | } 60 | 61 | [Test] 62 | public void UnityTypesTest() 63 | { 64 | UnityTypes data = UnityTypes.GenerateRandomValues(); 65 | 66 | var result = JSON.ToJson(data, true); 67 | var test = JSON.FromJson(result); 68 | test.Should().BeEquivalentTo(data); 69 | } 70 | 71 | [Test] 72 | public void DictionaryTest() 73 | { 74 | Dictionary data = new Dictionary(); 75 | data.Add("Key1", "Value1"); 76 | data.Add("Key2", "Value2"); 77 | 78 | var truth = JsonConvert.SerializeObject(data, Formatting.Indented); 79 | NUnit.Framework.Assert.AreEqual(truth, JSON.ToJson(data, true)); 80 | var jsonNet = JsonConvert.DeserializeObject>(truth); 81 | var test = JSON.FromJson>(truth); 82 | jsonNet.Should().BeEquivalentTo(data); 83 | test.Should().BeEquivalentTo(data); 84 | } 85 | 86 | [Test] 87 | public void SerializeFirstAsObjectTest() 88 | { 89 | var data = new SerializeFirstAsObjectTypes(); 90 | data.Add("Test", "test"); 91 | data.Add("Test2", "test2"); 92 | 93 | var result = JSON.ToJson(data, true); 94 | NUnit.Framework.Assert.AreEqual("{\r\n \"Test\": \"test\",\r\n \"Test2\": \"test2\"\r\n}", result); 95 | var test = JSON.FromJson(result); 96 | test.Get().Should().BeEquivalentTo(data.Get()); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Tests/SimpleJSON/UnityTypes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using Random = System.Random; 4 | 5 | namespace Tests 6 | { 7 | public class UnityTypes 8 | { 9 | public Vector2 Vector2Field; 10 | public Vector3 Vector3Field; 11 | 12 | public List ListVector2Field; 13 | 14 | public static UnityTypes GenerateRandomValues() 15 | { 16 | UnityTypes value = new UnityTypes(); 17 | var random = new Random(0); 18 | value.Vector2Field = new Vector2((float)random.NextDouble(), (float)random.NextDouble()); 19 | value.Vector3Field = new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); 20 | 21 | value.ListVector2Field = new List(); 22 | for (int i = 0; i < 3; i++) 23 | { 24 | value.ListVector2Field.Add(new Vector2((float)random.NextDouble(), (float)random.NextDouble())); 25 | } 26 | 27 | return value; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tests/UIFixes/FromJsonTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using NUnit.Framework; 3 | using TranslationCommon.Fonts; 4 | using TranslationCommon.SimpleJSON; 5 | 6 | namespace Tests.UIFixes 7 | { 8 | [TestFixture] 9 | public class FromJsonTests 10 | { 11 | [Test] 12 | public void TestFromJsonBasic() 13 | { 14 | string json = 15 | "{\"ActionFixMap\":{\"OnInit\":{\"t:UIOptionWindow\":[{\"Path\":\"*details.content*.*\",\"Fix\":{\"anchoredPosition\":\"(+75, +0)\"}}]}}}"; 16 | var data = JSON.FromJson(json); 17 | NUnit.Framework.Assert.IsTrue(data.ActionFixMap.Count == 1); 18 | var action = data.ActionFixMap["OnInit"]; 19 | Assert.NotNull(action); 20 | var fixes = action.GetByType(new TestableUIBehaviourComponent() 21 | { 22 | Type = "UIOptionWindow" 23 | }); 24 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 25 | NUnit.Framework.Assert.AreEqual("*details.content*.*", fixes[0].Path); 26 | NUnit.Framework.Assert.IsInstanceOf(fixes[0].Fix[0]); 27 | var rectFix = (RectFix)fixes[0].Fix[0]; 28 | NUnit.Framework.Assert.AreEqual("(+75, +0)", rectFix.anchoredPosition); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Tests/UIFixes/RangeValueTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using NUnit.Framework; 5 | using TranslationCommon.Fonts; 6 | 7 | namespace Tests.UIFixes 8 | { 9 | [TestFixture] 10 | public class RangeValueTests 11 | { 12 | [TestCase("-7", -7, 0, 0)] 13 | [TestCase("-7", -7, -10, 1)] 14 | [TestCase("0", 0, 0, 1)] 15 | [TestCase("0", 0, -1, 1)] 16 | [TestCase("1", 1)] 17 | [TestCase("4", 4)] 18 | public void RangeValue_Equal_Test(string test, int expected, int startRange = 0, int expectedCount = 1) 19 | { 20 | var range = RangeValue.Parse(test); 21 | range.Reset(startRange); 22 | var count = 0; 23 | while (range.TryGetNextMatch(out var match)) 24 | { 25 | NUnit.Framework.Assert.AreEqual(expected, match); 26 | count++; 27 | } 28 | 29 | NUnit.Framework.Assert.AreEqual(expectedCount, count); 30 | } 31 | 32 | [TestCase("<-7", new []{-10, -9, -8}, -10)] 33 | [TestCase("<0", new int[]{}, 0)] 34 | [TestCase("<4", new []{0, 1, 2, 3})] 35 | public void RangeValue_LessThan_Test(string test, int[] expected, int startRange = 0) 36 | { 37 | RangeValueTestRange(test, expected, startRange); 38 | } 39 | 40 | [TestCase("<=-7", new []{-10, -9, -8, -7}, -10)] 41 | [TestCase("<=0", new int[]{0})] 42 | [TestCase("<=4", new []{0, 1, 2, 3, 4})] 43 | public void RangeValue_LessThanEqual_Test(string test, int[] expected, int startRange = 0) 44 | { 45 | RangeValueTestRange(test, expected, startRange); 46 | } 47 | 48 | [TestCase(">-7", new []{-6, -5, -4,}, -10)] 49 | [TestCase(">0", new int[]{1, 2, 3})] 50 | [TestCase(">4", new []{5, 6, 7})] 51 | public void RangeValue_GreaterThen_Test(string test, int[] expected, int startRange = 0, int iterations = 3) 52 | { 53 | RangeValueTestRange(test, expected, startRange, iterations); 54 | } 55 | 56 | [TestCase(">=-7", new []{-7, -6, -5}, -10)] 57 | [TestCase(">=0", new int[]{0, 1, 2})] 58 | [TestCase(">=4", new []{4, 5, 6})] 59 | public void RangeValue_GreaterThenEqual_Test(string test, int[] expected, int startRange = 0, int iterations = 3) 60 | { 61 | RangeValueTestRange(test, expected, startRange, iterations); 62 | } 63 | 64 | [TestCase("-7..-5", new []{-7, -6, -5}, -10)] 65 | [TestCase("0..0", new int[]{0})] 66 | [TestCase("3..0", new int[]{0, 1, 2, 3})] 67 | [TestCase("0..3", new int[]{0, 1, 2, 3})] 68 | [TestCase("4..6", new []{4, 5, 6})] 69 | public void RangeValue_Between_Test(string test, int[] expected, int startRange = 0) 70 | { 71 | RangeValueTestRange(test, expected, startRange); 72 | } 73 | 74 | [TestCase("-7;-5", new []{-7, -5}, -10)] 75 | [TestCase("0;0", new int[]{0})] 76 | [TestCase("3;0", new int[]{0, 3})] 77 | [TestCase("0;3", new int[]{0, 3})] 78 | [TestCase("4;6", new []{4, 6})] 79 | public void RangeValue_Separator_Test(string test, int[] expected, int startRange = 0) 80 | { 81 | RangeValueTestRange(test, expected, startRange); 82 | } 83 | 84 | [TestCase("3..5; 8; >12", new []{3,4,5,8,13,14}, 6)] 85 | public void RangeValue_Complex_Test(string test, int[] expected, int iterations) 86 | { 87 | RangeValueTestRange(test, expected, 0, iterations); 88 | } 89 | 90 | private static void RangeValueTestRange(string test, int[] expected, int startRange, int iterations = int.MaxValue) 91 | { 92 | var range = RangeValue.Parse(test); 93 | range.Reset(startRange); 94 | var count = 0; 95 | while (range.TryGetNextMatch(out var match) && count < iterations) 96 | { 97 | NUnit.Framework.Assert.AreEqual(true, expected.Contains(match), 98 | $"Expected [{expected.Select(x => x.ToString()).Aggregate((x,y) => $"{x}, {y}")}]\n" + 99 | $"But was [{match}] at Count: {count}"); 100 | count++; 101 | } 102 | 103 | NUnit.Framework.Assert.AreEqual(expected.Length, count); 104 | } 105 | 106 | } 107 | } -------------------------------------------------------------------------------- /Tests/UIFixes/SimpleUIFixesTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using TranslationCommon.Fonts; 5 | using TranslationCommon.SimpleJSON; 6 | 7 | namespace Tests.UIFixes 8 | { 9 | [TestFixture] 10 | public class SimpleUIFixesTests 11 | { 12 | public UIBehaviourCache Cache = new UIBehaviourCache(); 13 | public List Components = new List(); 14 | 15 | [SetUp] 16 | public void Setup() 17 | { 18 | Cache = new UIBehaviourCache(); 19 | var actionFixesMap = Cache.UIFixes.ActionFixMap; 20 | var actionFixes = new UIActionFixes(); 21 | actionFixesMap.Add("OnCreate", actionFixes); 22 | var dictionaryFixes = new Dictionary>(); 23 | dictionaryFixes.Add("t:UIAction", new List() 24 | { 25 | new UIFix() 26 | { 27 | Path = "TypeTest" 28 | } 29 | }); 30 | dictionaryFixes.Add("p:simplePath", new List() 31 | { 32 | new UIFix() 33 | { 34 | Path = "SimplePath" 35 | } 36 | }); 37 | dictionaryFixes.Add("p:*wildCardPath", new List() 38 | { 39 | new UIFix() 40 | { 41 | Path = "StartWildCardPath" 42 | } 43 | }); 44 | dictionaryFixes.Add("p:wildCardPath*", new List() 45 | { 46 | new UIFix() 47 | { 48 | Path = "EndWildCardPath" 49 | } 50 | }); 51 | dictionaryFixes.Add("p:*multiMatches*", new List() 52 | { 53 | new UIFix() 54 | { 55 | Path = "MultiWildCardPath" 56 | } 57 | }); 58 | dictionaryFixes.Add("p:*details.content*.*", new List() 59 | { 60 | new UIFix() 61 | { 62 | Path = "ComplexWildCardPath" 63 | } 64 | }); 65 | actionFixes.Initialize(dictionaryFixes); 66 | 67 | var actionFixes2 = new UIActionFixes(); 68 | actionFixesMap.Add("OnInit", actionFixes2); 69 | var dictionaryFixes2 = new Dictionary>(); 70 | dictionaryFixes2.Add("t:UIAction", new List() 71 | { 72 | new UIFix() 73 | { 74 | Path = "TypeTest" 75 | } 76 | }); 77 | actionFixes2.Initialize(dictionaryFixes2); 78 | CreateBehaviourComponents(); 79 | } 80 | 81 | private void CreateBehaviourComponents() 82 | { 83 | Components.Add(new TestableUIBehaviourComponent() 84 | { 85 | Path = "Path", 86 | Type = "UIAction", 87 | }); 88 | } 89 | 90 | [Test] 91 | public void TestableUIBehaviour_TypeTest() 92 | { 93 | var testable = new TestableUIBehaviourComponent() 94 | { 95 | Path = "Path", 96 | Type = "UIAction", 97 | }; 98 | var fixes = Cache.GetFixes("OnCreate", testable); 99 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 100 | NUnit.Framework.Assert.AreEqual("TypeTest", fixes[0].Path); 101 | } 102 | 103 | [Test] 104 | public void TestableUIBehaviour_PathTest() 105 | { 106 | var testable = new TestableUIBehaviourComponent() 107 | { 108 | Path = "simplePath", 109 | Type = "Type", 110 | }; 111 | var fixes = Cache.GetFixes("OnCreate", testable); 112 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 113 | NUnit.Framework.Assert.AreEqual("SimplePath", fixes[0].Path); 114 | } 115 | 116 | [Test] 117 | public void TestableUIBehaviour_StartWildCardPathTest() 118 | { 119 | var testable = new TestableUIBehaviourComponent() 120 | { 121 | Path = "this.is.some.wildCardPath", 122 | Type = "Type", 123 | }; 124 | var fixes = Cache.GetFixes("OnCreate", testable); 125 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 126 | NUnit.Framework.Assert.AreEqual("StartWildCardPath", fixes[0].Path); 127 | } 128 | 129 | [Test] 130 | public void TestableUIBehaviour_EndWildCardPathTest() 131 | { 132 | var testable = new TestableUIBehaviourComponent() 133 | { 134 | Path = "wildCardPath.start.with.explosion!", 135 | Type = "Type", 136 | }; 137 | var fixes = Cache.GetFixes("OnCreate", testable); 138 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 139 | NUnit.Framework.Assert.AreEqual("EndWildCardPath", fixes[0].Path); 140 | } 141 | 142 | [Test] 143 | public void TestableUIBehaviour_MultiWildCardPathTest() 144 | { 145 | var testable = new TestableUIBehaviourComponent() 146 | { 147 | Path = "wildcards.multiMatches.rocks", 148 | Type = "Type", 149 | }; 150 | var fixes = Cache.GetFixes("OnCreate", testable); 151 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 152 | NUnit.Framework.Assert.AreEqual("MultiWildCardPath", fixes[0].Path); 153 | testable = new TestableUIBehaviourComponent() 154 | { 155 | Path = "multiMatches.rocks", 156 | Type = "Type", 157 | }; 158 | fixes = Cache.GetFixes("OnCreate", testable); 159 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 160 | NUnit.Framework.Assert.AreEqual("MultiWildCardPath", fixes[0].Path); 161 | testable = new TestableUIBehaviourComponent() 162 | { 163 | Path = "wildcards.multiMatches", 164 | Type = "Type", 165 | }; 166 | fixes = Cache.GetFixes("OnCreate", testable); 167 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 168 | NUnit.Framework.Assert.AreEqual("MultiWildCardPath", fixes[0].Path); 169 | } 170 | 171 | [Test] 172 | public void TestableUIBehaviour_MultiMatches() 173 | { 174 | var testable = new TestableUIBehaviourComponent() 175 | { 176 | Path = "simplePath", 177 | Type = "UIAction", 178 | }; 179 | var fixes = Cache.GetFixes("OnCreate", testable); 180 | NUnit.Framework.Assert.AreEqual(2, fixes.Count); 181 | NUnit.Framework.Assert.IsTrue(fixes.Select(x => x.Path).Contains("SimplePath")); 182 | NUnit.Framework.Assert.IsTrue(fixes.Select(x => x.Path).Contains("TypeTest")); 183 | } 184 | 185 | [Test] 186 | public void TestableUIBehaviour_ComplexWildCardPathTest() 187 | { 188 | var testable = new TestableUIBehaviourComponent() 189 | { 190 | Path = "Root.Menu.UI.details.content-1.path", 191 | Type = "Type", 192 | }; 193 | var fixes = Cache.GetFixes("OnCreate", testable); 194 | NUnit.Framework.Assert.AreEqual(1, fixes.Count); 195 | NUnit.Framework.Assert.AreEqual("ComplexWildCardPath", fixes[0].Path); 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /Tests/UIFixes/TestableUIBehaviourComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using TranslationCommon.Fonts; 5 | 6 | namespace Tests.UIFixes 7 | { 8 | public class TestableUIBehaviourComponent : IBehaviourComponent 9 | { 10 | public string Type; 11 | public string Path; 12 | 13 | public string GetComponentType() 14 | { 15 | return Type; 16 | } 17 | 18 | public string GetComponentPath() 19 | { 20 | return Path; 21 | } 22 | 23 | public List GetComponentsByField(string field) 24 | where T : UnityEngine.Component 25 | { 26 | throw new System.NotImplementedException(); 27 | } 28 | 29 | public List GetComponentsByPath(string path, List except, RangeValue matchChildrenLayersCount) 30 | where T : UnityEngine.Component 31 | { 32 | throw new System.NotImplementedException(); 33 | } 34 | 35 | public List GetComponentsByType(RangeValue matchChildrenLayersCount) 36 | where T : UnityEngine.Component 37 | { 38 | throw new System.NotImplementedException(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /TranslationCommon/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Logging; 2 | 3 | namespace TranslationCommon 4 | { 5 | /// 6 | /// Console logger to output to BepInEx console 7 | /// 8 | public static class ConsoleLogger 9 | { 10 | public static readonly ManualLogSource LogSource; 11 | static ConsoleLogger() 12 | { 13 | LogSource = Logger.CreateLogSource(nameof(ConsoleLogger)); 14 | } 15 | 16 | public static void Log(LogLevel level, object log) 17 | { 18 | LogSource.Log(level, log); 19 | } 20 | 21 | public static void LogDebug(object log) 22 | { 23 | LogSource.LogDebug(log); 24 | } 25 | 26 | public static void LogError(object log) 27 | { 28 | LogSource.LogError(log); 29 | } 30 | 31 | public static void LogFatal(object log) 32 | { 33 | LogSource.LogFatal(log); 34 | } 35 | 36 | public static void LogInfo(object log) 37 | { 38 | LogSource.LogInfo(log); 39 | } 40 | 41 | public static void LogWarning(object log) 42 | { 43 | LogSource.LogWarning(log); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/CustomFontData.cs: -------------------------------------------------------------------------------- 1 | using Font = UnityEngine.Font; 2 | 3 | namespace DSPTranslationPlugin.UnityHarmony 4 | { 5 | /// 6 | /// Custom font data container 7 | /// 8 | public class CustomFontData 9 | { 10 | /// 11 | /// Is using default font or custom font 12 | /// 13 | public bool IsUsingCustomFont; 14 | /// 15 | /// Custom font data 16 | /// 17 | public Font CustomFont; 18 | } 19 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/IBehaviourComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace TranslationCommon.Fonts 5 | { 6 | public interface IBehaviourComponent 7 | { 8 | string GetComponentType(); 9 | string GetComponentPath(); 10 | 11 | List GetComponentsByField(string field) where T : Component; 12 | 13 | List GetComponentsByPath(string path, List except, RangeValue matchChildrenRange) where T : Component; 14 | 15 | List GetComponentsByType(RangeValue matchChildrenRange) where T : Component; 16 | } 17 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/IFix.cs: -------------------------------------------------------------------------------- 1 | namespace TranslationCommon.Fonts 2 | { 3 | public interface IFix 4 | { 5 | void Evaluate(UIFix parent, IBehaviourComponent component); 6 | } 7 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/RangeValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using UnityEngine; 5 | 6 | namespace TranslationCommon.Fonts 7 | { 8 | /// 9 | /// Range value that can 10 | /// 11 | /// 12 | /// Following strings will provide following range 13 | /// "4" - match only index equal to 4 14 | /// "<4" - match indexes that are larger then 4 15 | /// ">4" - match indexes that are smaller then 4 16 | /// "<=4" - match indexes that are larger or equal 4 17 | /// ">=4" - match indexes that are smaller or equal 4 18 | /// "2..4" - match indexes between 2 inclusive and 4 inclusive 19 | /// "2;4" - match indexes 2 and 4 20 | /// Complex example: 21 | /// "3..5; 8; <12" - match indexes 3 to 5 inclusive, index 8 and all above 12 22 | /// 23 | public class RangeValue 24 | { 25 | /// 26 | /// All tokens used for this range 27 | /// 28 | private readonly List _rangeTokens = new List(); 29 | /// 30 | /// Only currently valid tokens for this iteration 31 | /// 32 | private readonly List _validRangeTokens = new List(); 33 | 34 | /// 35 | /// Current index 36 | /// 37 | private int _currentIndex = 0; 38 | 39 | /// 40 | /// Parse string to RangeValue 41 | /// 42 | /// 43 | /// 44 | public static RangeValue Parse(string rangeString) 45 | { 46 | return new RangeValue(rangeString); 47 | } 48 | 49 | /// 50 | /// Hide default constructor 51 | /// 52 | [Obsolete] 53 | private RangeValue() 54 | { 55 | 56 | } 57 | 58 | /// 59 | /// Default constructor 60 | /// 61 | /// 62 | private RangeValue(string rangeString) 63 | { 64 | var split = rangeString.Replace(" ", "").Split(';'); 65 | foreach (var s in split) 66 | { 67 | var token = Token.GetMatchingToken(s); 68 | _rangeTokens.Add(token); 69 | if (token.TryGetNextValid(_currentIndex, out var nextValid)) 70 | { 71 | _validRangeTokens.Add(token); 72 | } 73 | } 74 | } 75 | 76 | /// 77 | /// Try get next match, returns next valid index 78 | /// 79 | /// 80 | /// 81 | public bool TryGetNextMatch(out int match) 82 | { 83 | match = Int32.MaxValue; 84 | for (var i = 0; i < _validRangeTokens.Count; i++) 85 | { 86 | var rangeToken = _validRangeTokens[i]; 87 | if (rangeToken.TryGetNextValid(_currentIndex, out var nextValid)) 88 | { 89 | match = Mathf.Min(match, nextValid); 90 | } 91 | else 92 | { 93 | _validRangeTokens.Remove(rangeToken); 94 | i--; 95 | } 96 | } 97 | 98 | if (match != Int32.MaxValue) 99 | { 100 | _currentIndex = match + 1; 101 | return true; 102 | } 103 | 104 | match = 0; 105 | return false; 106 | } 107 | 108 | /// 109 | /// Try get next match from provided index, skipping range optimizations 110 | /// 111 | /// 112 | /// 113 | /// 114 | public bool TryGetMatch(int currentIndex, out int match) 115 | { 116 | match = Int32.MaxValue; 117 | for (var i = 0; i < _rangeTokens.Count; i++) 118 | { 119 | var rangeToken = _rangeTokens[i]; 120 | if (rangeToken.TryGetNextValid(currentIndex, out var nextValid)) 121 | { 122 | match = Mathf.Min(match, nextValid); 123 | } 124 | } 125 | 126 | if (match != Int32.MaxValue) 127 | { 128 | return true; 129 | } 130 | 131 | match = 0; 132 | return false; 133 | } 134 | 135 | /// 136 | /// Resets range iteration 137 | /// 138 | /// 139 | public void Reset(int startRangeValue = 0) 140 | { 141 | _currentIndex = startRangeValue; 142 | _validRangeTokens.Clear(); 143 | foreach (var rangeToken in _rangeTokens) 144 | { 145 | if (rangeToken.TryGetNextValid(_currentIndex, out var nextValid)) 146 | { 147 | _validRangeTokens.Add(rangeToken); 148 | } 149 | } 150 | } 151 | 152 | /// 153 | /// Base class for token 154 | /// 155 | private abstract class Token 156 | { 157 | /// 158 | /// Create token that matches provided string, string needs to be a single value 159 | /// 160 | /// 161 | /// 162 | /// 163 | public static Token GetMatchingToken(string tokenString) 164 | { 165 | if (Equal.TryParse(tokenString, out var token)) return token; 166 | if (LessThen.TryParse(tokenString, out token)) return token; 167 | if (LessThenEqual.TryParse(tokenString, out token)) return token; 168 | if (GreaterThen.TryParse(tokenString, out token)) return token; 169 | if (GreaterThenEqual.TryParse(tokenString, out token)) return token; 170 | if (Between.TryParse(tokenString, out token)) return token; 171 | 172 | throw new Exception($"Provided string was invalid {tokenString}, please provide valid range token"); 173 | } 174 | 175 | /// 176 | /// Try get next value, returns next valid value and true if possible, else false 177 | /// 178 | /// Current range value 179 | /// Next valid output 180 | /// 181 | public abstract bool TryGetNextValid(int current, out int nextValid); 182 | } 183 | 184 | /// 185 | /// Token for Equal operation 186 | /// 187 | private class Equal : Token 188 | { 189 | private int _value; 190 | 191 | public static bool TryParse(string tokenString, out Token token) 192 | { 193 | if (int.TryParse(tokenString, out var result)) 194 | { 195 | token = new Equal() 196 | { 197 | _value = result 198 | }; 199 | return true; 200 | } 201 | token = null; 202 | return false; 203 | } 204 | 205 | public override bool TryGetNextValid(int current, out int nextValid) 206 | { 207 | if (current <= _value) 208 | { 209 | nextValid = _value; 210 | return true; 211 | } 212 | 213 | nextValid = 0; 214 | return false; 215 | } 216 | } 217 | 218 | /// 219 | /// Token for LessThen operation 220 | /// 221 | private class LessThen : Token 222 | { 223 | private int _value; 224 | 225 | public static bool TryParse(string tokenString, out Token token) 226 | { 227 | if (tokenString[0] == '<' && 228 | int.TryParse(tokenString.Remove(0, 1), out var result)) 229 | { 230 | token = new LessThen() 231 | { 232 | _value = result 233 | }; 234 | return true; 235 | } 236 | token = null; 237 | return false; 238 | } 239 | 240 | public override bool TryGetNextValid(int current, out int nextValid) 241 | { 242 | if (current < _value) 243 | { 244 | nextValid = current; 245 | return true; 246 | } 247 | 248 | nextValid = 0; 249 | return false; 250 | } 251 | } 252 | 253 | /// 254 | /// Token for LessThenEqual operation 255 | /// 256 | private class LessThenEqual : Token 257 | { 258 | private int _value; 259 | 260 | public static bool TryParse(string tokenString, out Token token) 261 | { 262 | if (tokenString[0] == '<' && tokenString[1] == '=' && 263 | int.TryParse(tokenString.Remove(0, 2), out var result)) 264 | { 265 | token = new LessThenEqual() 266 | { 267 | _value = result 268 | }; 269 | return true; 270 | } 271 | token = null; 272 | return false; 273 | } 274 | 275 | public override bool TryGetNextValid(int current, out int nextValid) 276 | { 277 | if (current <= _value) 278 | { 279 | nextValid = current; 280 | return true; 281 | } 282 | 283 | nextValid = 0; 284 | return false; 285 | } 286 | } 287 | 288 | /// 289 | /// Token for GreaterThen operation 290 | /// 291 | private class GreaterThen : Token 292 | { 293 | private int _value; 294 | 295 | public static bool TryParse(string tokenString, out Token token) 296 | { 297 | if (tokenString[0] == '>' && 298 | int.TryParse(tokenString.Remove(0, 1), out var result)) 299 | { 300 | token = new GreaterThen() 301 | { 302 | _value = result 303 | }; 304 | return true; 305 | } 306 | token = null; 307 | return false; 308 | } 309 | 310 | public override bool TryGetNextValid(int current, out int nextValid) 311 | { 312 | if (current > _value) 313 | { 314 | nextValid = current; 315 | return true; 316 | } 317 | else 318 | { 319 | nextValid = _value + 1; 320 | return true; 321 | } 322 | } 323 | } 324 | 325 | /// 326 | /// Token for GreaterThenEqual operation 327 | /// 328 | private class GreaterThenEqual : Token 329 | { 330 | private int _value; 331 | 332 | public static bool TryParse(string tokenString, out Token token) 333 | { 334 | if (tokenString[0] == '>' && tokenString[1] == '=' && 335 | int.TryParse(tokenString.Remove(0, 2), out var result)) 336 | { 337 | token = new GreaterThenEqual() 338 | { 339 | _value = result 340 | }; 341 | return true; 342 | } 343 | token = null; 344 | return false; 345 | } 346 | 347 | public override bool TryGetNextValid(int current, out int nextValid) 348 | { 349 | if (current >= _value) 350 | { 351 | nextValid = current; 352 | return true; 353 | } 354 | else 355 | { 356 | nextValid = _value; 357 | return true; 358 | } 359 | } 360 | } 361 | 362 | /// 363 | /// Token for Between operation 364 | /// 365 | private class Between : Token 366 | { 367 | private int _lessThen; 368 | private int _greaterThen; 369 | 370 | public static bool TryParse(string tokenString, out Token token) 371 | { 372 | var split = tokenString.Split(new[] {".."}, StringSplitOptions.RemoveEmptyEntries); 373 | 374 | if (int.TryParse(split[0], out var lessThen) && 375 | int.TryParse(split[1], out var greaterThen)) 376 | { 377 | token = new Between() 378 | { 379 | _lessThen = lessThen < greaterThen ? lessThen : greaterThen, 380 | _greaterThen = lessThen < greaterThen ? greaterThen : lessThen, 381 | }; 382 | return true; 383 | } 384 | 385 | token = null; 386 | return false; 387 | } 388 | 389 | public override bool TryGetNextValid(int current, out int nextValid) 390 | { 391 | if (current <= _lessThen) 392 | { 393 | nextValid = _lessThen; 394 | return true; 395 | } 396 | else if (current >= _lessThen && current <= _greaterThen) 397 | { 398 | nextValid = current; 399 | return true; 400 | } 401 | 402 | nextValid = 0; 403 | return false; 404 | } 405 | } 406 | } 407 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/RectFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace TranslationCommon.Fonts 5 | { 6 | public class RectFix : IFix 7 | { 8 | public string pivot; 9 | public string anchoredPosition; 10 | public string anchorMax; 11 | public string anchorMin; 12 | public string offsetMax; 13 | public string offsetMin; 14 | public string sizeDelta; 15 | 16 | public void Evaluate(UIFix parent, IBehaviourComponent component) 17 | { 18 | var rectTransforms = parent.GetComponents(component); 19 | foreach (var transform in rectTransforms) 20 | { 21 | Fix(transform); 22 | } 23 | } 24 | 25 | private void Fix(RectTransform rTrans) 26 | { 27 | if (!string.IsNullOrEmpty(pivot)) 28 | { 29 | rTrans.pivot = rTrans.pivot.SetRelative(pivot); 30 | } 31 | if (!string.IsNullOrEmpty(anchoredPosition)) 32 | { 33 | rTrans.anchoredPosition = rTrans.anchoredPosition.SetRelative(anchoredPosition); 34 | } 35 | if (!string.IsNullOrEmpty(anchorMax)) 36 | { 37 | rTrans.anchorMax = rTrans.anchorMax.SetRelative(anchorMax); 38 | } 39 | if (!string.IsNullOrEmpty(anchorMin)) 40 | { 41 | rTrans.anchorMin = rTrans.anchorMin.SetRelative(anchorMin); 42 | } 43 | if (!string.IsNullOrEmpty(offsetMax)) 44 | { 45 | rTrans.offsetMax = rTrans.offsetMax.SetRelative(offsetMax); 46 | } 47 | if (!string.IsNullOrEmpty(offsetMin)) 48 | { 49 | rTrans.offsetMin = rTrans.offsetMin.SetRelative(offsetMin); 50 | } 51 | if (!string.IsNullOrEmpty(sizeDelta)) 52 | { 53 | rTrans.sizeDelta = rTrans.sizeDelta.SetRelative(sizeDelta); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/RelativeValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using UnityEngine; 4 | 5 | namespace TranslationCommon.Fonts 6 | { 7 | public static class RelativeValue 8 | { 9 | public static int SetRelative(this int value, string relative) 10 | { 11 | var sign = relative[0]; 12 | var relVal = int.Parse(relative.Substring(1)); 13 | switch (sign) 14 | { 15 | case '@': 16 | return relVal; 17 | case '+': 18 | return value + relVal; 19 | case '-': 20 | return value - relVal; 21 | case '*': 22 | return value * relVal; 23 | case '/': 24 | return value / relVal; 25 | case '%': 26 | return value % relVal; 27 | case '^': 28 | return (int)Mathf.Pow(value, relVal); 29 | } 30 | 31 | return relVal; 32 | } 33 | 34 | public static float SetRelative(this float value, string relative) 35 | { 36 | var sign = relative[0]; 37 | var relVal = float.Parse(relative.Substring(1)); 38 | switch (sign) 39 | { 40 | case '@': 41 | return relVal; 42 | case '+': 43 | return value + relVal; 44 | case '-': 45 | return value - relVal; 46 | case '*': 47 | return value * relVal; 48 | case '/': 49 | return value / relVal; 50 | case '%': 51 | return value % relVal; 52 | case '^': 53 | return Mathf.Pow(value, relVal); 54 | } 55 | 56 | return relVal; 57 | } 58 | 59 | public static double SetRelative(this double value, string relative) 60 | { 61 | var sign = relative[0]; 62 | var relVal = double.Parse(relative.Substring(1)); 63 | switch (sign) 64 | { 65 | case '@': 66 | return relVal; 67 | case '+': 68 | return value + relVal; 69 | case '-': 70 | return value - relVal; 71 | case '*': 72 | return value * relVal; 73 | case '/': 74 | return value / relVal; 75 | case '%': 76 | return value % relVal; 77 | case '^': 78 | return Math.Pow(value, relVal); 79 | } 80 | 81 | return relVal; 82 | } 83 | 84 | public static Vector2 SetRelative(this Vector2 value, string relative) 85 | { 86 | if (relative.StartsWith("(") && relative.EndsWith(")") && relative.Count(c => c == ',') == 1) 87 | { 88 | relative = relative.Substring(1, relative.Length - 2); 89 | relative = relative.Replace(" ", ""); 90 | var split = relative.Split(','); 91 | return new Vector2( 92 | value[0].SetRelative(split[0]), 93 | value[1].SetRelative(split[1]) 94 | ); 95 | } 96 | throw new Exception($"Expected \"(value, value)\" got \"{relative}\""); 97 | } 98 | 99 | public static Vector3 SetRelative(this Vector3 value, string relative) 100 | { 101 | if (relative.StartsWith("(") && relative.EndsWith(")") && relative.Count(c => c == ',') == 2) 102 | { 103 | relative = relative.Substring(1, relative.Length - 2); 104 | relative = relative.Replace(" ", ""); 105 | var split = relative.Split(','); 106 | return new Vector3( 107 | value[0].SetRelative(split[0]), 108 | value[1].SetRelative(split[1]), 109 | value[2].SetRelative(split[2]) 110 | ); 111 | } 112 | throw new Exception($"Expected \"(value, value, value)\" got \"{relative}\""); 113 | } 114 | 115 | public static Rect SetRelative(this Rect value, string relative) 116 | { 117 | if (relative.StartsWith("(") && relative.EndsWith(")") && relative.Count(c => c == ',') == 2) 118 | { 119 | relative = relative.Substring(1, relative.Length - 2); 120 | relative = relative.Replace(" ", ""); 121 | var split = relative.Split(','); 122 | return new Rect( 123 | value.x.SetRelative(split[0]), 124 | value.y.SetRelative(split[1]), 125 | value.width.SetRelative(split[2]), 126 | value.height.SetRelative(split[3]) 127 | ); 128 | } 129 | throw new Exception($"Expected \"(x, y, width, height)\" got \"{relative}\""); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/TextFix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.UI; 4 | 5 | namespace TranslationCommon.Fonts 6 | { 7 | public class TextFix : IFix 8 | { 9 | public TextAnchor? alignment; 10 | public string fontSize; 11 | public FontStyle? fontStyle; 12 | public HorizontalWrapMode? horizontalOverflow; 13 | public string lineSpacing; 14 | public VerticalWrapMode? verticalOverflow; 15 | public bool? alignByGeometry; 16 | public bool? resizeTextForBestFit; 17 | public string resizeTextMaxSize; 18 | public string resizeTextMinSize; 19 | public Color? color; 20 | 21 | 22 | public void Evaluate(UIFix parent, IBehaviourComponent component) 23 | { 24 | var texts = parent.GetComponents(component); 25 | foreach (var text in texts) 26 | { 27 | Fix(text); 28 | } 29 | } 30 | 31 | private void Fix(Text text) 32 | { 33 | if (alignment != null) 34 | { 35 | text.alignment = alignment.Value; 36 | } 37 | if (!string.IsNullOrEmpty(fontSize)) 38 | { 39 | text.fontSize = text.fontSize.SetRelative(fontSize); 40 | } 41 | if (fontStyle != null) 42 | { 43 | text.fontStyle = fontStyle.Value; 44 | } 45 | if (horizontalOverflow != null) 46 | { 47 | text.horizontalOverflow = horizontalOverflow.Value; 48 | } 49 | if (!string.IsNullOrEmpty(lineSpacing)) 50 | { 51 | text.lineSpacing = text.lineSpacing.SetRelative(lineSpacing); 52 | } 53 | if (verticalOverflow != null) 54 | { 55 | text.verticalOverflow = verticalOverflow.Value; 56 | } 57 | if (alignByGeometry != null) 58 | { 59 | text.alignByGeometry = alignByGeometry.Value; 60 | } 61 | if (resizeTextForBestFit != null) 62 | { 63 | text.resizeTextForBestFit = resizeTextForBestFit.Value; 64 | } 65 | if (!string.IsNullOrEmpty(resizeTextMaxSize)) 66 | { 67 | text.resizeTextMaxSize = text.resizeTextMaxSize.SetRelative(resizeTextMaxSize); 68 | } 69 | if (!string.IsNullOrEmpty(resizeTextMinSize)) 70 | { 71 | text.resizeTextMinSize = text.resizeTextMinSize.SetRelative(resizeTextMinSize); 72 | } 73 | if (color != null) 74 | { 75 | text.color = color.Value; 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/UIActionFixes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using TranslationCommon.SimpleJSON; 3 | using UnityEngine; 4 | 5 | namespace TranslationCommon.Fonts 6 | { 7 | public class UIActionFixes 8 | { 9 | [SerializeField] 10 | [SerializeFirstAsObject] 11 | private Dictionary> _serializedFixMap = new Dictionary>(); 12 | 13 | private Dictionary> _typeFixMap; 14 | private List _pathFixMap; 15 | 16 | public List GetByType(IBehaviourComponent component) 17 | { 18 | if (_typeFixMap == null) 19 | { 20 | Initialize(_serializedFixMap); 21 | } 22 | 23 | var name = component.GetComponentType(); 24 | return _typeFixMap.ContainsKey(name) ? _typeFixMap[name] : new List(); 25 | } 26 | 27 | public List GetByPath(IBehaviourComponent component) 28 | { 29 | if (_pathFixMap == null) 30 | { 31 | Initialize(_serializedFixMap); 32 | } 33 | 34 | var name = component.GetComponentPath(); 35 | var fixes = new List(); 36 | for (int i = 0; i < _pathFixMap.Count; i++) 37 | { 38 | if (name.EqualsWildcard(_pathFixMap[i].Path)) 39 | { 40 | fixes.AddRange(_pathFixMap[i].Fixes); 41 | } 42 | } 43 | return fixes; 44 | } 45 | 46 | public void Initialize(Dictionary> serializedFixesMap) 47 | { 48 | _serializedFixMap = serializedFixesMap; 49 | _typeFixMap = new Dictionary>(); 50 | _pathFixMap = new List(); 51 | foreach (var pair in serializedFixesMap) 52 | { 53 | if (pair.Key.StartsWith("t:")) 54 | { 55 | var key = pair.Key.Remove(0,2); 56 | _typeFixMap.Add(key, pair.Value); 57 | } 58 | else if (pair.Key.StartsWith("p:")) 59 | { 60 | var path = pair.Key.Remove(0,2); 61 | _pathFixMap.Add(new UIPathTarget() 62 | { 63 | Path = path, 64 | Fixes = pair.Value, 65 | }); 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/UIBehaviourCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace TranslationCommon.Fonts 5 | { 6 | /// 7 | /// Cache of all possible fixes available to be used 8 | /// 9 | public class UIBehaviourCache 10 | { 11 | /// 12 | /// List of fixes - serialized to json 13 | /// 14 | public UIFixesData UIFixes = new UIFixesData(); 15 | 16 | /// 17 | /// Get fixes for requested component 18 | /// 19 | /// 20 | /// 21 | /// 22 | public List GetFixes(string actionName, IBehaviourComponent component) 23 | { 24 | var fixes = new List(); 25 | var action = UIFixes.Get(actionName); 26 | if (action == null) 27 | { 28 | return null; 29 | } 30 | 31 | fixes.AddRange(action.GetByType(component)); 32 | fixes.AddRange(action.GetByPath(component)); 33 | if (fixes.Count != 0) 34 | { 35 | ConsoleLogger.LogDebug($"Match! {fixes.Count} {component.GetComponentPath()}"); 36 | } 37 | return fixes.Count != 0 ? fixes : null; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/UIBehaviourComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using TranslationCommon.SimpleJSON; 6 | using UnityEngine; 7 | 8 | namespace TranslationCommon.Fonts 9 | { 10 | /* 11 | * ActionFixMap 12 | * { 13 | * "OnCreate" : { 14 | * "t:UIAction":{... 15 | * } 16 | * "p:*dasdas":{...} 17 | * "p:*dasdas":{...} 18 | * } 19 | * } 20 | * 21 | * { 22 | * Type: "Text", 23 | * Path: "content1", 24 | * Field: "_dspText", 25 | * Text: [], 26 | * RectTransform: [], 27 | * } 28 | */ 29 | 30 | public class UIBehaviourComponent : IBehaviourComponent 31 | { 32 | private static UIBehaviourCache _behaviourCache; 33 | 34 | private readonly ManualBehaviour _behaviour; 35 | 36 | public UIBehaviourComponent(ManualBehaviour behaviour) 37 | { 38 | _behaviour = behaviour; 39 | } 40 | 41 | public static UIBehaviourCache BehaviourCache 42 | { 43 | get 44 | { 45 | if (_behaviourCache == null) 46 | { 47 | var path = $"{Utils.ConfigPath}/Translation/fontFix.json"; 48 | if (File.Exists(path)) 49 | { 50 | _behaviourCache = new UIBehaviourCache(); 51 | var json = File.ReadAllText(path); 52 | _behaviourCache.UIFixes = JSON.FromJson(json); 53 | } 54 | } 55 | 56 | return _behaviourCache; 57 | } 58 | } 59 | 60 | public string GetComponentType() 61 | { 62 | return _behaviour.GetType().Name; 63 | } 64 | 65 | public string GetComponentPath() 66 | { 67 | return GetComponentPath(_behaviour); 68 | } 69 | 70 | public List GetComponentsByField(string field) where T : Component 71 | { 72 | var fieldInfos = _behaviour.GetType() 73 | .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 74 | var limitedFieldInfos = fieldInfos 75 | .Where(info => info.FieldType.IsAssignableFrom(typeof(T))) 76 | .Where(info => info.FieldType.Name.EqualsWildcard(field)); 77 | return limitedFieldInfos.Select(info => (T) info.GetValue(_behaviour)).ToList(); 78 | } 79 | 80 | public List GetComponentsByPath(string path, List except, RangeValue matchChildrenRange) where T : Component 81 | { 82 | var components = _behaviour.GetComponentsInChildrenWithSelf(matchChildrenRange); 83 | return components 84 | .Where(arg => 85 | { 86 | var componentPath = GetComponentPath(arg); 87 | if (except != null && except.Any(ex => componentPath.EqualsWildcard(ex))) 88 | { 89 | return false; 90 | } 91 | return componentPath.EqualsWildcard(path); 92 | }) 93 | .ToList(); 94 | } 95 | 96 | public List GetComponentsByType(RangeValue matchChildrenRange) where T : Component 97 | { 98 | return _behaviour.GetComponentsInChildrenWithSelf(matchChildrenRange).ToList(); 99 | } 100 | 101 | public static string GetComponentPath(Component component) 102 | { 103 | var pathList = new List(); 104 | var current = component.gameObject.transform; 105 | while (current != null) 106 | { 107 | pathList.Add(current.name); 108 | current = current.parent; 109 | } 110 | 111 | pathList.Reverse(); 112 | var join = string.Join(".", pathList.ToArray()); 113 | return join; 114 | } 115 | 116 | public void OnCreate() 117 | { 118 | BehaviourCache?.GetFixes("OnCreate", this)? 119 | .ForEach(fix => fix.Fix.ForEach(f => f.Evaluate(fix, this))); 120 | } 121 | 122 | public void OnInit() 123 | { 124 | BehaviourCache?.GetFixes("OnInit", this)? 125 | .ForEach(fix => fix.Fix.ForEach(f => f.Evaluate(fix, this))); 126 | } 127 | 128 | public void OnOpen() 129 | { 130 | BehaviourCache?.GetFixes("OnOpen", this)? 131 | .ForEach(fix => fix.Fix.ForEach(f => f.Evaluate(fix, this))); 132 | } 133 | 134 | public void OnUpdate() 135 | { 136 | BehaviourCache?.GetFixes("OnUpdate", this)? 137 | .ForEach(fix => fix.Fix.ForEach(f => f.Evaluate(fix, this))); 138 | } 139 | 140 | public void OnLateUpdate() 141 | { 142 | BehaviourCache?.GetFixes("OnLateUpdate", this)? 143 | .ForEach(fix => fix.Fix.ForEach(f => f.Evaluate(fix, this))); 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/UIFix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace TranslationCommon.Fonts 6 | { 7 | public class UIFix 8 | { 9 | public string Path; 10 | public string Field; 11 | 12 | public List ExceptPath; 13 | 14 | public string MatchChildrenRange; 15 | 16 | public List Fix; 17 | 18 | public List GetComponents(IBehaviourComponent component) 19 | where T : Component 20 | { 21 | if (!String.IsNullOrEmpty(Path)) 22 | { 23 | return component.GetComponentsByPath(Path, ExceptPath, !String.IsNullOrEmpty(MatchChildrenRange) ? RangeValue.Parse(MatchChildrenRange) : null); 24 | } 25 | else if (!String.IsNullOrEmpty(Field)) 26 | { 27 | return component.GetComponentsByField(Field); 28 | } 29 | else 30 | { 31 | return component.GetComponentsByType(!String.IsNullOrEmpty(MatchChildrenRange) ? RangeValue.Parse(MatchChildrenRange) : null); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/UIFixesData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TranslationCommon.Fonts 4 | { 5 | public class UIFixesData 6 | { 7 | public Dictionary ActionFixMap = new Dictionary(); 8 | 9 | /// 10 | /// Get action fixes 11 | /// 12 | /// 13 | /// 14 | public UIActionFixes Get(string actionName) 15 | { 16 | if (ActionFixMap.ContainsKey(actionName)) 17 | { 18 | return ActionFixMap[actionName]; 19 | } 20 | 21 | return null; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/Fixes/UIPathTarget.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TranslationCommon.Fonts 4 | { 5 | public class UIPathTarget 6 | { 7 | public string Path; 8 | public List Fixes; 9 | } 10 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/TextDefaultFont.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using HarmonyLib; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | 8 | namespace DSPTranslationPlugin.UnityHarmony 9 | { 10 | /// 11 | /// Additional container for Text component - this should be always present but it's not a monobehaviour. 12 | /// Use static api of to request underlying font information 13 | /// 14 | public class TextDefaultFont 15 | { 16 | /// 17 | /// Field info helper to access font data 18 | /// 19 | private readonly static FieldInfo FieldInfo_FontData = AccessTools.Field(typeof(Text), "m_FontData"); 20 | 21 | /// 22 | /// Default font used by this component 23 | /// 24 | public Font DefaultFont; 25 | 26 | /// 27 | /// Stored text refernece 28 | /// 29 | public Text Reference; 30 | 31 | /// 32 | /// FontData private field reference 33 | /// 34 | private FontData FontData; 35 | 36 | /// 37 | /// Default constructor 38 | /// 39 | /// 40 | public TextDefaultFont(Text reference) 41 | { 42 | Reference = reference; 43 | FontData = (FontData)FieldInfo_FontData.GetValue(Reference); 44 | DefaultFont = FontData.font; 45 | } 46 | 47 | /// 48 | /// Method invoked exclusively by 49 | /// 50 | public void OnGetFont() 51 | { 52 | if (DefaultFont == null || FontData == null) return; 53 | 54 | foreach (var customFont in TextFontManager.CustomFonts) 55 | { 56 | if (customFont.Key == DefaultFont.name || 57 | (customFont.Key == "Default" && !TextFontManager.CustomFonts.ContainsKey(DefaultFont.name))) 58 | { 59 | if (customFont.Value.IsUsingCustomFont) 60 | { 61 | if (FontData.font != customFont.Value.CustomFont) 62 | { 63 | FontData.font = customFont.Value.CustomFont; 64 | } 65 | } 66 | else 67 | { 68 | if (FontData.font != DefaultFont) 69 | { 70 | FontData.font = DefaultFont; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | /// 78 | /// Apply custom fond immediately skipping TextFontManager 79 | /// 80 | /// 81 | public void UseCustomFontImmediate(Font fontToUse) 82 | { 83 | Reference.font = fontToUse; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /TranslationCommon/Fonts/TextFontManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using TranslationCommon; 5 | using TranslationCommon.Translation; 6 | using UnityEngine; 7 | using UnityEngine.UI; 8 | 9 | namespace DSPTranslationPlugin.UnityHarmony 10 | { 11 | /// 12 | /// Font manager for all text components 13 | /// 14 | public static class TextFontManager 15 | { 16 | /// 17 | /// Original Font name and Custom font pair 18 | /// 19 | public static readonly Dictionary CustomFonts = new Dictionary(); 20 | 21 | /// 22 | /// All Text object references with it's corresponding font data 23 | /// 24 | private static readonly Dictionary _textReferences = new Dictionary(); 25 | 26 | /// 27 | /// Player prefs key for all saved fonts keys 28 | /// 29 | public const string PlayerPrefsSavedFontsCode = "dps_translate_muchaszewski_fonts"; 30 | /// 31 | /// Player prefs key for font used under given name 32 | /// 33 | public const string PlayerPrefsFontCode = "dps_translate_muchaszewski_font_"; 34 | 35 | /// 36 | /// List of all installed fonts 37 | /// 38 | public static string[] InstalledFonts { get; private set; } 39 | 40 | /// 41 | /// Static constructor 42 | /// 43 | static TextFontManager() 44 | { 45 | InstalledFonts = Font.GetOSInstalledFontNames(); 46 | } 47 | 48 | public static void CheckFonts() 49 | { 50 | List fonts = Font.GetOSInstalledFontNames().ToList(); 51 | if (TranslationManager.CurrentLanguage != null && TranslationManager.CurrentLanguage.Fonts != null) 52 | { 53 | foreach (Font font in TranslationManager.CurrentLanguage.Fonts) 54 | { 55 | fonts.Add(font.name); 56 | } 57 | } 58 | 59 | InstalledFonts = fonts.ToArray(); 60 | } 61 | 62 | /// 63 | /// Loads text font settings 64 | /// 65 | internal static void LoadSettings() 66 | { 67 | var savedFonts = PlayerPrefs.GetString(PlayerPrefsSavedFontsCode); 68 | var split = savedFonts.Split('|').Where(s => !String.IsNullOrEmpty(s)).ToList(); 69 | foreach (var savedFontType in split) 70 | { 71 | var fontName = PlayerPrefs.GetString(PlayerPrefsFontCode + savedFontType); 72 | 73 | if (fontName == savedFontType) 74 | { 75 | RestoreDefaultFont(savedFontType); 76 | } 77 | else 78 | { 79 | Font font; 80 | try 81 | { 82 | if (TranslationManager.CurrentLanguage == null || TranslationManager.CurrentLanguage.Fonts == null) 83 | throw new InvalidOperationException(); 84 | 85 | font = TranslationManager.CurrentLanguage.Fonts.First(font1 => font1.name == fontName); 86 | } 87 | catch (InvalidOperationException) 88 | { 89 | font = Font.CreateDynamicFontFromOSFont(fontName, 12); 90 | } 91 | 92 | ApplyCustomFont(savedFontType, font); 93 | } 94 | } 95 | } 96 | 97 | /// 98 | /// Get all currently existing text elements 99 | /// 100 | /// 101 | public static IEnumerable GetExistingTextsElements() 102 | { 103 | return _textReferences.Values; 104 | } 105 | 106 | /// 107 | /// Get text data by it's reference 108 | /// 109 | /// Text reference 110 | /// 111 | public static TextDefaultFont Get(Text text) 112 | { 113 | _textReferences.TryGetValue(text, out var value); 114 | return value; 115 | } 116 | 117 | /// 118 | /// Add text data by it's reference 119 | /// 120 | /// Text reference 121 | public static void Add(Text text) 122 | { 123 | var find = Get(text); 124 | if (find == null) 125 | { 126 | _textReferences.Add(text, new TextDefaultFont(text)); 127 | } 128 | } 129 | 130 | /// 131 | /// Remove text data by it's reference 132 | /// 133 | /// Text reference 134 | public static void Remove(Text text) 135 | { 136 | var find = Get(text); 137 | if (find != null) 138 | { 139 | _textReferences.Remove(text); 140 | } 141 | } 142 | 143 | /// 144 | /// Get setting index for menu settings 145 | /// 146 | /// Font key 147 | /// 148 | public static int GetSettingIndex(string key) 149 | { 150 | if (CustomFonts.ContainsKey(key)) 151 | { 152 | if (!CustomFonts[key].IsUsingCustomFont) return 0; 153 | var fontName = CustomFonts[key].CustomFont.name; 154 | for (int i = 0; i < InstalledFonts.Length; i++) 155 | { 156 | if(InstalledFonts[i] != fontName) continue; 157 | return i + 1; 158 | } 159 | } 160 | 161 | return 0; 162 | } 163 | 164 | /// 165 | /// Saves settings into player prefs 166 | /// 167 | /// Font key 168 | /// 169 | public static void SaveSettings(string key, string fontName) 170 | { 171 | var savedFonts = PlayerPrefs.GetString(PlayerPrefsSavedFontsCode); 172 | var split = savedFonts.Split('|').Where(s => !String.IsNullOrEmpty(s)).ToList(); 173 | if (!split.Contains(key)) 174 | { 175 | split.Add(key); 176 | } 177 | PlayerPrefs.SetString(PlayerPrefsFontCode + key, fontName); 178 | PlayerPrefs.SetString(PlayerPrefsSavedFontsCode, String.Join("|", split.ToArray())); 179 | } 180 | 181 | /// 182 | /// Apply provided font to font key group 183 | /// 184 | /// Font key 185 | /// Font to apply 186 | public static void ApplyCustomFont(string key, Font fontToUse) 187 | { 188 | CustomFonts[key] = new CustomFontData() 189 | { 190 | CustomFont = fontToUse, 191 | IsUsingCustomFont = true, 192 | }; 193 | SaveSettings(key, fontToUse.name); 194 | foreach (var text in _textReferences) 195 | { 196 | text.Value.UseCustomFontImmediate(fontToUse); 197 | } 198 | } 199 | 200 | /// 201 | /// Restore default font 202 | /// 203 | /// Font key 204 | public static void RestoreDefaultFont(string key) 205 | { 206 | CustomFonts[key] = new CustomFontData() 207 | { 208 | IsUsingCustomFont = false, 209 | }; 210 | SaveSettings(key, key); 211 | foreach (var text in _textReferences) 212 | { 213 | text.Value.UseCustomFontImmediate(text.Value.DefaultFont); 214 | } 215 | } 216 | } 217 | } -------------------------------------------------------------------------------- /TranslationCommon/GameObjectsUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using TranslationCommon.Fonts; 4 | using UnityEngine; 5 | 6 | namespace TranslationCommon 7 | { 8 | public static class GameObjectsUtils 9 | { 10 | public static IEnumerable GetChildrenWithSelf(this Component component, RangeValue childLayers) 11 | { 12 | return GetChildrenWithSelf(component.transform, childLayers); 13 | } 14 | 15 | public static IEnumerable GetChildrenWithSelf(this GameObject gameObject, RangeValue childLayers) 16 | { 17 | return GetChildrenWithSelf(gameObject.transform, childLayers); 18 | } 19 | 20 | public static IEnumerable GetChildrenWithSelf(this Transform transform, RangeValue childLayers) 21 | { 22 | return GetChildrenWithSelf_Impl(transform, childLayers, 0); 23 | } 24 | 25 | private static IEnumerable GetChildrenWithSelf_Impl(this Transform transform, 26 | RangeValue childLayers, int index) 27 | { 28 | if (childLayers == null) 29 | { 30 | yield return transform.gameObject; 31 | yield break; 32 | } 33 | 34 | if (!childLayers.TryGetMatch(index, out var match)) 35 | { 36 | yield break; 37 | } 38 | 39 | if (index == match) 40 | { 41 | yield return transform.gameObject; 42 | } 43 | 44 | for (var i = 0; i < transform.childCount; i++) 45 | { 46 | var child = transform.GetChild(i); 47 | foreach (var gameObject in GetChildrenWithSelf_Impl(child, childLayers, index + 1)) 48 | { 49 | yield return gameObject; 50 | } 51 | } 52 | } 53 | 54 | public static IEnumerable GetComponentsInChildrenWithSelf(this Component component, RangeValue childLayers) 55 | where T : Component 56 | { 57 | return GetComponentsInChildrenWithSelf(component.transform, childLayers); 58 | } 59 | 60 | public static IEnumerable GetComponentsInChildrenWithSelf(this GameObject gameObject, RangeValue childLayers) 61 | where T : Component 62 | { 63 | return GetComponentsInChildrenWithSelf(gameObject.transform, childLayers); 64 | } 65 | 66 | public static IEnumerable GetComponentsInChildrenWithSelf(this Transform transform, RangeValue childLayers) 67 | where T : Component 68 | { 69 | return GetChildrenWithSelf(transform, childLayers).SelectMany(gameObject => gameObject.GetComponents()); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /TranslationCommon/SimpleJSON/Attributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TranslationCommon.SimpleJSON 4 | { 5 | public class SerializeFirstAsObjectAttribute : Attribute 6 | { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /TranslationCommon/SimpleJSON/Changelog.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * [2012-06-09 First Version] 3 | * - provides strongly typed node classes and lists / dictionaries 4 | * - provides easy access to class members / array items / data values 5 | * - the parser now properly identifies types. So generating JSON with this framework should work. 6 | * - only double quotes (") are used for quoting strings. 7 | * - provides "casting" properties to easily convert to / from those types: 8 | * int / float / double / bool 9 | * - provides a common interface for each node so no explicit casting is required. 10 | * - the parser tries to avoid errors, but if malformed JSON is parsed the result is more or less undefined 11 | * - It can serialize/deserialize a node tree into/from an experimental compact binary format. It might 12 | * be handy if you want to store things in a file and don't want it to be easily modifiable 13 | * 14 | * [2012-12-17 Update] 15 | * - Added internal JSONLazyCreator class which simplifies the construction of a JSON tree 16 | * Now you can simple reference any item that doesn't exist yet and it will return a JSONLazyCreator 17 | * The class determines the required type by it's further use, creates the type and removes itself. 18 | * - Added binary serialization / deserialization. 19 | * - Added support for BZip2 zipped binary format. Requires the SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ ) 20 | * The usage of the SharpZipLib library can be disabled by removing or commenting out the USE_SharpZipLib define at the top 21 | * - The serializer uses different types when it comes to store the values. Since my data values 22 | * are all of type string, the serializer will "try" which format fits best. The order is: int, float, double, bool, string. 23 | * It's not the most efficient way but for a moderate amount of data it should work on all platforms. 24 | * 25 | * [2017-03-08 Update] 26 | * - Optimised parsing by using a StringBuilder for token. This prevents performance issues when large 27 | * string data fields are contained in the json data. 28 | * - Finally refactored the badly named JSONClass into JSONObject. 29 | * - Replaced the old JSONData class by distict typed classes ( JSONString, JSONNumber, JSONBool, JSONNull ) this 30 | * allows to propertly convert the node tree back to json without type information loss. The actual value 31 | * parsing now happens at parsing time and not when you actually access one of the casting properties. 32 | * 33 | * [2017-04-11 Update] 34 | * - Fixed parsing bug where empty string values have been ignored. 35 | * - Optimised "ToString" by using a StringBuilder internally. This should heavily improve performance for large files 36 | * - Changed the overload of "ToString(string aIndent)" to "ToString(int aIndent)" 37 | * 38 | * [2017-11-29 Update] 39 | * - Removed the IEnumerator implementations on JSONArray & JSONObject and replaced it with a common 40 | * struct Enumerator in JSONNode that should avoid garbage generation. The enumerator always works 41 | * on KeyValuePair, even for JSONArray. 42 | * - Added two wrapper Enumerators that allows for easy key or value enumeration. A JSONNode now has 43 | * a "Keys" and a "Values" enumerable property. Those are also struct enumerators / enumerables 44 | * - A KeyValuePair can now be implicitly converted into a JSONNode. This allows 45 | * a foreach loop over a JSONNode to directly access the values only. Since KeyValuePair as well as 46 | * all the Enumerators are structs, no garbage is allocated. 47 | * - To add Linq support another "LinqEnumerator" is available through the "Linq" property. This 48 | * enumerator does implement the generic IEnumerable interface so most Linq extensions can be used 49 | * on this enumerable object. This one does allocate memory as it's a wrapper class. 50 | * - The Escape method now escapes all control characters (# < 32) in strings as uncode characters 51 | * (\uXXXX) and if the static bool JSONNode.forceASCII is set to true it will also escape all 52 | * characters # > 127. This might be useful if you require an ASCII output. Though keep in mind 53 | * when your strings contain many non-ascii characters the strings become much longer (x6) and are 54 | * no longer human readable. 55 | * - The node types JSONObject and JSONArray now have an "Inline" boolean switch which will default to 56 | * false. It can be used to serialize this element inline even you serialize with an indented format 57 | * This is useful for arrays containing numbers so it doesn't place every number on a new line 58 | * - Extracted the binary serialization code into a seperate extension file. All classes are now declared 59 | * as "partial" so an extension file can even add a new virtual or abstract method / interface to 60 | * JSONNode and override it in the concrete type classes. It's of course a hacky approach which is 61 | * generally not recommended, but i wanted to keep everything tightly packed. 62 | * - Added a static CreateOrGet method to the JSONNull class. Since this class is immutable it could 63 | * be reused without major problems. If you have a lot null fields in your data it will help reduce 64 | * the memory / garbage overhead. I also added a static setting (reuseSameInstance) to JSONNull 65 | * (default is true) which will change the behaviour of "CreateOrGet". If you set this to false 66 | * CreateOrGet will not reuse the cached instance but instead create a new JSONNull instance each time. 67 | * I made the JSONNull constructor private so if you need to create an instance manually use 68 | * JSONNull.CreateOrGet() 69 | * 70 | * [2018-01-09 Update] 71 | * - Changed all double.TryParse and double.ToString uses to use the invariant culture to avoid problems 72 | * on systems with a culture that uses a comma as decimal point. 73 | * 74 | * [2018-01-26 Update] 75 | * - Added AsLong. Note that a JSONNumber is stored as double and can't represent all long values. However 76 | * storing it as string would work. 77 | * - Added static setting "JSONNode.longAsString" which controls the default type that is used by the 78 | * LazyCreator when using AsLong 79 | * 80 | * [2018-04-25 Update] 81 | * - Added support for parsing single values (JSONBool, JSONString, JSONNumber, JSONNull) as top level value. 82 | * 83 | * [2019-02-18 Update] 84 | * - Added HasKey(key) and GetValueOrDefault(key, default) to the JSONNode class to provide way to read 85 | * values conditionally without creating a LazyCreator 86 | * 87 | * [2019-03-25 Update] 88 | * - Added static setting "allowLineComments" to the JSONNode class which is true by default. This allows 89 | * "//" line comments when parsing json text as long as it's not within quoted text. All text after // up 90 | * to the end of the line is completely ignored / skipped. This makes it easier to create human readable 91 | * and editable files. Note that stripped comments are not read, processed or preserved in any way. So 92 | * this feature is only relevant for human created files. 93 | * - Explicitly strip BOM (Byte Order Mark) when parsing to avoid getting it leaked into a single primitive 94 | * value. That's a rare case but better safe than sorry. 95 | * - Allowing adding the empty string as key 96 | * 97 | * [2019-12-10 Update] 98 | * - Added Clone() method to JSONNode to allow cloning of a whole node tree. 99 | * 100 | * [2020-09-19 Update] 101 | * - Added Clear() method to JSONNode. 102 | * - The parser will now automatically mark arrays or objects as inline when it doesn't contain any 103 | * new line characters. This should more or less preserve the layout. 104 | * - Added new extension file "SimpleJSONDotNetTypes.cs" to provide support for some basic .NET types 105 | * like decimal, char, byte, sbyte, short, ushort, uint, DateTime, TimeSpan and Guid as well as some 106 | * nullable types. 107 | * - Fixed an error in the Unity extension file. The Color component order was wrong (it was argb, now it's rgba) 108 | * - There are now two static float variables (ColorDefaultAlpha and Color32DefaultAlpha) to specify the default 109 | * alpha values when reading UnityEngine.Color / Color32 values where the alpha value is absent. The default 110 | * values are 1.0f and 255 respectively. 111 | */ 112 | -------------------------------------------------------------------------------- /TranslationCommon/SimpleJSON/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2017 Markus Göbel (Bunny83) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TranslationCommon/SimpleJSON/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muchaszewski/DSP_TranslationMod/bc74e3ae3500ce3c4fb86b7191a9cc5b9e56ee2f/TranslationCommon/SimpleJSON/README -------------------------------------------------------------------------------- /TranslationCommon/SimpleJSON/ReflectionUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace TranslationCommon.SimpleJSON 6 | { 7 | public static class ReflectionUtils 8 | { 9 | public static bool ContainsAttribute(this MemberInfo memberInfo) 10 | where T : Attribute 11 | { 12 | var customAttributes = memberInfo.GetCustomAttributes(true); 13 | return customAttributes.FirstOrDefault(o => o.GetType().IsAssignableFrom(typeof(T))) != null; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /TranslationCommon/SimpleJSON/SimpleJSONBinary.cs: -------------------------------------------------------------------------------- 1 | //#define USE_SharpZipLib 2 | /* * * * * 3 | * This is an extension of the SimpleJSON framework to provide methods to 4 | * serialize a JSON object tree into a compact binary format. Optionally the 5 | * binary stream can be compressed with the SharpZipLib when using the define 6 | * "USE_SharpZipLib" 7 | * 8 | * Those methods where originally part of the framework but since it's rarely 9 | * used I've extracted this part into this seperate module file. 10 | * 11 | * You can use the define "SimpleJSON_ExcludeBinary" to selectively disable 12 | * this extension without the need to remove the file from the project. 13 | * 14 | * If you want to use compression when saving to file / stream / B64 you have to include 15 | * SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ ) in your project and 16 | * define "USE_SharpZipLib" at the top of the file 17 | * 18 | * 19 | * The MIT License (MIT) 20 | * 21 | * Copyright (c) 2012-2017 Markus Göbel (Bunny83) 22 | * 23 | * Permission is hereby granted, free of charge, to any person obtaining a copy 24 | * of this software and associated documentation files (the "Software"), to deal 25 | * in the Software without restriction, including without limitation the rights 26 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27 | * copies of the Software, and to permit persons to whom the Software is 28 | * furnished to do so, subject to the following conditions: 29 | * 30 | * The above copyright notice and this permission notice shall be included in all 31 | * copies or substantial portions of the Software. 32 | * 33 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 39 | * SOFTWARE. 40 | * 41 | * * * * */ 42 | 43 | using System; 44 | 45 | namespace TranslationCommon.SimpleJSON 46 | { 47 | #if !SimpleJSON_ExcludeBinary 48 | public abstract partial class JSONNode 49 | { 50 | public abstract void SerializeBinary(System.IO.BinaryWriter aWriter); 51 | 52 | public void SaveToBinaryStream(System.IO.Stream aData) 53 | { 54 | var W = new System.IO.BinaryWriter(aData); 55 | SerializeBinary(W); 56 | } 57 | 58 | #if USE_SharpZipLib 59 | public void SaveToCompressedStream(System.IO.Stream aData) 60 | { 61 | using (var gzipOut = new ICSharpCode.SharpZipLib.BZip2.BZip2OutputStream(aData)) 62 | { 63 | gzipOut.IsStreamOwner = false; 64 | SaveToBinaryStream(gzipOut); 65 | gzipOut.Close(); 66 | } 67 | } 68 | 69 | public void SaveToCompressedFile(string aFileName) 70 | { 71 | 72 | System.IO.Directory.CreateDirectory((new System.IO.FileInfo(aFileName)).Directory.FullName); 73 | using(var F = System.IO.File.OpenWrite(aFileName)) 74 | { 75 | SaveToCompressedStream(F); 76 | } 77 | } 78 | public string SaveToCompressedBase64() 79 | { 80 | using (var stream = new System.IO.MemoryStream()) 81 | { 82 | SaveToCompressedStream(stream); 83 | stream.Position = 0; 84 | return System.Convert.ToBase64String(stream.ToArray()); 85 | } 86 | } 87 | 88 | #else 89 | public void SaveToCompressedStream(System.IO.Stream aData) 90 | { 91 | throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 92 | } 93 | 94 | public void SaveToCompressedFile(string aFileName) 95 | { 96 | throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 97 | } 98 | 99 | public string SaveToCompressedBase64() 100 | { 101 | throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 102 | } 103 | #endif 104 | 105 | public void SaveToBinaryFile(string aFileName) 106 | { 107 | System.IO.Directory.CreateDirectory((new System.IO.FileInfo(aFileName)).Directory.FullName); 108 | using (var F = System.IO.File.OpenWrite(aFileName)) 109 | { 110 | SaveToBinaryStream(F); 111 | } 112 | } 113 | 114 | public string SaveToBinaryBase64() 115 | { 116 | using (var stream = new System.IO.MemoryStream()) 117 | { 118 | SaveToBinaryStream(stream); 119 | stream.Position = 0; 120 | return System.Convert.ToBase64String(stream.ToArray()); 121 | } 122 | } 123 | 124 | public static JSONNode DeserializeBinary(System.IO.BinaryReader aReader) 125 | { 126 | JSONNodeType type = (JSONNodeType)aReader.ReadByte(); 127 | switch (type) 128 | { 129 | case JSONNodeType.Array: 130 | { 131 | int count = aReader.ReadInt32(); 132 | JSONArray tmp = new JSONArray(); 133 | for (int i = 0; i < count; i++) 134 | tmp.Add(DeserializeBinary(aReader)); 135 | return tmp; 136 | } 137 | case JSONNodeType.Object: 138 | { 139 | int count = aReader.ReadInt32(); 140 | JSONObject tmp = new JSONObject(); 141 | for (int i = 0; i < count; i++) 142 | { 143 | string key = aReader.ReadString(); 144 | var val = DeserializeBinary(aReader); 145 | tmp.Add(key, val); 146 | } 147 | return tmp; 148 | } 149 | case JSONNodeType.String: 150 | { 151 | return new JSONString(aReader.ReadString()); 152 | } 153 | case JSONNodeType.Number: 154 | { 155 | return new JSONNumber(aReader.ReadDouble()); 156 | } 157 | case JSONNodeType.Boolean: 158 | { 159 | return new JSONBool(aReader.ReadBoolean()); 160 | } 161 | case JSONNodeType.NullValue: 162 | { 163 | return JSONNull.CreateOrGet(); 164 | } 165 | default: 166 | { 167 | throw new Exception("Error deserializing JSON. Unknown tag: " + type); 168 | } 169 | } 170 | } 171 | 172 | #if USE_SharpZipLib 173 | public static JSONNode LoadFromCompressedStream(System.IO.Stream aData) 174 | { 175 | var zin = new ICSharpCode.SharpZipLib.BZip2.BZip2InputStream(aData); 176 | return LoadFromBinaryStream(zin); 177 | } 178 | public static JSONNode LoadFromCompressedFile(string aFileName) 179 | { 180 | using(var F = System.IO.File.OpenRead(aFileName)) 181 | { 182 | return LoadFromCompressedStream(F); 183 | } 184 | } 185 | public static JSONNode LoadFromCompressedBase64(string aBase64) 186 | { 187 | var tmp = System.Convert.FromBase64String(aBase64); 188 | var stream = new System.IO.MemoryStream(tmp); 189 | stream.Position = 0; 190 | return LoadFromCompressedStream(stream); 191 | } 192 | #else 193 | public static JSONNode LoadFromCompressedFile(string aFileName) 194 | { 195 | throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 196 | } 197 | 198 | public static JSONNode LoadFromCompressedStream(System.IO.Stream aData) 199 | { 200 | throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 201 | } 202 | 203 | public static JSONNode LoadFromCompressedBase64(string aBase64) 204 | { 205 | throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); 206 | } 207 | #endif 208 | 209 | public static JSONNode LoadFromBinaryStream(System.IO.Stream aData) 210 | { 211 | using (var R = new System.IO.BinaryReader(aData)) 212 | { 213 | return DeserializeBinary(R); 214 | } 215 | } 216 | 217 | public static JSONNode LoadFromBinaryFile(string aFileName) 218 | { 219 | using (var F = System.IO.File.OpenRead(aFileName)) 220 | { 221 | return LoadFromBinaryStream(F); 222 | } 223 | } 224 | 225 | public static JSONNode LoadFromBinaryBase64(string aBase64) 226 | { 227 | var tmp = System.Convert.FromBase64String(aBase64); 228 | var stream = new System.IO.MemoryStream(tmp); 229 | stream.Position = 0; 230 | return LoadFromBinaryStream(stream); 231 | } 232 | } 233 | 234 | public partial class JSONArray : JSONNode 235 | { 236 | public override void SerializeBinary(System.IO.BinaryWriter aWriter) 237 | { 238 | aWriter.Write((byte)JSONNodeType.Array); 239 | aWriter.Write(m_List.Count); 240 | for (int i = 0; i < m_List.Count; i++) 241 | { 242 | m_List[i].SerializeBinary(aWriter); 243 | } 244 | } 245 | } 246 | 247 | public partial class JSONObject : JSONNode 248 | { 249 | public override void SerializeBinary(System.IO.BinaryWriter aWriter) 250 | { 251 | aWriter.Write((byte)JSONNodeType.Object); 252 | aWriter.Write(m_Dict.Count); 253 | foreach (string K in m_Dict.Keys) 254 | { 255 | aWriter.Write(K); 256 | m_Dict[K].SerializeBinary(aWriter); 257 | } 258 | } 259 | } 260 | 261 | public partial class JSONString : JSONNode 262 | { 263 | public override void SerializeBinary(System.IO.BinaryWriter aWriter) 264 | { 265 | aWriter.Write((byte)JSONNodeType.String); 266 | aWriter.Write(m_Data); 267 | } 268 | } 269 | 270 | public partial class JSONNumber : JSONNode 271 | { 272 | public override void SerializeBinary(System.IO.BinaryWriter aWriter) 273 | { 274 | aWriter.Write((byte)JSONNodeType.Number); 275 | aWriter.Write(m_Data); 276 | } 277 | } 278 | 279 | public partial class JSONBool : JSONNode 280 | { 281 | public override void SerializeBinary(System.IO.BinaryWriter aWriter) 282 | { 283 | aWriter.Write((byte)JSONNodeType.Boolean); 284 | aWriter.Write(m_Data); 285 | } 286 | } 287 | public partial class JSONNull : JSONNode 288 | { 289 | public override void SerializeBinary(System.IO.BinaryWriter aWriter) 290 | { 291 | aWriter.Write((byte)JSONNodeType.NullValue); 292 | } 293 | } 294 | internal partial class JSONLazyCreator : JSONNode 295 | { 296 | public override void SerializeBinary(System.IO.BinaryWriter aWriter) 297 | { 298 | 299 | } 300 | } 301 | #endif 302 | } 303 | -------------------------------------------------------------------------------- /TranslationCommon/SimpleJSON/SimpleJSONBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using UnityEngine; 9 | 10 | namespace TranslationCommon.SimpleJSON 11 | { 12 | public class SimpleJSONBuilder 13 | { 14 | public T Deserialize(string serializationStream) 15 | where T : new() 16 | { 17 | var jsonNode = JSON.Parse(serializationStream); 18 | var settings = new SimpleJSONParserSettings(); 19 | return (T) SimpleJSONStaticParser.FromJsonNode(jsonNode, typeof(T), settings); 20 | } 21 | 22 | public string Serialize(object graph, bool isIndented) 23 | { 24 | var settings = new SimpleJSONParserSettings 25 | { 26 | IsIndented = isIndented 27 | }; 28 | var jsonNode = SimpleJSONStaticParser.ToJsonNode(graph, settings); 29 | 30 | var sb = new StringBuilder(); 31 | jsonNode.WriteToStringBuilder(sb, 0, settings.IsIndented ? 2 : 0, 32 | settings.IsIndented ? JSONTextMode.Indent : JSONTextMode.Compact); 33 | return sb.ToString(); 34 | } 35 | } 36 | 37 | public struct SimpleJSONParserSettings 38 | { 39 | public static SimpleJSONParserSettings Default() 40 | { 41 | return new SimpleJSONParserSettings 42 | { 43 | IsIndented = true 44 | }; 45 | } 46 | 47 | public bool IsIndented; 48 | } 49 | 50 | public static class SimpleJSONStaticParser 51 | { 52 | public static Type[] AppDomainTypes; 53 | 54 | public static object FromJsonNode(JSONNode value, Type type, SimpleJSONParserSettings settings) 55 | { 56 | if (value == null) 57 | { 58 | return null; 59 | } 60 | 61 | if (type == typeof(string)) 62 | { 63 | return (string) value; 64 | } 65 | 66 | if (type == typeof(char)) 67 | { 68 | return (char) value; 69 | } 70 | 71 | if (type == typeof(bool)) 72 | { 73 | return (bool) value; 74 | } 75 | 76 | if (typeof(IList).IsAssignableFrom(type)) 77 | { 78 | var listType = typeof(List<>); 79 | var elementType = type.GetGenericArguments(); 80 | var constructedListType = listType.MakeGenericType(elementType); 81 | var list = (IList) Activator.CreateInstance(constructedListType); 82 | foreach (var array in value.AsArray.Children) 83 | { 84 | list.Add(FromJsonNode(array, elementType[0], settings)); 85 | } 86 | 87 | return list; 88 | } 89 | 90 | if (typeof(IDictionary).IsAssignableFrom(type)) 91 | { 92 | var dictType = typeof(Dictionary<,>); 93 | var elementType = type.GetGenericArguments(); 94 | var constructedListType = dictType.MakeGenericType(elementType); 95 | var dictionary = (IDictionary) Activator.CreateInstance(constructedListType); 96 | foreach (var dict in value) 97 | { 98 | dictionary.Add( 99 | FromJsonNode(dict.Key, elementType[0], settings), 100 | FromJsonNode(dict.Value, elementType[1], settings) 101 | ); 102 | } 103 | 104 | return dictionary; 105 | } 106 | 107 | if (type == typeof(Vector2)) 108 | { 109 | return (Vector2) value; 110 | } 111 | 112 | if (type == typeof(Vector3)) 113 | { 114 | return (Vector3) value; 115 | } 116 | 117 | if (type == typeof(Vector4)) 118 | { 119 | return (Vector4) value; 120 | } 121 | 122 | if (type == typeof(Quaternion)) 123 | { 124 | return (Quaternion) value; 125 | } 126 | 127 | if (type == typeof(Rect)) 128 | { 129 | return (Rect) value; 130 | } 131 | 132 | if (type == typeof(RectOffset)) 133 | { 134 | return (RectOffset) value; 135 | } 136 | 137 | if (type == typeof(Matrix4x4)) 138 | { 139 | return (Matrix4x4) value; 140 | } 141 | 142 | if (type == typeof(Color)) 143 | { 144 | return (Color) value; 145 | } 146 | 147 | if (type == typeof(Color32)) 148 | { 149 | return (Color32) value; 150 | } 151 | 152 | if (type == typeof(byte)) 153 | { 154 | return (byte) value; 155 | } 156 | 157 | if (type == typeof(sbyte)) 158 | { 159 | return (sbyte) value; 160 | } 161 | 162 | if (type == typeof(int)) 163 | { 164 | return (int) value; 165 | } 166 | 167 | if (type == typeof(uint)) 168 | { 169 | return (uint) value; 170 | } 171 | 172 | if (type == typeof(short)) 173 | { 174 | return (short) value; 175 | } 176 | 177 | if (type == typeof(ushort)) 178 | { 179 | return (ushort) value; 180 | } 181 | 182 | if (type == typeof(char)) 183 | { 184 | return (char) value; 185 | } 186 | 187 | if (type == typeof(float)) 188 | { 189 | return (float) value; 190 | } 191 | 192 | if (type == typeof(double)) 193 | { 194 | return (double) value; 195 | } 196 | 197 | if (type == typeof(decimal)) 198 | { 199 | return (decimal) value; 200 | } 201 | 202 | if (type == typeof(long)) 203 | { 204 | return (long) value; 205 | } 206 | 207 | if (type == typeof(ulong)) 208 | { 209 | return (ulong) value; 210 | } 211 | 212 | var obj = CreateMatchingType(value, type, out var resultType); 213 | var fields = resultType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 214 | foreach (var fieldInfo in fields) 215 | { 216 | if (fieldInfo.IsPublic || fieldInfo.ContainsAttribute()) 217 | { 218 | if (fieldInfo.ContainsAttribute()) 219 | { 220 | fieldInfo.SetValue(obj, FromJsonNode(value, fieldInfo.FieldType, settings)); 221 | return obj; 222 | } 223 | 224 | fieldInfo.SetValue(obj, FromJsonNode(value[fieldInfo.Name], fieldInfo.FieldType, settings)); 225 | } 226 | } 227 | 228 | return obj; 229 | } 230 | 231 | private static object CreateMatchingType(JSONNode value, Type type, out Type resultType) 232 | { 233 | if (type.IsInterface) 234 | { 235 | bool ContainsAllItems(IEnumerable a, IEnumerable b) 236 | { 237 | return !b.Except(a).Any(); 238 | } 239 | 240 | if (AppDomainTypes == null) 241 | { 242 | var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 243 | var matchingTypes = new List(); 244 | foreach (var assembly in assemblies) 245 | { 246 | try 247 | { 248 | matchingTypes.AddRange(assembly.GetTypes() 249 | .Where(type.IsAssignableFrom) 250 | .Where(t => !t.IsInterface && !t.IsAbstract)); 251 | } 252 | catch //(ReflectionTypeLoadException e) 253 | { 254 | /*ConsoleLogger.LogDebug(assembly.FullName + " " + e.Message + "\n" + e.StackTrace + "\n" + 255 | e.LoaderExceptions.Select(exception => exception.Message).Aggregate((x,y) => x + " " + y));*/ 256 | } 257 | } 258 | AppDomainTypes = matchingTypes.ToArray(); 259 | } 260 | 261 | foreach (var matchingType in AppDomainTypes) 262 | { 263 | var fields = 264 | matchingType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 265 | if (ContainsAllItems(fields.Select(info => info.Name), value.GetEnumerator().ToKeyEnumerable())) 266 | { 267 | resultType = matchingType; 268 | return Activator.CreateInstance(matchingType); 269 | } 270 | } 271 | 272 | throw new KeyNotFoundException( 273 | $"No matching member was not found with signature {value.GetEnumerator().ToKeyEnumerable().Aggregate((x, y) => x + " " + y)}"); 274 | } 275 | 276 | resultType = type; 277 | return Activator.CreateInstance(type); 278 | } 279 | 280 | public static JSONNode ToJsonNode(object value, SimpleJSONParserSettings settings) 281 | { 282 | switch (value) 283 | { 284 | case null: 285 | return JSONNull.CreateOrGet(); 286 | case JSONNode jsonValue: 287 | return jsonValue; 288 | case string strValue: 289 | return new JSONString(strValue); 290 | case char charValue: 291 | return new JSONString(new string(charValue, 1)); 292 | case bool boolValue: 293 | return new JSONBool(boolValue); 294 | case IList listValue: 295 | return ToJsonNode(listValue, settings); 296 | case IDictionary dictValue: 297 | return ToJsonNode(dictValue, settings); 298 | 299 | case Vector2 v2Value: 300 | return v2Value; 301 | case Vector3 v3Value: 302 | return v3Value; 303 | case Vector4 v4Value: 304 | return v4Value; 305 | case Quaternion quatValue: 306 | return quatValue; 307 | case Rect rectValue: 308 | return rectValue; 309 | case RectOffset rectOffsetValue: 310 | return rectOffsetValue; 311 | case Matrix4x4 matrixValue: 312 | return matrixValue; 313 | case Color colorValue: 314 | return colorValue; 315 | case Color32 color32Value: 316 | return color32Value; 317 | 318 | case float floatValue: 319 | return new JSONNumber(floatValue.ToString("R", CultureInfo.InvariantCulture)); 320 | default: 321 | if (JSONNumber.IsNumeric(value)) 322 | { 323 | return new JSONNumber(Convert.ToDouble(value)); 324 | } 325 | else 326 | { 327 | var jsonObject = new JSONObject(); 328 | var fields = value.GetType() 329 | .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 330 | foreach (var fieldInfo in fields) 331 | { 332 | if (fieldInfo.IsPublic || fieldInfo.ContainsAttribute()) 333 | { 334 | var fieldValue = fieldInfo.GetValue(value); 335 | if (fieldValue == default) 336 | { 337 | continue; 338 | } 339 | 340 | var jsonNode = ToJsonNode(fieldValue, settings); 341 | if (fieldInfo.ContainsAttribute()) 342 | { 343 | return jsonNode; 344 | } 345 | 346 | jsonObject.Add(fieldInfo.Name, jsonNode); 347 | } 348 | } 349 | 350 | return jsonObject; 351 | } 352 | } 353 | } 354 | 355 | private static JSONArray ToJsonNode(IList list, SimpleJSONParserSettings settings) 356 | { 357 | var jsonArray = new JSONArray(); 358 | 359 | for (var i = 0; i < list.Count; i++) 360 | { 361 | jsonArray.Add(ToJsonNode(list[i], settings)); 362 | } 363 | 364 | return jsonArray; 365 | } 366 | 367 | private static JSONObject ToJsonNode(IDictionary dict, SimpleJSONParserSettings settings) 368 | { 369 | var jsonObject = new JSONObject(); 370 | 371 | foreach (var key in dict.Keys) 372 | { 373 | jsonObject.Add(key.ToString(), ToJsonNode(dict[key], settings)); 374 | } 375 | 376 | return jsonObject; 377 | } 378 | } 379 | } -------------------------------------------------------------------------------- /TranslationCommon/SimpleJSON/SimpleJSONUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TranslationCommon.SimpleJSON 4 | { 5 | public static class SimpleJSONUtils 6 | { 7 | public static IEnumerable ToEnumerable(this IEnumerator enumerator) 8 | { 9 | while ( enumerator.MoveNext() ) { 10 | yield return enumerator.Current; 11 | } 12 | } 13 | 14 | public static IEnumerable ToKeyEnumerable(this JSONNode.Enumerator enumerator) 15 | { 16 | while ( enumerator.MoveNext() ) { 17 | yield return enumerator.Current.Key; 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /TranslationCommon/Translation/LanguageContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using TranslationCommon.SimpleJSON; 8 | using UnityEngine; 9 | using Debug = UnityEngine.Debug; 10 | 11 | namespace TranslationCommon.Translation 12 | { 13 | /// 14 | /// Container for single language methods 15 | /// 16 | public class LanguageContainer 17 | { 18 | /// 19 | /// Settings of the language 20 | /// 21 | public LanguageSettings Settings; 22 | 23 | /// 24 | /// Translation data 25 | /// 26 | public LanguageData Translation; 27 | 28 | public Font[] Fonts; 29 | 30 | /// 31 | /// Constructor of the container with settings alread loaded into memory 32 | /// 33 | /// 34 | public LanguageContainer(LanguageSettings settings) 35 | { 36 | Settings = settings; 37 | } 38 | 39 | /// 40 | /// Load translation data using template Language data 41 | /// 42 | /// 43 | public void LoadTranslation(LanguageData template) 44 | { 45 | var translationFilePath = Path.Combine(Settings.SettingsDirectory, LanguageData.TranslationFileName); 46 | PlainTextDump(template, translationFilePath); 47 | 48 | if (Fonts == null) 49 | { 50 | Debug.Log($"Loading bundle {Settings.FontBundlePath}"); 51 | AssetBundle fontBundle = AssetBundle.LoadFromFile(Settings.FontBundlePath); 52 | if (fontBundle != null) 53 | { 54 | Fonts = fontBundle.LoadAllAssets(); 55 | } 56 | } 57 | 58 | if (Settings.ImportFromLegacy) 59 | { 60 | LegacyDump(template, translationFilePath); 61 | } 62 | else 63 | { 64 | CrowdinDump(template); 65 | } 66 | } 67 | 68 | /// 69 | /// Load and/or Dump Crowdin format translation 70 | /// 71 | /// Template for new language files 72 | private void CrowdinDump(LanguageData template) 73 | { 74 | Func key = proto => $"{proto.Name}"; 75 | Func value = proto => proto.Translation; 76 | var translationCrowdinFilePath = Path.Combine(Settings.SettingsDirectory, LanguageData.TranslationCrowdinFileName); 77 | if (!File.Exists(translationCrowdinFilePath)) 78 | { 79 | var dict = ToCrowdinDictionary(template.TranslationTable, key, value); 80 | File.WriteAllText(translationCrowdinFilePath, JSON.ToJson(dict)); 81 | } 82 | else 83 | { 84 | var dictionary = JSON.FromJson>(File.ReadAllText(translationCrowdinFilePath)); 85 | Translation = new LanguageData(); 86 | Translation.TranslationTable = new List(); 87 | foreach (var pair in dictionary) 88 | { 89 | // TODO make sure that miss matches with name and ID are handled 90 | var translationProto = TranslationProto.FromCrowdin(pair.Key, pair.Value); 91 | var match = template.TranslationTable.FirstOrDefault(proto => proto.Name == translationProto.Name); 92 | if (translationProto.Name == "string" && match != null) 93 | { 94 | translationProto.Original = match.Original; 95 | translationProto.Name = match.Name; 96 | } 97 | 98 | Translation.TranslationTable.Add(translationProto); 99 | } 100 | 101 | //Translation.UpdateTranslationItems(Settings, template); 102 | // overwrite if new translation show up 103 | var dict = ToCrowdinDictionary(Translation.TranslationTable, key, value); 104 | File.WriteAllText(translationCrowdinFilePath, JSON.ToJson(dict)); 105 | } 106 | } 107 | 108 | /// 109 | /// Load and/or Dump Legacy format translation 110 | /// 111 | /// Template for new language files 112 | /// Translation file path 113 | private void LegacyDump(LanguageData template, string translationFilePath) 114 | { 115 | if (!File.Exists(translationFilePath)) 116 | { 117 | File.WriteAllText(translationFilePath, JSON.ToJson(template)); 118 | } 119 | else 120 | { 121 | Translation = JSON.FromJson(File.ReadAllText(translationFilePath)); 122 | UpdateTranslationItems(Settings, Translation, template); 123 | // overwrite if new translation show up 124 | File.WriteAllText(translationFilePath, JSON.ToJson(Translation)); 125 | } 126 | } 127 | 128 | /// 129 | /// Load and/or Dump Plain text format translation 130 | /// 131 | /// Template for new language files 132 | /// Translation file path 133 | private void PlainTextDump(LanguageData template, string translationFilePath) 134 | { 135 | if (Settings.CreateAndUpdateFromPlainTextDumpUnsafe) 136 | { 137 | var translationDumpFilePath = Path.Combine(Settings.SettingsDirectory, LanguageData.TranslationDumpFileName); 138 | if (!File.Exists(translationDumpFilePath)) 139 | { 140 | var sb = new StringBuilder(); 141 | foreach (var table in template.TranslationTable) 142 | { 143 | sb.AppendLine(table.Original); 144 | sb.AppendLine("-----"); 145 | } 146 | 147 | File.WriteAllText(translationDumpFilePath, sb.ToString()); 148 | } 149 | else 150 | { 151 | var lines = File.ReadAllText(translationDumpFilePath); 152 | var split = lines.Split(new string[] {"-----"}, StringSplitOptions.None); 153 | Translation = new LanguageData {TranslationTable = new List()}; 154 | for (var index = 0; index < template.TranslationTable.Count; index++) 155 | { 156 | Translation.TranslationTable.Add(new TranslationProto(template.TranslationTable[index], split[index] 157 | .Trim('\r', '\n'))); 158 | } 159 | 160 | File.WriteAllText(translationFilePath, JSON.ToJson(Translation)); 161 | } 162 | } 163 | } 164 | 165 | /// 166 | /// Converts list of translation to Dictionary for Crowdin support 167 | /// 168 | /// Translation list to convert 169 | /// Key Expression 170 | /// Value Expression 171 | /// 172 | private Dictionary ToCrowdinDictionary(List translationProto, Func key, Func value) 173 | { 174 | var dict = new Dictionary(); 175 | foreach (var proto in translationProto) 176 | { 177 | if (dict.ContainsKey(key(proto))) 178 | { 179 | Debugger.Break(); 180 | } 181 | else 182 | { 183 | dict.Add(key(proto), value(proto)); 184 | } 185 | } 186 | 187 | return dict; 188 | } 189 | 190 | /// 191 | /// Updates target file with new template data using settings 192 | /// 193 | /// Settings 194 | /// Target file 195 | /// Template file 196 | public void UpdateTranslationItems(LanguageSettings settings, LanguageData targetFile, LanguageData template) 197 | { 198 | var missMatchList = new List(); 199 | var tempTranslationTable = targetFile.TranslationTable.ToList(); 200 | // Find invalid or missing translations 201 | foreach (var translationProto in tempTranslationTable) 202 | { 203 | var match = template.TranslationTable.FirstOrDefault(proto => proto.Name == translationProto.Name); 204 | if (match != null) 205 | { 206 | if (match.Original != translationProto.Original) 207 | { 208 | translationProto.IsValid = false; 209 | missMatchList.Add(translationProto); 210 | ConsoleLogger.LogWarning($"Translation for {translationProto.Original} -- {translationProto.Translation} is no longer valid! This entry original meaning has changed"); 211 | } 212 | else 213 | { 214 | translationProto.Original = match.Original; 215 | if (translationProto.Original.StartsWith("UI")) 216 | { 217 | translationProto.Translation = match.Original; 218 | } 219 | } 220 | } 221 | else 222 | { 223 | translationProto.IsValid = false; 224 | missMatchList.Add(translationProto); 225 | ConsoleLogger.LogWarning($"Translation for {translationProto.Original} -- {translationProto.Translation} is no longer valid! This entry was probably removed"); 226 | } 227 | } 228 | // New translations 229 | foreach (var translationProto in template.TranslationTable) 230 | { 231 | var match = targetFile.TranslationTable.FirstOrDefault(proto => proto.Name == translationProto.Name); 232 | if (match == null) 233 | { 234 | missMatchList.Add(translationProto); 235 | tempTranslationTable.Add(translationProto); 236 | ConsoleLogger.LogWarning($"New translation entry for {translationProto.Original} (Upgrade from {settings.GameVersion} to {GameConfig.gameVersion.ToFullString()})"); 237 | } 238 | } 239 | 240 | targetFile.TranslationTable = tempTranslationTable; 241 | settings.GameVersion = GameConfig.gameVersion.ToFullString(); 242 | } 243 | } 244 | } -------------------------------------------------------------------------------- /TranslationCommon/Translation/LanguageData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using UnityEngine; 5 | 6 | namespace TranslationCommon.Translation 7 | { 8 | /// 9 | /// Container for language translation data 10 | /// 11 | [Serializable] 12 | public class LanguageData 13 | { 14 | /// 15 | /// File name of legacy format 16 | /// 17 | [NonSerialized] 18 | public const string TranslationFileName = "translation.json"; 19 | /// 20 | /// File name of plain text format 21 | /// 22 | [NonSerialized] 23 | public const string TranslationDumpFileName = "translation.dump.txt"; 24 | /// 25 | /// File name of crowdin format 26 | /// 27 | [NonSerialized] 28 | public const string TranslationCrowdinFileName = "translation_DysonSphereProgram.json"; 29 | 30 | /// 31 | /// Translation table - used for loading 32 | /// 33 | [SerializeField] 34 | public List TranslationTable; 35 | 36 | /// 37 | /// Default empty constructor 38 | /// 39 | public LanguageData() 40 | { 41 | } 42 | 43 | /// 44 | /// Copy constructor to generate new Language data from in Game Data 45 | /// 46 | /// Settings 47 | /// In game translations 48 | public LanguageData(LanguageSettings settings, ProtoSet stringProto) 49 | { 50 | settings.VersionUpdate(); 51 | TranslationTable = new List(stringProto.Length); 52 | var translationDelegate = GetOriginalTextDelegate(settings); 53 | for (var i = 0; i < stringProto.dataArray.Length; i++) 54 | { 55 | var proto = stringProto.dataArray[i]; 56 | TranslationProto translationProto = new TranslationProto(); 57 | translationProto.IsValid = true; 58 | translationProto.Original = translationDelegate(proto); 59 | translationProto.Translation = translationDelegate(proto); 60 | translationProto.Name = proto.Name; 61 | TranslationTable.Add(translationProto); 62 | } 63 | } 64 | 65 | /// 66 | /// Delegate constructor for getting field value from original translation 67 | /// 68 | /// Language settings 69 | /// Type of data 70 | /// Resulting delegate to get string from 71 | /// Throws exception if given field info was not found in assmebly 72 | public static Func GetOriginalTextDelegate(LanguageSettings settings) 73 | { 74 | var fieldInfo = typeof(T).GetField(settings.OriginalLanguage, BindingFlags.Public | BindingFlags.Instance); 75 | if (fieldInfo == null) 76 | { 77 | throw new ArgumentException($"LanguageSettings has incorrect original translation value -- used {settings.OriginalLanguage} but it do not exists"); 78 | } 79 | 80 | var d = new Func(arg => (string)fieldInfo.GetValue(arg)); 81 | return param => d(param); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /TranslationCommon/Translation/LanguageSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace TranslationCommon.Translation 5 | { 6 | /// 7 | /// Settings file 8 | /// 9 | [Serializable] 10 | public class LanguageSettings 11 | { 12 | /// 13 | /// Translation settings version 14 | /// 15 | public string Version; 16 | 17 | /// 18 | /// Game version 19 | /// 20 | public string GameVersion; 21 | 22 | /// 23 | /// Original language of the in game translation 24 | /// 25 | public string OriginalLanguage; 26 | 27 | /// 28 | /// Display name in the menu 29 | /// 30 | public string LanguageDisplayName; 31 | 32 | /// 33 | /// Should use legacy format 34 | /// 35 | public bool ImportFromLegacy; 36 | 37 | /// 38 | /// Should use plain text format 39 | /// 40 | public bool CreateAndUpdateFromPlainTextDumpUnsafe; 41 | 42 | /// 43 | /// Settings path 44 | /// 45 | public string SettingsPath { get; set; } 46 | 47 | public string FontBundlePath { get; set; } 48 | 49 | /// 50 | /// Settings directory 51 | /// 52 | public string SettingsDirectory => Path.GetDirectoryName(SettingsPath); 53 | 54 | /// 55 | /// Update settings file 56 | /// 57 | public void VersionUpdate() 58 | { 59 | var defaultVersion = "0.1.0.3"; 60 | if (Version != defaultVersion) Version = defaultVersion; 61 | 62 | GameVersion = GameConfig.gameVersion.ToFullString(); 63 | 64 | if (String.IsNullOrEmpty(OriginalLanguage)) OriginalLanguage = "ENUS"; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /TranslationCommon/Translation/TranslationManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using DSPTranslationPlugin.UnityHarmony; 6 | using TranslationCommon.SimpleJSON; 7 | using UnityEngine; 8 | 9 | namespace TranslationCommon.Translation 10 | { 11 | public static class TranslationManager 12 | { 13 | public const string PlayerPrefsCode = "dps_translate_muchaszewski_language"; 14 | 15 | private static List _langauges; 16 | private static Dictionary _translationDictionary; 17 | private static LanguageContainer _currentLanguage; 18 | private static bool _isInitialized; 19 | 20 | /// 21 | /// List of all translation languages, theirs settings and translations 22 | /// 23 | public static List Langauges 24 | { 25 | get 26 | { 27 | if (_langauges == null) 28 | { 29 | CheckForNewTranslationFolder(); 30 | if (!IsInitialized) 31 | { 32 | LoadCurrentLanguage(); 33 | } 34 | } 35 | return _langauges; 36 | } 37 | set => _langauges = value; 38 | } 39 | 40 | public static string SelectedLanguage { get; set; } 41 | 42 | public static LanguageContainer CurrentLanguage 43 | { 44 | get => _currentLanguage; 45 | set 46 | { 47 | _currentLanguage = value; 48 | if (_currentLanguage != null) 49 | { 50 | SetupTranslationDictionary(); 51 | if (!IsInitialized) 52 | { 53 | LoadCurrentLanguage(); 54 | } 55 | } 56 | } 57 | } 58 | 59 | public static bool IsInitialized 60 | { 61 | get => _isInitialized; 62 | set => _isInitialized = value; 63 | } 64 | 65 | public static Dictionary TranslationDictionary 66 | { 67 | get => _translationDictionary; 68 | private set => _translationDictionary = value; 69 | } 70 | 71 | private static void SetupTranslationDictionary() 72 | { 73 | TranslationDictionary = CurrentLanguage?.Translation.TranslationTable.ToDictionary(proto => proto.Name); 74 | } 75 | 76 | /// 77 | /// Load custom language 78 | /// 79 | public static void LoadCurrentLanguage() 80 | { 81 | if (!IsInitialized) 82 | { 83 | var stringProtoSet = LDB.strings; 84 | foreach (var languageContainer in Langauges) 85 | { 86 | if (languageContainer.Translation == null) 87 | { 88 | var templateLanguageData = new LanguageData(languageContainer.Settings, stringProtoSet); 89 | languageContainer.LoadTranslation(templateLanguageData); 90 | } 91 | } 92 | 93 | var result = PlayerPrefs.GetString(TranslationManager.PlayerPrefsCode); 94 | if (!String.IsNullOrEmpty(result)) 95 | { 96 | SelectedLanguage = result; 97 | if (!Langauges.Exists(container => 98 | container.Settings.LanguageDisplayName == SelectedLanguage)) 99 | { 100 | ConsoleLogger.LogFatal($"Selected language do not exists... {result}"); 101 | SelectedLanguage = null; 102 | } 103 | } 104 | IsInitialized = true; 105 | } 106 | 107 | if (!string.IsNullOrEmpty(SelectedLanguage)) 108 | { 109 | var selectedContainer = Langauges.FirstOrDefault(container => 110 | container.Settings.LanguageDisplayName == SelectedLanguage); 111 | CurrentLanguage = selectedContainer; 112 | TextFontManager.CheckFonts(); 113 | TextFontManager.LoadSettings(); 114 | Localization.language = Language.enUS; 115 | Localization.OnLanguageChange?.Invoke(Language.enUS); 116 | 117 | 118 | } 119 | } 120 | 121 | /// 122 | /// Translation folder settings file name 123 | /// 124 | public const string SettingsJsonFileName = "settings.json"; 125 | 126 | public const string FondBundleFileName = "font_bundle"; 127 | /// 128 | /// Root translation folder 129 | /// 130 | public static string TranslationDirectory => $"{Utils.ConfigPath}/Translation"; 131 | 132 | 133 | /// 134 | /// Gets all languages form the translation folder 135 | /// 136 | private static void CheckForNewTranslationFolder() 137 | { 138 | if (!Directory.Exists(TranslationDirectory)) 139 | { 140 | Directory.CreateDirectory(TranslationDirectory); 141 | } 142 | var directories = Directory.GetDirectories(TranslationDirectory); 143 | Langauges = new List(); 144 | foreach (var directory in directories) 145 | { 146 | LanguageSettings settings; 147 | settings = GetLanguageSettings(directory); 148 | 149 | settings.SettingsPath = Path.Combine(directory, SettingsJsonFileName); 150 | Langauges.Add(new LanguageContainer(settings)); 151 | } 152 | } 153 | 154 | /// 155 | /// Get language settings 156 | /// 157 | /// Directory name 158 | /// 159 | private static LanguageSettings GetLanguageSettings(string directory) 160 | { 161 | LanguageSettings settings = null; 162 | 163 | string translationMain = null; 164 | string bundlePath = ""; 165 | foreach (var file in Directory.GetFiles(directory)) 166 | { 167 | if (file.Contains(SettingsJsonFileName)) translationMain = file; 168 | if (file.Contains(FondBundleFileName)) bundlePath = file; 169 | } 170 | 171 | if (translationMain == null) 172 | { 173 | settings = new LanguageSettings() 174 | { 175 | LanguageDisplayName = Path.GetFileName(directory), 176 | }; 177 | File.WriteAllText(Path.Combine(directory, SettingsJsonFileName), JSON.ToJson(settings, true)); 178 | } 179 | else 180 | { 181 | settings = JSON.FromJson(File.ReadAllText(translationMain)); 182 | settings.FontBundlePath = bundlePath; 183 | settings.VersionUpdate(); 184 | // Overwrite file with potential new settings 185 | File.WriteAllText(Path.Combine(directory, SettingsJsonFileName), JSON.ToJson(settings, true)); 186 | } 187 | 188 | return settings; 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /TranslationCommon/Translation/TranslationProto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TranslationCommon.Translation 4 | { 5 | [Serializable] 6 | public class TranslationProto 7 | { 8 | public bool IsValid; 9 | 10 | public string Name; 11 | 12 | public string Original; 13 | public string Translation; 14 | 15 | public string GetTranslation() 16 | { 17 | return Translation; 18 | } 19 | 20 | public TranslationProto(TranslationProto proto, string translation) 21 | { 22 | Name = proto.Name; 23 | Original = proto.Original; 24 | Translation = translation; 25 | } 26 | 27 | public static TranslationProto FromCrowdin(string name, string translation) 28 | { 29 | TranslationProto result = new TranslationProto(); 30 | result.Name = name; 31 | result.Translation = translation; 32 | return result; 33 | } 34 | 35 | public TranslationProto(string name, int id, string original, string translation) 36 | { 37 | Name = name; 38 | Original = original; 39 | Translation = translation; 40 | } 41 | 42 | public TranslationProto() 43 | { 44 | 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /TranslationCommon/TranslationCommon.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /TranslationCommon/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using TranslationCommon.SimpleJSON; 6 | using UnityEngine; 7 | 8 | namespace TranslationCommon 9 | { 10 | public static class Utils 11 | { 12 | private static readonly string AssemblyLocation = Assembly.GetCallingAssembly().Location; 13 | 14 | public static string PluginFolderName = Path.GetDirectoryName(AssemblyLocation); 15 | /// 16 | /// Plugin path 17 | /// 18 | public static string PluginPath = Path.GetDirectoryName(AssemblyLocation); 19 | 20 | public static string ConfigPath = Application.dataPath; 21 | } 22 | } -------------------------------------------------------------------------------- /TranslationCommon/WildcardMatch.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 All Rights Reserved 3 | // 4 | // H.A. Sullivan 5 | // 04/11/2016 6 | // Wildcard matching string extension method 7 | // MIT License 8 | // 9 | // Copyright(c) [2016] 10 | // [H.A. Sullivan] 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | // 30 | 31 | using System.Collections.Generic; 32 | 33 | public static class WildcardMatch 34 | { 35 | public static bool EqualsWildcard(this string text, string wildcardString) 36 | { 37 | var isLike = true; 38 | byte matchCase = 0; 39 | char[] filter; 40 | char[] reversedFilter; 41 | char[] reversedWord; 42 | char[] word; 43 | var currentPatternStartIndex = 0; 44 | var lastCheckedHeadIndex = 0; 45 | var lastCheckedTailIndex = 0; 46 | var reversedWordIndex = 0; 47 | var reversedPatterns = new List(); 48 | 49 | if (text == null || wildcardString == null) 50 | { 51 | return false; 52 | } 53 | 54 | word = text.ToCharArray(); 55 | filter = wildcardString.ToCharArray(); 56 | 57 | //Set which case will be used (0 = no wildcards, 1 = only ?, 2 = only *, 3 = both ? and * 58 | for (var i = 0; i < filter.Length; i++) 59 | { 60 | if (filter[i] == '?') 61 | { 62 | matchCase += 1; 63 | break; 64 | } 65 | } 66 | 67 | for (var i = 0; i < filter.Length; i++) 68 | { 69 | if (filter[i] == '*') 70 | { 71 | matchCase += 2; 72 | break; 73 | } 74 | } 75 | 76 | if ((matchCase == 0 || matchCase == 1) && word.Length != filter.Length) 77 | { 78 | return false; 79 | } 80 | 81 | switch (matchCase) 82 | { 83 | case 0: 84 | isLike = text == wildcardString; 85 | break; 86 | 87 | case 1: 88 | for (var i = 0; i < text.Length; i++) 89 | { 90 | if (word[i] != filter[i] && filter[i] != '?') 91 | { 92 | isLike = false; 93 | } 94 | } 95 | 96 | break; 97 | 98 | case 2: 99 | //Search for matches until first * 100 | for (var i = 0; i < filter.Length; i++) 101 | { 102 | if (filter[i] != '*') 103 | { 104 | if (filter[i] != word[i]) 105 | { 106 | return false; 107 | } 108 | } 109 | else 110 | { 111 | lastCheckedHeadIndex = i; 112 | break; 113 | } 114 | } 115 | 116 | //Search Tail for matches until first * 117 | for (var i = 0; i < filter.Length; i++) 118 | { 119 | if (filter[filter.Length - 1 - i] != '*') 120 | { 121 | if (word.Length == i) 122 | { 123 | return false; 124 | } 125 | if (filter[filter.Length - 1 - i] != word[word.Length - 1 - i]) 126 | { 127 | return false; 128 | } 129 | } 130 | else 131 | { 132 | lastCheckedTailIndex = i; 133 | break; 134 | } 135 | } 136 | 137 | 138 | //Create a reverse word and filter for searching in reverse. The reversed word and filter do not include already checked chars 139 | reversedWord = new char[word.Length - lastCheckedHeadIndex - lastCheckedTailIndex]; 140 | reversedFilter = new char[filter.Length - lastCheckedHeadIndex - lastCheckedTailIndex]; 141 | 142 | for (var i = 0; i < reversedWord.Length; i++) 143 | { 144 | reversedWord[i] = word[word.Length - (i + 1) - lastCheckedTailIndex]; 145 | } 146 | 147 | for (var i = 0; i < reversedFilter.Length; i++) 148 | { 149 | reversedFilter[i] = filter[filter.Length - (i + 1) - lastCheckedTailIndex]; 150 | } 151 | 152 | //Cut up the filter into seperate patterns, exclude * as they are not longer needed 153 | for (var i = 0; i < reversedFilter.Length; i++) 154 | { 155 | if (reversedFilter[i] == '*') 156 | { 157 | if (i - currentPatternStartIndex > 0) 158 | { 159 | var pattern = new char[i - currentPatternStartIndex]; 160 | for (var j = 0; j < pattern.Length; j++) 161 | { 162 | pattern[j] = reversedFilter[currentPatternStartIndex + j]; 163 | } 164 | 165 | reversedPatterns.Add(pattern); 166 | } 167 | 168 | currentPatternStartIndex = i + 1; 169 | } 170 | } 171 | 172 | //Search for the patterns 173 | for (var i = 0; i < reversedPatterns.Count; i++) 174 | { 175 | for (var j = 0; j < reversedPatterns[i].Length; j++) 176 | { 177 | if (reversedPatterns[i].Length - 1 - j > reversedWord.Length - 1 - reversedWordIndex) 178 | { 179 | return false; 180 | } 181 | 182 | if (reversedPatterns[i][j] != reversedWord[reversedWordIndex + j]) 183 | { 184 | reversedWordIndex += 1; 185 | j = -1; 186 | } 187 | else 188 | { 189 | if (j == reversedPatterns[i].Length - 1) 190 | { 191 | reversedWordIndex = reversedWordIndex + reversedPatterns[i].Length; 192 | } 193 | } 194 | } 195 | } 196 | 197 | break; 198 | 199 | case 3: 200 | //Same as Case 2 except ? is considered a match 201 | //Search Head for matches util first * 202 | for (var i = 0; i < filter.Length; i++) 203 | { 204 | if (filter[i] != '*') 205 | { 206 | if (filter[i] != word[i] && filter[i] != '?') 207 | { 208 | return false; 209 | } 210 | } 211 | else 212 | { 213 | lastCheckedHeadIndex = i; 214 | break; 215 | } 216 | } 217 | 218 | //Search Tail for matches until first * 219 | for (var i = 0; i < filter.Length; i++) 220 | { 221 | if (filter[filter.Length - 1 - i] != '*') 222 | { 223 | if (filter[filter.Length - 1 - i] != word[word.Length - 1 - i] && 224 | filter[filter.Length - 1 - i] != '?') 225 | { 226 | return false; 227 | } 228 | } 229 | else 230 | { 231 | lastCheckedTailIndex = i; 232 | break; 233 | } 234 | } 235 | 236 | // Reverse and trim word and filter 237 | reversedWord = new char[word.Length - lastCheckedHeadIndex - lastCheckedTailIndex]; 238 | reversedFilter = new char[filter.Length - lastCheckedHeadIndex - lastCheckedTailIndex]; 239 | 240 | for (var i = 0; i < reversedWord.Length; i++) 241 | { 242 | reversedWord[i] = word[word.Length - (i + 1) - lastCheckedTailIndex]; 243 | } 244 | 245 | for (var i = 0; i < reversedFilter.Length; i++) 246 | { 247 | reversedFilter[i] = filter[filter.Length - (i + 1) - lastCheckedTailIndex]; 248 | } 249 | 250 | for (var i = 0; i < reversedFilter.Length; i++) 251 | { 252 | if (reversedFilter[i] == '*') 253 | { 254 | if (i - currentPatternStartIndex > 0) 255 | { 256 | var pattern = new char[i - currentPatternStartIndex]; 257 | for (var j = 0; j < pattern.Length; j++) 258 | { 259 | pattern[j] = reversedFilter[currentPatternStartIndex + j]; 260 | } 261 | 262 | reversedPatterns.Add(pattern); 263 | } 264 | 265 | currentPatternStartIndex = i + 1; 266 | } 267 | } 268 | 269 | //Search for the patterns 270 | for (var i = 0; i < reversedPatterns.Count; i++) 271 | { 272 | for (var j = 0; j < reversedPatterns[i].Length; j++) 273 | { 274 | if (reversedPatterns[i].Length - 1 - j > reversedWord.Length - 1 - reversedWordIndex) 275 | { 276 | return false; 277 | } 278 | 279 | 280 | if (reversedPatterns[i][j] != '?' && 281 | reversedPatterns[i][j] != reversedWord[reversedWordIndex + j]) 282 | { 283 | reversedWordIndex += 1; 284 | j = -1; 285 | } 286 | else 287 | { 288 | if (j == reversedPatterns[i].Length - 1) 289 | { 290 | reversedWordIndex = reversedWordIndex + reversedPatterns[i].Length; 291 | } 292 | } 293 | } 294 | } 295 | 296 | break; 297 | } 298 | 299 | return isLike; 300 | } 301 | 302 | public static bool EqualsWildcard(this string text, string wildcardString, bool ignoreCase) 303 | { 304 | if (ignoreCase) 305 | { 306 | return text.ToLower().EqualsWildcard(wildcardString.ToLower()); 307 | } 308 | 309 | return text.EqualsWildcard(wildcardString); 310 | } 311 | } -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muchaszewski/DSP_TranslationMod/bc74e3ae3500ce3c4fb86b7191a9cc5b9e56ee2f/icon.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DSPTranslationPlugin", 3 | "version_number": "0.5.2", 4 | "website_url": "https://github.com/Muchaszewski/DSP_TranslationMod", 5 | "description": "Dyson sphere translation plugin! Translations download form crowdin https://crowdin.com/translate/dyson-sphere-program/14#", 6 | "dependencies": [ 7 | "xiaoye97-BepInEx-5.4.17" 8 | ] 9 | } -------------------------------------------------------------------------------- /thunderstore.toml: -------------------------------------------------------------------------------- 1 | [config] 2 | schemaVersion = "0.0.1" 3 | 4 | [package] 5 | namespace = "Muchaszewski" 6 | name = "DSPTranslationPlugin" 7 | versionNumber = "0.5.2" 8 | description = "Dyson sphere translation plugin! Translations download form crowdin https://crowdin.com/translate/dyson-sphere-program/14#" 9 | websiteUrl = "https://github.com/Muchaszewski/DSP_TranslationMod" 10 | containsNsfwContent = false 11 | 12 | [package.dependencies] 13 | "xiaoye97-BepInEx" = "5.4.17" 14 | 15 | [publish] 16 | repository = "https://thunderstore.io" 17 | communities = ["dyson-sphere-program"] 18 | categories = [] --------------------------------------------------------------------------------