├── .clang-format
├── .github
└── workflows
│ ├── plugin-cd.yml
│ └── plugin-ci.yml
├── .gitignore
├── Config
├── DefaultTolgee.ini
└── FilterPlugin.ini
├── README.md
├── Resources
├── Icon128.png
└── Settings-Icon.png
├── Source
├── Tolgee
│ ├── Private
│ │ ├── Tolgee.cpp
│ │ ├── TolgeeCdnFetcherSubsystem.cpp
│ │ ├── TolgeeLocalizationInjectorSubsystem.cpp
│ │ ├── TolgeeLog.cpp
│ │ ├── TolgeeRuntimeSettings.cpp
│ │ ├── TolgeeTextSource.cpp
│ │ └── TolgeeUtils.cpp
│ ├── Public
│ │ ├── Tolgee.h
│ │ ├── TolgeeCdnFetcherSubsystem.h
│ │ ├── TolgeeLocalizationInjectorSubsystem.h
│ │ ├── TolgeeLog.h
│ │ ├── TolgeeRuntimeSettings.h
│ │ ├── TolgeeTextSource.h
│ │ └── TolgeeUtils.h
│ └── Tolgee.Build.cs
├── TolgeeEditor
│ ├── Private
│ │ ├── STolgeeTranslationTab.cpp
│ │ ├── TolgeeEditor.cpp
│ │ ├── TolgeeEditorIntegrationSubsystem.cpp
│ │ ├── TolgeeEditorSettings.cpp
│ │ └── TolgeeStyle.cpp
│ ├── Public
│ │ ├── STolgeeTranslationTab.h
│ │ ├── TolgeeEditor.h
│ │ ├── TolgeeEditorIntegrationSubsystem.h
│ │ ├── TolgeeEditorSettings.h
│ │ └── TolgeeStyle.h
│ └── TolgeeEditor.Build.cs
└── TolgeeProvider
│ ├── Private
│ ├── TolgeeLocalizationProvider.cpp
│ ├── TolgeeProvider.cpp
│ ├── TolgeeProviderLocalizationServiceCommand.cpp
│ ├── TolgeeProviderLocalizationServiceOperations.cpp
│ └── TolgeeProviderUtils.cpp
│ ├── Public
│ ├── TolgeeLocalizationProvider.h
│ ├── TolgeeProvider.h
│ ├── TolgeeProviderLocalizationServiceCommand.h
│ ├── TolgeeProviderLocalizationServiceOperations.h
│ ├── TolgeeProviderLocalizationServiceWorker.h
│ └── TolgeeProviderUtils.h
│ └── TolgeeProvider.Build.cs
└── Tolgee.uplugin
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | BasedOnStyle: LLVM
4 | AccessModifierOffset: -4
5 | AlignConsecutiveAssignments: false
6 | AlignConsecutiveDeclarations: false
7 | AlignOperands: false
8 | AlignAfterOpenBracket: BlockIndent
9 | AlignTrailingComments: false
10 | AllowShortFunctionsOnASingleLine : InlineOnly
11 | AllowShortLambdasOnASingleLine: Empty
12 | AlwaysBreakTemplateDeclarations: Yes
13 | PointerAlignment: Left
14 | BraceWrapping:
15 | AfterCaseLabel: true
16 | AfterClass: true
17 | AfterControlStatement: true
18 | AfterEnum: true
19 | AfterFunction: true
20 | AfterNamespace: true
21 | AfterStruct: true
22 | AfterUnion: true
23 | AfterExternBlock: false
24 | BeforeCatch: true
25 | BeforeElse: true
26 | BeforeLambdaBody: true
27 | BeforeWhile: true
28 | SplitEmptyFunction: true
29 | SplitEmptyRecord: true
30 | SplitEmptyNamespace: true
31 | BreakBeforeBraces: Custom
32 | BinPackArguments: false
33 | BinPackParameters: false
34 | BreakConstructorInitializers: AfterColon
35 | ColumnLimit: 1000
36 | IncludeBlocks: Regroup
37 | IncludeCategories:
38 | - Regex: '.*\.generated\.h'
39 | Priority: 10
40 | SortPriority: 10
41 | - Regex: '^<.*\.h>'
42 | Priority: 1
43 | SortPriority: 1
44 | - Regex: '.*'
45 | Priority: 2
46 | SortPriority: 2
47 | IncludeIsMainRegex: '([-_](test|unittest))?$'
48 | IncludeIsMainSourceRegex: ''
49 | IndentCaseBlocks: true
50 | IndentWidth: 4
51 | MacroBlockBegin: ''
52 | MacroBlockEnd: ''
53 | MaxEmptyLinesToKeep: 2
54 | NamespaceIndentation: All
55 | SpacesInAngles: false
56 | TabWidth: 4
57 | UseTab: Always
58 | ...
59 |
--------------------------------------------------------------------------------
/.github/workflows/plugin-cd.yml:
--------------------------------------------------------------------------------
1 | name: Plugin CD
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | plugin-cd:
10 | uses: outoftheboxplugins/BuildMachines/.github/workflows/release-to-github.yml@master
11 | with:
12 | plugin_name: Tolgee
13 |
14 | secrets: inherit
--------------------------------------------------------------------------------
/.github/workflows/plugin-ci.yml:
--------------------------------------------------------------------------------
1 | name: Plugin CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | plugin-ci:
13 | uses: outoftheboxplugins/BuildMachines/.github/workflows/compile-plugin.yml@master
14 | secrets:
15 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
16 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Binaries/
2 | Intermediate/
--------------------------------------------------------------------------------
/Config/DefaultTolgee.ini:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Config/FilterPlugin.ini:
--------------------------------------------------------------------------------
1 | [FilterPlugin]
2 | /.clang-format
3 | /.gitignore
4 | /README.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tolgee Unreal Engine Plugin
2 |
3 | 
4 | [](https://twitter.com/Tolgee_i18n)
5 | [](https://github.com/tolgee/tolgee-unreal)
6 | [](https://tolg.ee/slack)
7 |
8 | [
](https://tolgee.io)
9 |
10 | This repository contains the code for the Tolgee Unreal Engine Plugin.
11 |
12 | It provides ability to easily manage localization texts directly in your Unreal Engine project.
13 |
14 | To learn more visit [https://tolgee.io](https://tolgee.io)
15 |
--------------------------------------------------------------------------------
/Resources/Icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tolgee/tolgee-unreal/335689068f69f2d5f7945a12ece343fb541476a5/Resources/Icon128.png
--------------------------------------------------------------------------------
/Resources/Settings-Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tolgee/tolgee-unreal/335689068f69f2d5f7945a12ece343fb541476a5/Resources/Settings-Icon.png
--------------------------------------------------------------------------------
/Source/Tolgee/Private/Tolgee.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "Tolgee.h"
4 |
5 | IMPLEMENT_MODULE(FTolgeeModule, Tolgee)
6 |
--------------------------------------------------------------------------------
/Source/Tolgee/Private/TolgeeCdnFetcherSubsystem.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeCdnFetcherSubsystem.h"
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include "TolgeeRuntimeSettings.h"
10 | #include "TolgeeLog.h"
11 |
12 | void UTolgeeCdnFetcherSubsystem::OnGameInstanceStart(UGameInstance* GameInstance)
13 | {
14 | const UTolgeeRuntimeSettings* Settings = GetDefault();
15 | if (Settings->CdnAddresses.IsEmpty())
16 | {
17 | UE_LOG(LogTolgee, Display, TEXT("No CDN addresses configured. Packaged builds will only use static data."));
18 | return;
19 | }
20 | if (GIsEditor && !Settings->bUseCdnInEditor)
21 | {
22 | UE_LOG(LogTolgee, Display, TEXT("CDN was disabled for editor but it will be used in the final game."));
23 | return;
24 | }
25 |
26 | FetchAllCdns();
27 | }
28 |
29 | void UTolgeeCdnFetcherSubsystem::OnGameInstanceEnd(bool bIsSimulating)
30 | {
31 | ResetData();
32 |
33 | LastModifiedDates.Empty();
34 | }
35 |
36 | TMap> UTolgeeCdnFetcherSubsystem::GetDataToInject() const
37 | {
38 | return CachedTranslations;
39 | }
40 |
41 | void UTolgeeCdnFetcherSubsystem::FetchAllCdns()
42 | {
43 | const UTolgeeRuntimeSettings* Settings = GetDefault();
44 |
45 | for (const FString& CdnAddress : Settings->CdnAddresses)
46 | {
47 | TArray Cultures = UKismetInternationalizationLibrary::GetLocalizedCultures();
48 | for (const FString& Culture : Cultures)
49 | {
50 | const FString DownloadUrl = FString::Printf(TEXT("%s/%s.po"), *CdnAddress, *Culture);
51 |
52 | UE_LOG(LogTolgee, Display, TEXT("Fetching localization data for culture: %s from CDN: %s"), *Culture, *DownloadUrl);
53 | FetchFromCdn(Culture, DownloadUrl);
54 | }
55 | };
56 |
57 | }
58 |
59 | void UTolgeeCdnFetcherSubsystem::FetchFromCdn(const FString& Culture, const FString& DownloadUrl)
60 | {
61 | const FString* LastModifiedDate = LastModifiedDates.Find(DownloadUrl);
62 |
63 | const FHttpRequestRef HttpRequest = FHttpModule::Get().CreateRequest();
64 | HttpRequest->SetVerb("GET");
65 | HttpRequest->SetURL(DownloadUrl);
66 | HttpRequest->SetHeader(TEXT("accept"), TEXT("application/json"));
67 |
68 | if (LastModifiedDate)
69 | {
70 | HttpRequest->SetHeader(TEXT("If-Modified-Since"), *LastModifiedDate);
71 | }
72 |
73 | HttpRequest->OnProcessRequestComplete().BindUObject(this, &ThisClass::OnFetchedFromCdn, Culture);
74 | HttpRequest->ProcessRequest();
75 |
76 | NumRequestsSent++;
77 | }
78 |
79 | void UTolgeeCdnFetcherSubsystem::OnFetchedFromCdn(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FString InCutlure)
80 | {
81 | if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode()))
82 | {
83 | UE_LOG(LogTolgee, Display, TEXT("Fetch successfully for %s to %s."), *InCutlure, *Request->GetURL());
84 |
85 | TArray Translations = ExtractTranslationsFromPO(Response->GetContentAsString());
86 | CachedTranslations.Emplace(InCutlure, Translations);
87 |
88 | const FString LastModified = Response->GetHeader(TEXT("Last-Modified"));
89 | if (!LastModified.IsEmpty())
90 | {
91 | LastModifiedDates.Emplace(Request->GetURL(), LastModified);
92 | }
93 | }
94 | else if (Response.IsValid() && Response->GetResponseCode() == EHttpResponseCodes::NotModified)
95 | {
96 | UE_LOG(LogTolgee, Display, TEXT("No new data for %s to %s."), *InCutlure, *Request->GetURL());
97 | }
98 | else
99 | {
100 | UE_LOG(LogTolgee, Error, TEXT("Request for %s to %s failed."), *InCutlure, *Request->GetURL());
101 | }
102 |
103 | NumRequestsCompleted++;
104 |
105 | if (NumRequestsCompleted == NumRequestsSent)
106 | {
107 | UE_LOG(LogTolgee, Display, TEXT("All requests completed. Refreshing translation data."));
108 | RefreshTranslationDataAsync();
109 | }
110 | }
111 |
112 | void UTolgeeCdnFetcherSubsystem::ResetData()
113 | {
114 | NumRequestsSent = 0;
115 | NumRequestsCompleted = 0;
116 |
117 | CachedTranslations.Empty();
118 | }
--------------------------------------------------------------------------------
/Source/Tolgee/Private/TolgeeLocalizationInjectorSubsystem.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeLocalizationInjectorSubsystem.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #if WITH_LOCALIZATION_MODULE
11 | #include
12 | #include
13 | #endif
14 |
15 | #include "TolgeeLog.h"
16 | #include "TolgeeTextSource.h"
17 |
18 | void UTolgeeLocalizationInjectorSubsystem::OnGameInstanceStart(UGameInstance* GameInstance)
19 | {
20 | }
21 |
22 | void UTolgeeLocalizationInjectorSubsystem::OnGameInstanceEnd(bool bIsSimulating)
23 | {
24 | }
25 |
26 | void UTolgeeLocalizationInjectorSubsystem::GetLocalizedResources(const ELocalizationLoadFlags InLoadFlags, TArrayView InPrioritizedCultures, FTextLocalizationResource& InOutNativeResource, FTextLocalizationResource& InOutLocalizedResource) const
27 | {
28 | TMap> DataToInject = GetDataToInject();
29 | for (const TPair>& CachedTranslation : DataToInject)
30 | {
31 | if (!InPrioritizedCultures.Contains(CachedTranslation.Key))
32 | {
33 | continue;
34 | }
35 |
36 | for (const FTolgeeTranslationData& TranslationData : CachedTranslation.Value)
37 | {
38 | const FTextKey InNamespace = TranslationData.ParsedNamespace;
39 | const FTextKey InKey = TranslationData.ParsedKey;
40 | const FString InLocalizedString = TranslationData.Translation;
41 |
42 | if (FTextLocalizationResource::FEntry* ExistingEntry = InOutLocalizedResource.Entries.Find(FTextId(InNamespace, InKey)))
43 | {
44 | //NOTE: -1 is a higher than usual priority, meaning this entry will override any existing one. See FTextLocalizationResource::ShouldReplaceEntry
45 | InOutLocalizedResource.AddEntry(InNamespace, InKey, ExistingEntry->SourceStringHash, InLocalizedString, -1);
46 | }
47 | else
48 | {
49 | #if UE_VERSION_NEWER_THAN(5, 5, 0)
50 | const FString InNamespaceString = InNamespace.ToString();
51 | const FString InKeyString = InKey.ToString();
52 | #else
53 | const FString InNamespaceString = InNamespace.GetChars();
54 | const FString InKeyString = InKey.GetChars();
55 | #endif
56 | UE_LOG(LogTolgee, Warning, TEXT("Failed to inject translation for %s:%s. Default entry not found."), *InNamespaceString, *InKeyString);
57 | }
58 | }
59 | }
60 | }
61 |
62 | TMap> UTolgeeLocalizationInjectorSubsystem::GetDataToInject() const
63 | {
64 | return {};
65 | }
66 |
67 | void UTolgeeLocalizationInjectorSubsystem::Initialize(FSubsystemCollectionBase& Collection)
68 | {
69 | Super::Initialize(Collection);
70 |
71 | TextSource = MakeShared();
72 | TextSource->GetLocalizedResources.BindUObject(this, &ThisClass::GetLocalizedResources);
73 | FTextLocalizationManager::Get().RegisterTextSource(TextSource.ToSharedRef());
74 |
75 | FWorldDelegates::OnStartGameInstance.AddUObject(this, &ThisClass::OnGameInstanceStart);
76 |
77 | #if WITH_EDITOR
78 | FEditorDelegates::PrePIEEnded.AddUObject(this, &ThisClass::OnGameInstanceEnd);
79 | #endif
80 | }
81 |
82 | void UTolgeeLocalizationInjectorSubsystem::RefreshTranslationDataAsync()
83 | {
84 | UE_LOG(LogTolgee, Verbose, TEXT("RefreshTranslationDataAsync requested."));
85 |
86 | AsyncTask(
87 | ENamedThreads::AnyHiPriThreadHiPriTask,
88 | [=]()
89 | {
90 | TRACE_CPUPROFILER_EVENT_SCOPE(UTolgeeLocalizationInjectorSubsystem::RefreshResources)
91 |
92 | UE_LOG(LogTolgee, Verbose, TEXT("RefreshTranslationDataAsync executing."));
93 |
94 | FTextLocalizationManager::Get().RefreshResources();
95 | }
96 | );
97 | }
98 |
99 | TArray UTolgeeLocalizationInjectorSubsystem::ExtractTranslationsFromPO(const FString& PoContent)
100 | {
101 | TRACE_CPUPROFILER_EVENT_SCOPE(UTolgeeLocalizationInjectorSubsystem::ExtractTranslationsFromPO)
102 |
103 | TArray Result;
104 |
105 | #if WITH_LOCALIZATION_MODULE
106 | FPortableObjectFormatDOM PortableObject;
107 | PortableObject.FromString(PoContent);
108 |
109 | for (auto EntryPairIter = PortableObject.GetEntriesIterator(); EntryPairIter; ++EntryPairIter)
110 | {
111 | auto POEntry = EntryPairIter->Value;
112 | if (POEntry->MsgId.IsEmpty() || POEntry->MsgStr.Num() == 0 || POEntry->MsgStr[0].IsEmpty())
113 | {
114 | // We ignore the header entry or entries with no translation.
115 | continue;
116 | }
117 |
118 | FTolgeeTranslationData TranslationData;
119 |
120 | constexpr ELocalizedTextCollapseMode InTextCollapseMode = ELocalizedTextCollapseMode::IdenticalTextIdAndSource;
121 | constexpr EPortableObjectFormat InPOFormat = EPortableObjectFormat::Crowdin;
122 |
123 | PortableObjectPipeline::ParseBasicPOFileEntry(*POEntry, TranslationData.ParsedNamespace, TranslationData.ParsedKey, TranslationData.SourceText, TranslationData.Translation, InTextCollapseMode, InPOFormat);
124 |
125 | Result.Add(TranslationData);
126 | }
127 | #else
128 | UE_LOG(LogTolgee, Error, TEXT("Localization module is not available. Cannot extract translations from PO content."));
129 | #endif
130 |
131 | return Result;
132 | }
--------------------------------------------------------------------------------
/Source/Tolgee/Private/TolgeeLog.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeLog.h"
4 |
5 | DEFINE_LOG_CATEGORY(LogTolgee);
6 |
--------------------------------------------------------------------------------
/Source/Tolgee/Private/TolgeeRuntimeSettings.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeRuntimeSettings.h"
4 |
5 | FName UTolgeeRuntimeSettings::GetCategoryName() const
6 | {
7 | return TEXT("Plugins");
8 | }
--------------------------------------------------------------------------------
/Source/Tolgee/Private/TolgeeTextSource.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeTextSource.h"
4 |
5 | #include
6 |
7 | int32 FTolgeeTextSource::GetPriority() const
8 | {
9 | //NOTE: you might expect to see ::Highest, but we actually want to be the last one to load resources so we can re-use the existing SourceStringHash
10 | // Then, during the load we will inject the new entries the highest priority.
11 | return ELocalizedTextSourcePriority::Lowest;
12 | }
13 |
14 | bool FTolgeeTextSource::GetNativeCultureName(const ELocalizedTextSourceCategory InCategory, FString& OutNativeCultureName)
15 | {
16 | // TODO: Investigate if we should implement this
17 | return false;
18 | }
19 |
20 | void FTolgeeTextSource::GetLocalizedCultureNames(const ELocalizationLoadFlags InLoadFlags, TSet& OutLocalizedCultureNames)
21 | {
22 | // TODO: Investigate if we should implement this
23 | }
24 |
25 | void FTolgeeTextSource::LoadLocalizedResources(const ELocalizationLoadFlags InLoadFlags, TArrayView InPrioritizedCultures, FTextLocalizationResource& InOutNativeResource, FTextLocalizationResource& InOutLocalizedResource)
26 | {
27 | GetLocalizedResources.Execute(InLoadFlags, InPrioritizedCultures, InOutNativeResource, InOutLocalizedResource);
28 | }
--------------------------------------------------------------------------------
/Source/Tolgee/Private/TolgeeUtils.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeUtils.h"
4 |
5 | #include
6 |
7 | FString TolgeeUtils::AppendQueryParameters(const FString& BaseUrl, const TArray& Parameters)
8 | {
9 | FString FinalUrl = BaseUrl;
10 | TArray RemainingParameters = Parameters;
11 |
12 | if (RemainingParameters.Num() >= 1)
13 | {
14 | FinalUrl.Appendf(TEXT("?%s"), *RemainingParameters[0]);
15 | RemainingParameters.RemoveAt(0);
16 | }
17 |
18 | for (const FString& Parameter : RemainingParameters)
19 | {
20 | FinalUrl.Appendf(TEXT("&%s"), *Parameter);
21 | }
22 |
23 | return FinalUrl;
24 | }
25 |
26 | FString TolgeeUtils::GetSdkType()
27 | {
28 | return TEXT("Unreal");
29 | }
30 |
31 | FString TolgeeUtils::GetSdkVersion()
32 | {
33 | const TSharedPtr TolgeePlugin = IPluginManager::Get().FindPlugin("Tolgee");
34 | return TolgeePlugin->GetDescriptor().VersionName;
35 | }
36 |
37 | void TolgeeUtils::AddSdkHeaders(FHttpRequestRef& HttpRequest)
38 | {
39 | HttpRequest->SetHeader(TEXT("X-Tolgee-SDK-Type"), GetSdkType());
40 | HttpRequest->SetHeader(TEXT("X-Tolgee-SDK-Version"), GetSdkVersion());
41 | }
--------------------------------------------------------------------------------
/Source/Tolgee/Public/Tolgee.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | /**
8 | * Base module for common Tolgee functionality and runtime features.
9 | */
10 | class FTolgeeModule : public IModuleInterface
11 | {
12 | };
13 |
--------------------------------------------------------------------------------
/Source/Tolgee/Public/TolgeeCdnFetcherSubsystem.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "TolgeeLocalizationInjectorSubsystem.h"
6 |
7 | #include
8 |
9 | #include "TolgeeCdnFetcherSubsystem.generated.h"
10 |
11 | /**
12 | * Subsystem responsible for fetching localization data from a CDN and injecting it into the game.
13 | */
14 | UCLASS()
15 | class UTolgeeCdnFetcherSubsystem : public UTolgeeLocalizationInjectorSubsystem
16 | {
17 | GENERATED_BODY()
18 |
19 | // ~ Begin UTolgeeLocalizationInjectorSubsystem interface
20 | virtual void OnGameInstanceStart(UGameInstance* GameInstance) override;
21 | virtual void OnGameInstanceEnd(bool bIsSimulating) override;
22 | virtual TMap> GetDataToInject() const override;
23 | // ~ End UTolgeeLocalizationInjectorSubsystem interface
24 |
25 | /**
26 | * Runs multiple requests to fetch all projects from the CDN.
27 | */
28 | void FetchAllCdns();
29 | /**
30 | * Fetches the localization data from the CDN.
31 | */
32 | void FetchFromCdn(const FString& Culture, const FString& DownloadUrl);
33 | /**
34 | * Callback function for when the CDN request is completed.
35 | */
36 | void OnFetchedFromCdn(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FString InCutlure);
37 | /**
38 | * Clears the cached translations and resets the request counters.
39 | */
40 | void ResetData();
41 | /**
42 | * List of cached translations for each culture.
43 | */
44 | TMap> CachedTranslations;
45 | /**
46 | * Counts the number of requests sent.
47 | */
48 | int32 NumRequestsSent = 0;
49 | /**
50 | * Counts the number of requests completed.
51 | */
52 | int32 NumRequestsCompleted = 0;
53 | /**
54 | * Map storing the last modified dates of the translations.
55 | */
56 | TMap LastModifiedDates;
57 | };
--------------------------------------------------------------------------------
/Source/Tolgee/Public/TolgeeLocalizationInjectorSubsystem.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | #include "TolgeeLocalizationInjectorSubsystem.generated.h"
8 |
9 | class UGameInstance;
10 | class FTolgeeTextSource;
11 |
12 | /**
13 | * Holds the parsed data from the PO file
14 | */
15 | struct FTolgeeTranslationData
16 | {
17 | FString ParsedNamespace;
18 | FString ParsedKey;
19 | FString SourceText;
20 | FString Translation;
21 | };
22 |
23 | /**
24 | * Base class for all subsystems responsible for dynamically injecting data at runtime (e.g.: CDN, Dashboard, etc.)
25 | */
26 | UCLASS(Abstract)
27 | class TOLGEE_API UTolgeeLocalizationInjectorSubsystem : public UEngineSubsystem
28 | {
29 | GENERATED_BODY()
30 |
31 | protected:
32 | /**
33 | * Callback executed when the game instance is created and started.
34 | */
35 | virtual void OnGameInstanceStart(UGameInstance* GameInstance);
36 | /**
37 | * Callback executed when the game instance is destroyed.
38 | */
39 | virtual void OnGameInstanceEnd(bool bIsSimulating);
40 | /**
41 | * Callback executed when the TolgeeTextSource needs to load the localized resources.
42 | */
43 | virtual void GetLocalizedResources(const ELocalizationLoadFlags InLoadFlags, TArrayView InPrioritizedCultures, FTextLocalizationResource& InOutNativeResource, FTextLocalizationResource& InOutLocalizedResource) const;
44 | /**
45 | * Simplified getter to allow subclasses to provide their own data to inject for GetLocalizedResources.
46 | */
47 | virtual TMap> GetDataToInject() const;
48 | /**
49 | * Triggers an async refresh of the LocalizationManager resources.
50 | */
51 | void RefreshTranslationDataAsync();
52 | /**
53 | * Converts PO content to a list of translation data.
54 | */
55 | TArray ExtractTranslationsFromPO(const FString& PoContent);
56 |
57 | // Begin UEngineSubsystem interface
58 | virtual void Initialize(FSubsystemCollectionBase& Collection) override;
59 | // End UEngineSubsystem interface
60 |
61 | private:
62 | /**
63 | * Custom Localization Text Source that allows handling of Localized Resources via delegate
64 | */
65 | TSharedPtr TextSource;
66 | };
67 |
--------------------------------------------------------------------------------
/Source/Tolgee/Public/TolgeeLog.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | TOLGEE_API DECLARE_LOG_CATEGORY_EXTERN(LogTolgee, Log, All)
--------------------------------------------------------------------------------
/Source/Tolgee/Public/TolgeeRuntimeSettings.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | #include "TolgeeRuntimeSettings.generated.h"
8 |
9 | /**
10 | * @brief Settings for the Tolgee runtime functionality.
11 | * NOTE: Settings here will be packaged in the final game
12 | */
13 | UCLASS(config = Tolgee, defaultconfig)
14 | class TOLGEE_API UTolgeeRuntimeSettings : public UDeveloperSettings
15 | {
16 | GENERATED_BODY()
17 |
18 | public:
19 | /**
20 | * Root addresses we should use to fetch the localization data from the CDN.
21 | */
22 | UPROPERTY(Config, EditAnywhere, Category = "Tolgee|CDN")
23 | TArray CdnAddresses;
24 |
25 | /**
26 | * Easy toggle to disable the CDN functionality in the editor.
27 | */
28 | UPROPERTY(Config, EditAnywhere, Category = "Tolgee|CDN")
29 | bool bUseCdnInEditor = false;
30 |
31 | // ~ Begin UDeveloperSettings Interface
32 | virtual FName GetCategoryName() const override;
33 | // ~ End UDeveloperSettings Interface
34 | };
--------------------------------------------------------------------------------
/Source/Tolgee/Public/TolgeeTextSource.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | #include
8 |
9 | using FGetLocalizedResources = TDelegate InPrioritizedCultures, FTextLocalizationResource& InOutNativeResource, FTextLocalizationResource& InOutLocalizedResource)>;
10 |
11 | /**
12 | * Translation source data for injecting data fetched from Tolgee backend into the localization system.
13 | */
14 | class TOLGEE_API FTolgeeTextSource : public ILocalizedTextSource
15 | {
16 | public:
17 | /**
18 | * Callback executed when the text source needs to load the localized resources
19 | */
20 | FGetLocalizedResources GetLocalizedResources;
21 |
22 | private:
23 | // Begin ILocalizedTextSource interface
24 | virtual int32 GetPriority() const override;
25 | virtual bool GetNativeCultureName(const ELocalizedTextSourceCategory InCategory, FString& OutNativeCultureName) override;
26 | virtual void GetLocalizedCultureNames(const ELocalizationLoadFlags InLoadFlags, TSet& OutLocalizedCultureNames) override;
27 | virtual void LoadLocalizedResources(const ELocalizationLoadFlags InLoadFlags, TArrayView InPrioritizedCultures, FTextLocalizationResource& InOutNativeResource, FTextLocalizationResource& InOutLocalizedResource) override;
28 | // End ILocalizedTextSource interface
29 | };
30 |
--------------------------------------------------------------------------------
/Source/Tolgee/Public/TolgeeUtils.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | #include
8 |
9 | namespace TolgeeUtils
10 | {
11 | /**
12 | * @brief Constructs an endpoint by appending query parameters to a base url
13 | */
14 | FString TOLGEE_API AppendQueryParameters(const FString& BaseUrl, const TArray& Parameters);
15 | /**
16 | * @brief Sdk type of the Tolgee integration
17 | */
18 | FString TOLGEE_API GetSdkType();
19 | /**
20 | * @brief Sdk version of the Tolgee integration
21 | */
22 | FString TOLGEE_API GetSdkVersion();
23 | /**
24 | * Adds the Tolgee SDK type and version to the headers of the request
25 | */
26 | void TOLGEE_API AddSdkHeaders(FHttpRequestRef& HttpRequest);
27 | } // namespace TolgeeUtils
28 |
--------------------------------------------------------------------------------
/Source/Tolgee/Tolgee.Build.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | using UnrealBuildTool;
4 |
5 | public class Tolgee : ModuleRules
6 | {
7 | public Tolgee(ReadOnlyTargetRules Target) : base(Target)
8 | {
9 | PublicDependencyModuleNames.AddRange(
10 | new string[]
11 | {
12 | "Core",
13 | "CoreUObject",
14 | "DeveloperSettings",
15 | "Engine",
16 | "HTTP",
17 | "Json",
18 | "JsonUtilities",
19 | "Projects",
20 | }
21 | );
22 |
23 | if (Target.bBuildEditor)
24 | {
25 | PublicDependencyModuleNames.Add("UnrealEd");
26 | }
27 |
28 | bool bLocalizationModuleAvailable = !Target.bIsEngineInstalled || Target.Configuration != UnrealTargetConfiguration.Shipping;
29 | if (bLocalizationModuleAvailable)
30 | {
31 | PublicDefinitions.Add("WITH_LOCALIZATION_MODULE=1");
32 | PublicDependencyModuleNames.Add("Localization");
33 | }
34 | else
35 | {
36 | PublicDefinitions.Add("WITH_LOCALIZATION_MODULE=0");
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Private/STolgeeTranslationTab.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "STolgeeTranslationTab.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | #include "TolgeeEditorIntegrationSubsystem.h"
15 | #include "TolgeeEditorSettings.h"
16 | #include "TolgeeLog.h"
17 | #include "TolgeeUtils.h"
18 |
19 | namespace
20 | {
21 | FName STextBlockType(TEXT("STextBlock"));
22 | }
23 |
24 | void STolgeeTranslationTab::Construct(const FArguments& InArgs)
25 | {
26 | const UTolgeeEditorSettings* Settings = GetDefault();
27 | const FString LoginUrl = FString::Printf(TEXT("%s/login"), *Settings->ApiUrl);
28 |
29 | DrawHandle = UDebugDrawService::Register(TEXT("Game"), FDebugDrawDelegate::CreateSP(this, &STolgeeTranslationTab::DebugDrawCallback));
30 |
31 | // clang-format off
32 | SDockTab::Construct( SDockTab::FArguments()
33 | .TabRole(NomadTab)
34 | .OnTabClosed_Raw(this, &STolgeeTranslationTab::CloseTab)
35 | [
36 | SAssignNew(Browser, SWebBrowser)
37 | .InitialURL(LoginUrl)
38 | .ShowControls(false)
39 | .ShowErrorMessage(true)
40 | ]);
41 | // clang-format on
42 |
43 | FGlobalTabmanager::Get()->OnActiveTabChanged_Subscribe(FOnActiveTabChanged::FDelegate::CreateSP(this, &STolgeeTranslationTab::OnActiveTabChanged));
44 | }
45 |
46 | void STolgeeTranslationTab::CloseTab(TSharedRef DockTab)
47 | {
48 | UDebugDrawService::Unregister(DrawHandle);
49 | }
50 |
51 | void STolgeeTranslationTab::OnActiveTabChanged(TSharedPtr PreviouslyActive, TSharedPtr NewlyActivated)
52 | {
53 | if (PreviouslyActive == AsShared())
54 | {
55 | GEngine->GetEngineSubsystem()->ManualFetch();
56 | }
57 | }
58 |
59 | void STolgeeTranslationTab::DebugDrawCallback(UCanvas* Canvas, APlayerController* PC)
60 | {
61 | if (!GEngine->GameViewport)
62 | {
63 | return;
64 | }
65 |
66 | TSharedPtr GameViewportWidget = GEngine->GameViewport->GetGameViewportWidget();
67 | if (!GameViewportWidget.IsValid())
68 | {
69 | return;
70 | }
71 |
72 | FSlateApplication& Application = FSlateApplication::Get();
73 | FWidgetPath WidgetPath = Application.LocateWindowUnderMouse(Application.GetCursorPos(), Application.GetInteractiveTopLevelWindows());
74 |
75 | #if UE_VERSION_NEWER_THAN(5, 0, 0)
76 | const bool bValidHover = WidgetPath.Widgets.Num() > 0 && WidgetPath.ContainsWidget(GameViewportWidget.Get());
77 | #else
78 | const bool bValidHover = WidgetPath.Widgets.Num() > 0 && GameViewportWidget.IsValid() && WidgetPath.ContainsWidget(GameViewportWidget.ToSharedRef());
79 | #endif
80 |
81 | if (bValidHover)
82 | {
83 | TSharedPtr CurrentHoveredWidget = WidgetPath.GetLastWidget();
84 | if (CurrentHoveredWidget->GetType() == STextBlockType)
85 | {
86 | TSharedPtr CurrentTextBlock = StaticCastSharedPtr(CurrentHoveredWidget);
87 |
88 | // Calculate the Start & End in local space based on widget & parent viewport
89 | const FGeometry& HoveredGeometry = CurrentHoveredWidget->GetCachedGeometry();
90 | const FGeometry& ViewportGeometry = GameViewportWidget->GetCachedGeometry();
91 |
92 | // TODO: make this a setting
93 | const FVector2D Padding = FVector2D{0.2f, 0.2f};
94 |
95 | const FVector2D UpperLeft = {0, 0};
96 | const FVector2D LowerRight = {1, 1};
97 |
98 | FVector2D Start = HoveredGeometry.GetAbsolutePositionAtCoordinates(UpperLeft) - ViewportGeometry.GetAbsolutePositionAtCoordinates(UpperLeft) + Padding;
99 | FVector2D End = HoveredGeometry.GetAbsolutePositionAtCoordinates(LowerRight) - ViewportGeometry.GetAbsolutePositionAtCoordinates(UpperLeft) - Padding;
100 |
101 | FBox2D Box = FBox2D(Start, End);
102 | // TODO: make this a setting
103 | const FLinearColor DrawColor = FLinearColor(FColor::Red);
104 | DrawDebugCanvas2DBox(Canvas, Box, DrawColor);
105 |
106 | // Get information about the currently hovered text
107 | const FText CurrentText = CurrentTextBlock->GetText();
108 | const TOptional Namespace = FTextInspector::GetNamespace(CurrentText);
109 | const TOptional Key = FTextInspector::GetKey(CurrentText);
110 |
111 | // Update the browser widget if we have valid data and the URL is different
112 | if (Namespace && Key)
113 | {
114 | const FString CleanNamespace = TextNamespaceUtil::StripPackageNamespace(Namespace.GetValue());
115 |
116 | // NOTE: This might look odd, but we need to mirror the id's used internally by Unreal as those are used for importing the key.
117 | const FString TolgeeKeyId = FPlatformHttp::UrlEncode(FString::Printf(TEXT("%s,%s"), *CleanNamespace, *Key.GetValue()));
118 |
119 | ShowWidgetForAsync(TolgeeKeyId);
120 | }
121 | }
122 | }
123 | }
124 |
125 | void STolgeeTranslationTab::ShowWidgetForAsync(const FString& TolgeeKeyId)
126 | {
127 | AsyncTask(ENamedThreads::AnyBackgroundHiPriTask,
128 | [this, TolgeeKeyId]()
129 | {
130 | ShowWidgetFor(TolgeeKeyId);
131 | });
132 | }
133 |
134 | void STolgeeTranslationTab::ShowWidgetFor(const FString& TolgeeKeyId)
135 | {
136 | if (bRequestInProgress)
137 | {
138 | return;
139 | }
140 |
141 | TGuardValue ScopedRequestProgress(bRequestInProgress, true);
142 |
143 | const FString ProjectId = FindProjectIdFor(TolgeeKeyId);
144 | if (ProjectId.IsEmpty())
145 | {
146 | UE_LOG(LogTolgee, Warning, TEXT("No project found for key '%s'"), *TolgeeKeyId);
147 | return;
148 | }
149 |
150 | const UTolgeeEditorSettings* Settings = GetDefault();
151 |
152 | const FString NewUrl = FString::Printf(TEXT("%s/projects/%s/translations/single?key=%s"), *Settings->ApiUrl, *ProjectId, *TolgeeKeyId);
153 | const FString CurrentUrl = Browser->GetUrl();
154 |
155 | if (NewUrl != CurrentUrl && Browser->IsLoaded())
156 | {
157 | UE_LOG(LogTolgee, Log, TEXT("CurrentWidget displayed from %s"), *NewUrl);
158 |
159 | Browser->LoadURL(NewUrl);
160 | }
161 | }
162 |
163 | FString STolgeeTranslationTab::FindProjectIdFor(const FString& TolgeeKeyId) const
164 | {
165 | const UTolgeeEditorSettings* Settings = GetDefault();
166 |
167 | TMap PendingRequests;
168 | for (const FString& ProjectId : Settings->ProjectIds)
169 | {
170 | const FString RequestUrl = FString::Printf(TEXT("%s/v2/projects/%s/translations?filterKeyName=%s"), *Settings->ApiUrl, *ProjectId, *TolgeeKeyId);
171 |
172 | FHttpRequestRef HttpRequest = FHttpModule::Get().CreateRequest();
173 | HttpRequest->SetVerb("GET");
174 | HttpRequest->SetURL(RequestUrl);
175 | HttpRequest->SetHeader(TEXT("X-API-Key"), Settings->ApiKey);
176 | TolgeeUtils::AddSdkHeaders(HttpRequest);
177 |
178 | HttpRequest->ProcessRequest();
179 |
180 | PendingRequests.Add(ProjectId, HttpRequest);
181 | }
182 |
183 | while (!PendingRequests.IsEmpty())
184 | {
185 | FPlatformProcess::Sleep(0.1f);
186 |
187 | for (auto RequestIt = PendingRequests.CreateIterator(); RequestIt; ++RequestIt)
188 | {
189 | const TPair Pair = *RequestIt;
190 | const FHttpRequestPtr Request = Pair.Value;
191 | const FString& ProjectId = Pair.Key;
192 |
193 | if (EHttpRequestStatus::IsFinished(Request->GetStatus()))
194 | {
195 | const FHttpResponsePtr Response = Request->GetResponse();
196 | const FString ResponseContent = Response.IsValid() ? Response->GetContentAsString() : FString();
197 |
198 | const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(ResponseContent);
199 | TSharedPtr JsonObject;
200 |
201 | if (FJsonSerializer::Deserialize(JsonReader, JsonObject))
202 | {
203 | const TSharedPtr Embedded = JsonObject->GetObjectField(TEXT("_embedded"));
204 | const TArray> Keys = Embedded->GetArrayField(TEXT("keys"));
205 | if (!Keys.IsEmpty())
206 | {
207 | return ProjectId;
208 | }
209 | }
210 |
211 | RequestIt.RemoveCurrent();
212 | }
213 | }
214 | }
215 |
216 | return {};
217 | }
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Private/TolgeeEditor.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeEditor.h"
4 |
5 | #include
6 | #include
7 |
8 | #include "STolgeeTranslationTab.h"
9 | #include "TolgeeEditorSettings.h"
10 | #include "TolgeeStyle.h"
11 |
12 | #define LOCTEXT_NAMESPACE "Tolgee"
13 |
14 | namespace
15 | {
16 | const FName MenuTabName = FName("TolgeeDashboardMenuTab");
17 | }
18 |
19 | void FTolgeeEditorModule::StartupModule()
20 | {
21 | RegisterStyle();
22 | RegisterWindowExtension();
23 | RegisterToolbarExtension();
24 | }
25 |
26 | void FTolgeeEditorModule::ShutdownModule()
27 | {
28 | UnregisterStyle();
29 | UnregisterWindowExtension();
30 | UnregisterToolbarExtension();
31 | }
32 |
33 | void FTolgeeEditorModule::RegisterStyle()
34 | {
35 | FTolgeeStyle::Initialize();
36 | }
37 |
38 | void FTolgeeEditorModule::UnregisterStyle()
39 | {
40 | FTolgeeStyle::Shutdown();
41 | }
42 |
43 | void FTolgeeEditorModule::RegisterWindowExtension()
44 | {
45 | FTabSpawnerEntry& DashboardTab = FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
46 | MenuTabName,
47 | FOnSpawnTab::CreateLambda(
48 | [](const FSpawnTabArgs& Args)
49 | {
50 | return SNew(STolgeeTranslationTab);
51 | }
52 | )
53 | );
54 |
55 | // clang-format off
56 | DashboardTab.SetDisplayName(LOCTEXT("DashboardName", "Tolgee Dashboard"))
57 | .SetTooltipText(LOCTEXT("DashboardTooltip", "Get an overview of your project state in a separate tab."))
58 | .SetIcon(FSlateIcon(FTolgeeStyle::Get().GetStyleSetName(), "Tolgee.Settings"))
59 | .SetMenuType(ETabSpawnerMenuType::Hidden);
60 | // clang-format on
61 | }
62 |
63 | void FTolgeeEditorModule::UnregisterWindowExtension()
64 | {
65 | FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(MenuTabName);
66 | }
67 |
68 | void FTolgeeEditorModule::RegisterToolbarExtension()
69 | {
70 | FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor");
71 | const TSharedPtr ExtensionManager = LevelEditorModule.GetToolBarExtensibilityManager();
72 |
73 | #if ENGINE_MAJOR_VERSION > 4
74 | const FName ToolbarHook = TEXT("ProjectSettings");
75 | #else
76 | const FName ToolbarHook = TEXT("Settings");
77 | #endif
78 |
79 | ToolbarExtender = MakeShareable(new FExtender);
80 | ToolbarExtender->AddToolBarExtension(ToolbarHook, EExtensionHook::First, nullptr, FToolBarExtensionDelegate::CreateRaw(this, &FTolgeeEditorModule::ExtendToolbar));
81 | ExtensionManager->AddExtender(ToolbarExtender);
82 | }
83 |
84 | void FTolgeeEditorModule::UnregisterToolbarExtension()
85 | {
86 | if (ToolbarExtender.IsValid())
87 | {
88 | FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor");
89 | const TSharedPtr ExtensionManager = LevelEditorModule.GetToolBarExtensibilityManager();
90 |
91 | ExtensionManager->RemoveExtender(ToolbarExtender);
92 | ToolbarExtender.Reset();
93 | }
94 | }
95 |
96 | void FTolgeeEditorModule::ExtendToolbar(FToolBarBuilder& Builder)
97 | {
98 | Builder.AddComboButton(
99 | FUIAction(),
100 | FOnGetContent::CreateLambda(
101 | [=]()
102 | {
103 | FMenuBuilder MenuBuilder(true, NULL);
104 |
105 | MenuBuilder.AddMenuEntry(
106 | LOCTEXT("TranslationDashboard", "Translation Tab"),
107 | LOCTEXT("TranslationDashboardTip", "Open a translation widget inside Unreal which allows you translate text by hovering on top"),
108 | FSlateIcon(),
109 | FUIAction(FExecuteAction::CreateLambda(
110 | []()
111 | {
112 | FGlobalTabmanager::Get()->TryInvokeTab(MenuTabName);
113 | }
114 | ))
115 | );
116 |
117 | MenuBuilder.AddMenuEntry(
118 | LOCTEXT("WebDashboard", "Web dashboard"),
119 | LOCTEXT("WebDashboardTip", "Launches the Tolgee dashboard in your browser"),
120 | FSlateIcon(),
121 | FUIAction(FExecuteAction::CreateLambda(
122 | []()
123 | {
124 | const UTolgeeEditorSettings* Settings = GetDefault();
125 | FPlatformProcess::LaunchURL(*Settings->ApiUrl, nullptr, nullptr);
126 | }
127 | ))
128 | );
129 |
130 | MenuBuilder.AddMenuEntry(
131 | LOCTEXT("OpenSettings", "Open Settings"),
132 | LOCTEXT("OpenSettingsTip", "Open the plugin settings section"),
133 | FSlateIcon(),
134 | FUIAction(FExecuteAction::CreateLambda(
135 | []()
136 | {
137 | //TODO: Enable this after settings get unified
138 | //UTolgeeSettings::OpenSettings();
139 | }
140 | ))
141 | );
142 |
143 | MenuBuilder.AddMenuEntry(
144 | LOCTEXT("OpenDocumentation", "Open Documentation"),
145 | LOCTEXT("OpenDocumentationTip", "Open a step by step guide on how to use our integration"),
146 | FSlateIcon(),
147 | FUIAction(FExecuteAction::CreateLambda(
148 | []()
149 | {
150 | const TSharedPtr TolgeePlugin = IPluginManager::Get().FindPlugin("Tolgee");
151 | const FString DocsURL = TolgeePlugin->GetDescriptor().DocsURL;
152 | FPlatformProcess::LaunchURL(*DocsURL, nullptr, nullptr);
153 | }
154 | ))
155 | );
156 |
157 | MenuBuilder.AddMenuEntry(
158 | LOCTEXT("GetSupport", "Get support"),
159 | LOCTEXT("GetSupportTip", "Join our slack and get support directly"),
160 | FSlateIcon(),
161 | FUIAction(FExecuteAction::CreateLambda(
162 | []()
163 | {
164 | const TSharedPtr CleanProjectPlugin = IPluginManager::Get().FindPlugin("Tolgee");
165 | const FString DocsURL = CleanProjectPlugin->GetDescriptor().SupportURL;
166 | FPlatformProcess::LaunchURL(*DocsURL, nullptr, nullptr);
167 | }
168 | ))
169 | );
170 |
171 | MenuBuilder.AddMenuEntry(
172 | LOCTEXT("ReportIssue", "Report issue"),
173 | LOCTEXT("ReportIssueTip", "Report an issue on our GitHub"),
174 | FSlateIcon(),
175 | FUIAction(FExecuteAction::CreateLambda(
176 | []()
177 | {
178 | FPlatformProcess::LaunchURL(TEXT("https://github.com/tolgee/tolgee-unreal/issues"), nullptr, nullptr);
179 | }
180 | ))
181 | );
182 |
183 | return MenuBuilder.MakeWidget();
184 | }
185 | ),
186 | LOCTEXT("TolgeeDashboardSettingsCombo", "Tolgee Settings"),
187 | LOCTEXT("TolgeeDashboardSettingsCombo_ToolTip", "Tolgee Dashboard settings"),
188 | FSlateIcon(FTolgeeStyle::Get().GetStyleSetName(), "Tolgee.Settings"),
189 | false
190 | );
191 | }
192 |
193 | #undef LOCTEXT_NAMESPACE
194 |
195 | IMPLEMENT_MODULE(FTolgeeEditorModule, TolgeeEditor)
196 |
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Private/TolgeeEditorIntegrationSubsystem.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeEditorIntegrationSubsystem.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "TolgeeEditorSettings.h"
12 | #include "TolgeeLog.h"
13 | #include "TolgeeRuntimeSettings.h"
14 | #include "TolgeeUtils.h"
15 |
16 | void UTolgeeEditorIntegrationSubsystem::ManualFetch()
17 | {
18 | FetchIUpdatesAreAvailableAsync();
19 | }
20 |
21 | void UTolgeeEditorIntegrationSubsystem::OnGameInstanceStart(UGameInstance* GameInstance)
22 | {
23 | const UTolgeeRuntimeSettings* RuntimeSettings = GetDefault();
24 | if (RuntimeSettings->bUseCdnInEditor)
25 | {
26 | UE_LOG(LogTolgee, Display, TEXT("Tolgee is configured to use CDN in editor, fetching directly from Tolgee dashboard is disabled. If you want to use the dashboard data directly, disable CDN in the Tolgee settings."));
27 | return;
28 | }
29 |
30 | const UTolgeeEditorSettings* EditorSettings = GetDefault();
31 | if (EditorSettings->ProjectIds.IsEmpty())
32 | {
33 | UE_LOG(LogTolgee, Display, TEXT("CDN was disabled in editor, but no projects are configured. Static data will be used instead."));
34 | return;
35 | }
36 |
37 | FTimerDelegate Delegate = FTimerDelegate::CreateUObject(this, &ThisClass::OnRefreshTick);
38 | GameInstance->GetWorld()->GetTimerManager().SetTimer(RefreshTick, Delegate, EditorSettings->RefreshInterval, true);
39 |
40 | FetchAllProjects();
41 | }
42 |
43 | void UTolgeeEditorIntegrationSubsystem::OnGameInstanceEnd(bool bIsSimulating)
44 | {
45 | ResetData();
46 | }
47 |
48 | TMap> UTolgeeEditorIntegrationSubsystem::GetDataToInject() const
49 | {
50 | return CachedTranslations;
51 | }
52 |
53 | void UTolgeeEditorIntegrationSubsystem::FetchAllProjects()
54 | {
55 | const UTolgeeEditorSettings* Settings = GetDefault();
56 |
57 | for (const FString& ProjectId : Settings->ProjectIds)
58 | {
59 | const FString RequestUrl = FString::Printf(TEXT("%s/v2/projects/%s/export?format=PO"), *Settings->ApiUrl, *ProjectId);
60 | UE_LOG(LogTolgee, Display, TEXT("Fetching localization data for project %s from Tolgee dashboard: %s"), *ProjectId, *RequestUrl);
61 | FetchFromDashboard(ProjectId, RequestUrl);
62 | }
63 |
64 | LastFetchTime = FDateTime::UtcNow();
65 | }
66 |
67 | void UTolgeeEditorIntegrationSubsystem::FetchFromDashboard(const FString& ProjectId, const FString& RequestUrl)
68 | {
69 | const UTolgeeEditorSettings* Settings = GetDefault();
70 |
71 | FHttpRequestRef HttpRequest = FHttpModule::Get().CreateRequest();
72 | HttpRequest->SetURL(RequestUrl);
73 | HttpRequest->SetVerb("GET");
74 | HttpRequest->SetHeader(TEXT("X-API-Key"), Settings->ApiKey);
75 | TolgeeUtils::AddSdkHeaders(HttpRequest);
76 |
77 | HttpRequest->OnProcessRequestComplete().BindUObject(this, &ThisClass::OnFetchedFromDashboard, ProjectId);
78 | HttpRequest->ProcessRequest();
79 |
80 | NumRequestsSent++;
81 | }
82 |
83 | void UTolgeeEditorIntegrationSubsystem::OnFetchedFromDashboard(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FString ProjectId)
84 | {
85 | if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode()))
86 | {
87 | UE_LOG(LogTolgee, Display, TEXT("Fetch successfully for %s to %s."), *ProjectId, *Request->GetURL());
88 | ReadTranslationsFromZipContent(ProjectId, Response->GetContent());
89 | }
90 | else
91 | {
92 | UE_LOG(LogTolgee, Error, TEXT("Request for %s to %s failed."), *ProjectId, *Request->GetURL());
93 | }
94 |
95 | NumRequestsCompleted++;
96 |
97 | if (NumRequestsCompleted == NumRequestsSent)
98 | {
99 | UE_LOG(LogTolgee, Display, TEXT("All requests completed. Refreshing translation data."));
100 | RefreshTranslationDataAsync();
101 | }
102 | }
103 |
104 | void UTolgeeEditorIntegrationSubsystem::ResetData()
105 | {
106 | NumRequestsSent = 0;
107 | NumRequestsCompleted = 0;
108 |
109 | CachedTranslations.Empty();
110 | LastFetchTime = {0};
111 | }
112 |
113 | bool UTolgeeEditorIntegrationSubsystem::ReadTranslationsFromZipContent(const FString& ProjectId, const TArray& ResponseContent)
114 | {
115 | static FCriticalSection ReadFromFileCriticalSection;
116 | FScopeLock Lock(&ReadFromFileCriticalSection);
117 |
118 | const FString TempPath = FPaths::ProjectSavedDir() / TEXT("Tolgee") / TEXT("In-Context") / ProjectId + TEXT(".zip");
119 | const bool bSaveSuccess = FFileHelper::SaveArrayToFile(ResponseContent, *TempPath);
120 | if (!bSaveSuccess)
121 | {
122 | UE_LOG(LogTolgee, Error, TEXT("Failed to save zip for project %s as file to %s"), *ProjectId, *TempPath);
123 | return false;
124 | }
125 |
126 | IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
127 |
128 | IFileHandle* ArchiveFileHandle = PlatformFile.OpenRead(*TempPath);
129 | FZipArchiveReader ZipReader = {ArchiveFileHandle};
130 | if (!ZipReader.IsValid())
131 | {
132 | UE_LOG(LogTolgee, Error, TEXT("Failed to open zip for project %s located at %s"), *ProjectId, *TempPath);
133 | return false;
134 | }
135 |
136 | const TArray FileNames = ZipReader.GetFileNames();
137 | for (const FString& FileName : FileNames)
138 | {
139 | TArray FileBuffer;
140 | if (ZipReader.TryReadFile(FileName, FileBuffer))
141 | {
142 | const FString InCulture = FPaths::GetBaseFilename(FileName);
143 | const FString FileContents = FString(FileBuffer.Num(), UTF8_TO_TCHAR(FileBuffer.GetData()));
144 |
145 | TArray Translations = ExtractTranslationsFromPO(FileContents);
146 | CachedTranslations.FindOrAdd(InCulture).Append(Translations);
147 | }
148 | else
149 | {
150 | UE_LOG(LogTolgee, Warning, TEXT("Failed to read file %s for project %s inside zip at %s"), *FileName, *ProjectId, *TempPath);
151 | }
152 | }
153 |
154 | return true;
155 | }
156 |
157 | void UTolgeeEditorIntegrationSubsystem::OnRefreshTick()
158 | {
159 | FetchIUpdatesAreAvailableAsync();
160 | }
161 |
162 | void UTolgeeEditorIntegrationSubsystem::FetchIUpdatesAreAvailableAsync()
163 | {
164 | AsyncTask(ENamedThreads::AnyBackgroundHiPriTask,
165 | [this]()
166 | {
167 | FetchIfProjectsWereUpdated();
168 | });
169 | }
170 |
171 | void UTolgeeEditorIntegrationSubsystem::FetchIfProjectsWereUpdated()
172 | {
173 | if (bRequestInProgress)
174 | {
175 | return;
176 | }
177 |
178 | TGuardValue ScopedRequestProgress(bRequestInProgress, true);
179 |
180 | FDateTime LastProjectUpdate = {0};
181 | TArray PendingRequests;
182 | const UTolgeeEditorSettings* Settings = GetDefault();
183 |
184 | for (const FString& ProjectId : Settings->ProjectIds)
185 | {
186 | const FString RequestUrl = FString::Printf(TEXT("%s/v2/projects/%s/stats"), *Settings->ApiUrl, *ProjectId);
187 |
188 | FHttpRequestRef HttpRequest = FHttpModule::Get().CreateRequest();
189 | HttpRequest->SetVerb("GET");
190 | HttpRequest->SetURL(RequestUrl);
191 | HttpRequest->SetHeader(TEXT("X-API-Key"), Settings->ApiKey);
192 | TolgeeUtils::AddSdkHeaders(HttpRequest);
193 |
194 | HttpRequest->ProcessRequest();
195 |
196 | PendingRequests.Add(HttpRequest);
197 | }
198 |
199 |
200 | while (!PendingRequests.IsEmpty())
201 | {
202 | FPlatformProcess::Sleep(0.1f);
203 |
204 | for (auto RequestIt = PendingRequests.CreateIterator(); RequestIt; ++RequestIt)
205 | {
206 | const FHttpRequestPtr& Request = *RequestIt;
207 | if (EHttpRequestStatus::IsFinished(Request->GetStatus()))
208 | {
209 | const FHttpResponsePtr Response = Request->GetResponse();
210 | const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
211 | TSharedPtr JsonObject;
212 |
213 | if (FJsonSerializer::Deserialize(JsonReader, JsonObject))
214 | {
215 | const TArray> LanguageStats = JsonObject->GetArrayField(TEXT("languageStats"));
216 | for (const TSharedPtr& Language : LanguageStats)
217 | {
218 | const double LanguageUpdateTime = Language->AsObject()->GetNumberField(TEXT("translationsUpdatedAt"));
219 | const int64 UnixTimestampSeconds = LanguageUpdateTime / 1000;
220 | const FDateTime UpdateTime = FDateTime::FromUnixTimestamp(UnixTimestampSeconds);
221 |
222 | LastProjectUpdate = LastProjectUpdate < UpdateTime ? UpdateTime : LastProjectUpdate;
223 | }
224 | }
225 |
226 | RequestIt.RemoveCurrent();
227 | }
228 | }
229 | }
230 |
231 | if (LastProjectUpdate > LastFetchTime)
232 | {
233 | ResetData();
234 | FetchAllProjects();
235 | }
236 | else
237 | {
238 | UE_LOG(LogTolgee, Display, TEXT("No new updates since last fetch at %s, last project update was at %s"), *LastFetchTime.ToString(), *LastProjectUpdate.ToString());
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Private/TolgeeEditorSettings.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeEditorSettings.h"
4 |
5 | FName UTolgeeEditorSettings::GetCategoryName() const
6 | {
7 | return TEXT("Plugins");
8 | }
9 |
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Private/TolgeeStyle.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeStyle.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | FName FTolgeeStyle::StyleName("TolgeeStyle");
12 | TUniquePtr FTolgeeStyle::Inst(nullptr);
13 |
14 | const FName& FTolgeeStyle::GetStyleSetName() const
15 | {
16 | return StyleName;
17 | }
18 |
19 | const FTolgeeStyle& FTolgeeStyle::Get()
20 | {
21 | ensure(Inst.IsValid());
22 | return *Inst.Get();
23 | }
24 |
25 | void FTolgeeStyle::Initialize()
26 | {
27 | if (!Inst.IsValid())
28 | {
29 | Inst = TUniquePtr(new FTolgeeStyle);
30 | }
31 | }
32 |
33 | void FTolgeeStyle::Shutdown()
34 | {
35 | if (Inst.IsValid())
36 | {
37 | FSlateStyleRegistry::UnRegisterSlateStyle(*Inst.Get());
38 | Inst.Reset();
39 | }
40 | }
41 |
42 | FTolgeeStyle::FTolgeeStyle() : FSlateStyleSet(StyleName)
43 | {
44 |
45 | SetParentStyleName(FAppStyle::GetAppStyleSetName());
46 |
47 | FSlateStyleSet::SetContentRoot(IPluginManager::Get().FindPlugin(TEXT("Tolgee"))->GetBaseDir() / TEXT("Resources"));
48 |
49 | {
50 | Set("Tolgee.Settings", new IMAGE_BRUSH("Settings-Icon", FVector2D(64, 64)));
51 | }
52 |
53 | FSlateStyleRegistry::RegisterSlateStyle(*this);
54 | }
55 |
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Public/STolgeeTranslationTab.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | class SWebBrowser;
8 |
9 | /**
10 | * Nomad Tab used to display the web translation widget inside Unreal when an Textblock is hovered
11 | */
12 | class STolgeeTranslationTab : public SDockTab
13 | {
14 | public:
15 | SLATE_BEGIN_ARGS(STolgeeTranslationTab)
16 | {
17 | }
18 |
19 | SLATE_END_ARGS()
20 |
21 | /**
22 | * @brief Constructs the translation dashboard widget
23 | */
24 | void Construct(const FArguments& InArgs);
25 |
26 | private:
27 | /**
28 | * @brief Callback executed when the DockTab is deactivated
29 | */
30 | void CloseTab(TSharedRef DockTab);
31 | /**
32 | * @brief Callback executed when the active tab is changed
33 | */
34 | void OnActiveTabChanged(TSharedPtr NewlyActivated, TSharedPtr PreviouslyActive);
35 | /**
36 | * @brief Callback executed when the debug service wants to draw on screen
37 | */
38 | void DebugDrawCallback(UCanvas* Canvas, APlayerController* PC);
39 | /**
40 | * Runs an asynchronous request to find the matching Url to the given TolgeeKeyId.
41 | */
42 | void ShowWidgetForAsync(const FString& TolgeeKeyId);
43 | /**
44 | * Updates the browser widget to display the Tolgee web editor for the given key.
45 | */
46 | void ShowWidgetFor(const FString& TolgeeKeyId);
47 | /**
48 | * Finds the project id for the given TolgeeKeyId.
49 | */
50 | FString FindProjectIdFor(const FString& TolgeeKeyId) const;
51 | /**
52 | * @brief Handle for the registered debug callback
53 | */
54 | FDelegateHandle DrawHandle;
55 | /**
56 | * @brief Browser widget used to display tolgee web editor
57 | */
58 | TSharedPtr Browser;
59 | /**
60 | * Flag used to prevent multiple requests from being sent at the same time.
61 | */
62 | TAtomic bRequestInProgress = false;
63 | };
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Public/TolgeeEditor.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | /**
8 | * @brief TolgeeEditor module is responsible for integrating translation features into the editor (tab to allow in-editor translation, gather & upload local keys)
9 | */
10 | class FTolgeeEditorModule : public IModuleInterface
11 | {
12 | // Begin IModuleInterface interface
13 | virtual void StartupModule() override;
14 | virtual void ShutdownModule() override;
15 | // End IModuleInterface interface
16 |
17 | /**
18 | * @brief Registers the SlateStyle for custom editor styling
19 | */
20 | void RegisterStyle();
21 | /**
22 | * @brief Unregisters the SlateStyle for custom editor styling
23 | */
24 | void UnregisterStyle();
25 | /**
26 | * @brief Register the custom nomad tab spawn for the dashboard
27 | */
28 | void RegisterWindowExtension();
29 | /**
30 | * @brief Unregister the custom nomad tab spawn for the dashboard
31 | */
32 | void UnregisterWindowExtension();
33 | /**
34 | * @brief Registers all the extensions for the Toolbar menu
35 | */
36 | void RegisterToolbarExtension();
37 | /**
38 | * @brief Unregisters all the extensions for the Toolbar menu
39 | */
40 | void UnregisterToolbarExtension();
41 | /**
42 | * @brief Fills the toolbar with all the buttons accessible to the user
43 | */
44 | void ExtendToolbar(FToolBarBuilder& Builder);
45 | /**
46 | * @brief The extender passed to the level editor to extend it's toolbar
47 | */
48 | TSharedPtr ToolbarExtender;
49 | };
50 |
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Public/TolgeeEditorIntegrationSubsystem.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "TolgeeLocalizationInjectorSubsystem.h"
6 |
7 | #include
8 |
9 | #include "TolgeeEditorIntegrationSubsystem.generated.h"
10 |
11 | /**
12 | * Subsystem responsible for fetching localization data directly from the Tolgee dashboard (without exporting or CDN publishing).
13 | */
14 | UCLASS()
15 | class UTolgeeEditorIntegrationSubsystem : public UTolgeeLocalizationInjectorSubsystem
16 | {
17 | GENERATED_BODY()
18 |
19 | public:
20 | /**
21 | * Performs an immediate fetch of the localization data from the Tolgee dashboard.
22 | */
23 | void ManualFetch();
24 |
25 | private:
26 | // ~ Begin UTolgeeLocalizationInjectorSubsystem interface
27 | virtual void OnGameInstanceStart(UGameInstance* GameInstance) override;
28 | virtual void OnGameInstanceEnd(bool bIsSimulating) override;
29 | virtual TMap> GetDataToInject() const override;
30 | // ~ End UTolgeeLocalizationInjectorSubsystem interface
31 |
32 | /*
33 | * Runs multiple requests to fetch all projects from the Tolgee dashboard.
34 | */
35 | void FetchAllProjects();
36 | /**
37 | * Fetches the localization data from the Tolgee dashboard.
38 | */
39 | void FetchFromDashboard(const FString& ProjectId, const FString& RequestUrl);
40 | /**
41 | * Callback function for when the Tolgee dashboard data is retrived.
42 | */
43 | void OnFetchedFromDashboard(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FString ProjectId);
44 | /**
45 | * Clears the cached translations and resets the request counters.
46 | */
47 | void ResetData();
48 | /**
49 | * Saves a request content to a temporary zip on disk and reads the translations from it.
50 | */
51 | bool ReadTranslationsFromZipContent(const FString& ProjectId, const TArray& ResponseContent);
52 | /**
53 | * Callback function executed at a regular interval to refresh the localization data.
54 | */
55 | void OnRefreshTick();
56 | /**
57 | * Runs an asynchronous request to check if any updates are available for the projects.
58 | */
59 | void FetchIUpdatesAreAvailableAsync();
60 | /**
61 | * If any of the projects were updated, fetches resets the data and fetches the latest translations.
62 | */
63 | void FetchIfProjectsWereUpdated();
64 | /**
65 | * List of cached translations for each culture.
66 | */
67 | TMap> CachedTranslations;
68 | /**
69 | * Counts the number of requests sent.
70 | */
71 | int32 NumRequestsSent = 0;
72 | /**
73 | * Counts the number of requests completed.
74 | */
75 | int32 NumRequestsCompleted = 0;
76 | /*
77 | * Handle for the refresh tick delegate used to constantly refresh the localization data.
78 | */
79 | FTimerHandle RefreshTick;
80 | /**
81 | * Last time any translations were fetched from the Tolgee dashboard.
82 | */
83 | FDateTime LastFetchTime = {0};
84 | /**
85 | * Flag used to prevent multiple requests from being sent at the same time.
86 | */
87 | TAtomic bRequestInProgress = false;
88 | };
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Public/TolgeeEditorSettings.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | #include "TolgeeEditorSettings.generated.h"
8 |
9 | /**
10 | * Contains all the configurable properties for a single Localization Target.
11 | */
12 | USTRUCT()
13 | struct FTolgeePerTargetSettings
14 | {
15 | GENERATED_BODY()
16 |
17 | /**
18 | * Id of the project in Tolgee.
19 | */
20 | UPROPERTY(EditAnywhere, Category = "Tolgee Localization")
21 | FString ProjectId;
22 | };
23 |
24 |
25 | /*
26 | * @brief Settings for the Tolgee editor-only functionality.
27 | */
28 | UCLASS(config = Tolgee, defaultconfig)
29 | class TOLGEEEDITOR_API UTolgeeEditorSettings : public UDeveloperSettings
30 | {
31 | GENERATED_BODY()
32 |
33 | public:
34 | /**
35 | * Api Key used for requests authentication.
36 | * IMPORTANT: This will be saved in plain text in the config file.
37 | * IMPORTANT: If you have multiple Tolgee projects connected, use a Personal Access Token instead of the API key.
38 | */
39 | UPROPERTY(Config, EditAnywhere, Category = "Tolgee")
40 | FString ApiKey = TEXT("");
41 |
42 | /**
43 | * Api Url used for requests.
44 | * IMPORTANT: Change this if you are using a self-hosted Tolgee instance.
45 | */
46 | UPROPERTY(Config, EditAnywhere, Category = "Tolgee")
47 | FString ApiUrl = TEXT("https://app.tolgee.io");
48 |
49 | /**
50 | * Project IDs we want to fetch translations for during editor PIE sessions.
51 | */
52 | UPROPERTY(Config, EditAnywhere, Category = "Tolgee|In-Context")
53 | TArray ProjectIds;
54 |
55 | /**
56 | * How often we should check the projects above for updates.
57 | */
58 | UPROPERTY(Config, EditAnywhere, Category = "Tolgee|In-Context")
59 | float RefreshInterval = 20.0f;
60 |
61 | /**
62 | * Configurable settings for each localization target.
63 | */
64 | UPROPERTY(Config, EditAnywhere, Category = "Tolgee|Provider")
65 | TMap PerTargetSettings;
66 |
67 | // ~ Begin UDeveloperSettings Interface
68 | virtual FName GetCategoryName() const override;
69 | // ~ End UDeveloperSettings Interface
70 | };
71 |
72 |
--------------------------------------------------------------------------------
/Source/TolgeeEditor/Public/TolgeeStyle.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | /**
8 | * @brief Declares the Tolgee extension visual style.
9 | */
10 | class FTolgeeStyle : public FSlateStyleSet
11 | {
12 | public:
13 |
14 | /**
15 | * @brief Access the singleton instance for this SlateStyle
16 | */
17 | static const FTolgeeStyle& Get();
18 | /**
19 | * @brief Creates and Registers the plugin SlateStyle
20 | */
21 | static void Initialize();
22 | /**
23 | * @brief Unregisters the plugin SlateStyle
24 | */
25 | static void Shutdown();
26 |
27 | // Begin FSlateStyleSet Interface
28 | virtual const FName& GetStyleSetName() const override;
29 | // End FSlateStyleSet Interface
30 |
31 | private:
32 | FTolgeeStyle();
33 |
34 | /**
35 | * @brief Unique name for this SlateStyle
36 | */
37 | static FName StyleName;
38 | /**
39 | * @brief Singleton instances of this SlateStyle.
40 | */
41 | static TUniquePtr Inst;
42 | };
43 |
--------------------------------------------------------------------------------
/Source/TolgeeEditor/TolgeeEditor.Build.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | using UnrealBuildTool;
4 |
5 | public class TolgeeEditor : ModuleRules
6 | {
7 | public TolgeeEditor(ReadOnlyTargetRules Target) : base(Target)
8 | {
9 | PublicDependencyModuleNames.AddRange(
10 | new string[]
11 | {
12 | "Core",
13 | "CoreUObject",
14 | "DeveloperSettings",
15 | "EditorSubsystem",
16 | "Engine",
17 | "FileUtilities",
18 | "HTTP",
19 | "Json",
20 | "JsonUtilities",
21 | "Localization",
22 | "LocalizationCommandletExecution",
23 | "MainFrame",
24 | "Projects",
25 | "Slate",
26 | "SlateCore",
27 | "UnrealEd",
28 | "WebBrowser",
29 |
30 | "Tolgee"
31 | }
32 | );
33 | }
34 | }
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Private/TolgeeLocalizationProvider.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeLocalizationProvider.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #include "TolgeeProviderLocalizationServiceCommand.h"
13 | #include "TolgeeProviderLocalizationServiceWorker.h"
14 | #include "TolgeeEditorSettings.h"
15 |
16 | FString GetTempSubDirectory()
17 | {
18 | return FPaths::ProjectSavedDir() / TEXT("Tolgee") / TEXT("Temp");
19 | }
20 |
21 | void FTolgeeLocalizationProvider::Init(bool bForceConnection)
22 | {
23 | }
24 |
25 | void FTolgeeLocalizationProvider::Close()
26 | {
27 | }
28 |
29 | const FName& FTolgeeLocalizationProvider::GetName() const
30 | {
31 | static const FName ProviderName = "Tolgee";
32 | return ProviderName;
33 | }
34 |
35 | const FText FTolgeeLocalizationProvider::GetDisplayName() const
36 | {
37 | return NSLOCTEXT("Tolgee", "TolgeeProviderName", "Tolgee");
38 | }
39 |
40 | FText FTolgeeLocalizationProvider::GetStatusText() const
41 | {
42 | checkf(false, TEXT("Not used in 5.5+"));
43 | return FText::GetEmpty();
44 | }
45 |
46 | bool FTolgeeLocalizationProvider::IsEnabled() const
47 | {
48 | return true;
49 | }
50 |
51 | bool FTolgeeLocalizationProvider::IsAvailable() const
52 | {
53 | return true;
54 | }
55 |
56 | ELocalizationServiceOperationCommandResult::Type FTolgeeLocalizationProvider::GetState(const TArray& InTranslationIds,
57 | TArray>& OutState,
58 | ELocalizationServiceCacheUsage::Type InStateCacheUsage)
59 | {
60 | return ELocalizationServiceOperationCommandResult::Succeeded;
61 | }
62 |
63 | ELocalizationServiceOperationCommandResult::Type FTolgeeLocalizationProvider::Execute(const TSharedRef& InOperation, const TArray& InTranslationIds, ELocalizationServiceOperationConcurrency::Type InConcurrency, const FLocalizationServiceOperationComplete& InOperationCompleteDelegate)
64 | {
65 | // Query to see if the we allow this operation
66 | TSharedPtr Worker = CreateWorker(InOperation->GetName());
67 | if (!Worker.IsValid())
68 | {
69 | // this operation is unsupported by this source control provider
70 | FFormatNamedArguments Arguments;
71 | Arguments.Add(TEXT("OperationName"), FText::FromName(InOperation->GetName()));
72 | Arguments.Add(TEXT("ProviderName"), FText::FromName(GetName()));
73 | FText Message(FText::Format(INVTEXT("Operation '{OperationName}' not supported by revision control provider '{ProviderName}'"), Arguments));
74 | FMessageLog("LocalizationService").Error(Message);
75 |
76 | (void)InOperationCompleteDelegate.ExecuteIfBound(InOperation, ELocalizationServiceOperationCommandResult::Failed);
77 | return ELocalizationServiceOperationCommandResult::Failed;
78 | }
79 |
80 | FTolgeeProviderLocalizationServiceCommand* Command = new FTolgeeProviderLocalizationServiceCommand(InOperation, Worker.ToSharedRef());
81 | Command->OperationCompleteDelegate = InOperationCompleteDelegate;
82 |
83 | // fire off operation
84 | if (InConcurrency == ELocalizationServiceOperationConcurrency::Synchronous)
85 | {
86 | Command->bAutoDelete = false;
87 |
88 | return ExecuteSynchronousCommand(*Command);
89 | }
90 |
91 | Command->bAutoDelete = true;
92 | return IssueCommand(*Command);
93 | }
94 |
95 | bool FTolgeeLocalizationProvider::CanCancelOperation(const TSharedRef& InOperation) const
96 | {
97 | return false;
98 | }
99 |
100 | void FTolgeeLocalizationProvider::CancelOperation(const TSharedRef& InOperation)
101 | {
102 | }
103 |
104 | void FTolgeeLocalizationProvider::Tick()
105 | {
106 | bool bStatesUpdated = false;
107 | for (int32 CommandIndex = 0; CommandIndex < CommandQueue.Num(); ++CommandIndex)
108 | {
109 | FTolgeeProviderLocalizationServiceCommand& Command = *CommandQueue[CommandIndex];
110 | if (Command.bExecuteProcessed)
111 | {
112 | // Remove command from the queue
113 | CommandQueue.RemoveAt(CommandIndex);
114 |
115 | // let command update the states of any files
116 | bStatesUpdated |= Command.Worker->UpdateStates();
117 |
118 | // dump any messages to output log
119 | OutputCommandMessages(Command);
120 |
121 | Command.ReturnResults();
122 |
123 | // commands that are left in the array during a tick need to be deleted
124 | if (Command.bAutoDelete)
125 | {
126 | // Only delete commands that are not running 'synchronously'
127 | delete &Command;
128 | }
129 |
130 | // only do one command per tick loop, as we dont want concurrent modification
131 | // of the command queue (which can happen in the completion delegate)
132 | break;
133 | }
134 | }
135 |
136 | // NOTE: Currently. the ILocalizationServiceProvider doesn't have a StateChanged delegate like the ISourceControlProvider, but it might get one in the future.
137 | //if (bStatesUpdated)
138 | //{
139 | // OnSourceControlStateChanged.Broadcast();
140 | //}
141 | }
142 |
143 | void FTolgeeLocalizationProvider::CustomizeSettingsDetails(IDetailCategoryBuilder& DetailCategoryBuilder) const
144 | {
145 | UTolgeeEditorSettings* MutableSettings = GetMutableDefault();
146 |
147 | FAddPropertyParams AddPropertyParams;
148 | AddPropertyParams.HideRootObjectNode(true);
149 | DetailCategoryBuilder.AddExternalObjectProperty({MutableSettings}, GET_MEMBER_NAME_CHECKED(UTolgeeEditorSettings, ApiUrl), EPropertyLocation::Common, AddPropertyParams);
150 | DetailCategoryBuilder.AddExternalObjectProperty({MutableSettings}, GET_MEMBER_NAME_CHECKED(UTolgeeEditorSettings, ApiKey), EPropertyLocation::Common, AddPropertyParams);
151 | }
152 |
153 | void FTolgeeLocalizationProvider::CustomizeTargetDetails(IDetailCategoryBuilder& DetailCategoryBuilder, TWeakObjectPtr LocalizationTarget) const
154 | {
155 | checkf(false, TEXT("Not used in 5.5+"));
156 | }
157 |
158 | void FTolgeeLocalizationProvider::CustomizeTargetToolbar(TSharedRef& MenuExtender, TWeakObjectPtr LocalizationTarget) const
159 | {
160 | FTolgeeLocalizationProvider* ThisProvider = const_cast(this);
161 |
162 | MenuExtender->AddToolBarExtension(
163 | "LocalizationService",
164 | EExtensionHook::First,
165 | nullptr,
166 | FToolBarExtensionDelegate::CreateRaw(ThisProvider, &FTolgeeLocalizationProvider::AddTargetToolbarButtons, LocalizationTarget)
167 | );
168 | }
169 |
170 | void FTolgeeLocalizationProvider::CustomizeTargetSetToolbar(TSharedRef& MenuExtender, TWeakObjectPtr LocalizationTargetSet) const
171 | {
172 | FTolgeeLocalizationProvider* ThisProvider = const_cast(this);
173 |
174 | MenuExtender->AddToolBarExtension(
175 | "LocalizationService",
176 | EExtensionHook::First,
177 | nullptr,
178 | FToolBarExtensionDelegate::CreateRaw(ThisProvider, &FTolgeeLocalizationProvider::AddTargetSetToolbarButtons, LocalizationTargetSet)
179 | );
180 | }
181 |
182 | void FTolgeeLocalizationProvider::AddTargetToolbarButtons(FToolBarBuilder& ToolbarBuilder, TWeakObjectPtr InLocalizationTarget)
183 | {
184 | if (InLocalizationTarget->IsMemberOfEngineTargetSet())
185 | {
186 | return;
187 | }
188 |
189 | ToolbarBuilder.AddToolBarButton(
190 | FUIAction(
191 | FExecuteAction::CreateRaw(this, &FTolgeeLocalizationProvider::ImportAllCulturesForTargetFromTolgee, InLocalizationTarget)
192 | ),
193 | NAME_None,
194 | NSLOCTEXT("Tolgee", "TolgeeImportTarget", "Tolgee Pull"),
195 | NSLOCTEXT("Tolgee", "TolgeeImportTargetTip", "Imports all cultures for this target from Tolgee"),
196 | FSlateIcon(FAppStyle::GetAppStyleSetName(), "LocalizationDashboard.ImportTextAllTargetsAllCultures")
197 | );
198 |
199 | ToolbarBuilder.AddToolBarButton(
200 | FUIAction(
201 | FExecuteAction::CreateRaw(this, &FTolgeeLocalizationProvider::ExportAllCulturesForTargetToTolgee, InLocalizationTarget)
202 | ),
203 | NAME_None,
204 | NSLOCTEXT("Tolgee", "TolgeeExportTarget", "Tolgee Push"),
205 | NSLOCTEXT("Tolgee", "TolgeeExportTargetTip", "Exports all cultures for this target to Tolgee"),
206 | FSlateIcon(FAppStyle::GetAppStyleSetName(), "LocalizationDashboard.ExportTextAllTargetsAllCultures")
207 | );
208 |
209 | ToolbarBuilder.AddComboButton(
210 | FUIAction(),
211 | FOnGetContent::CreateRaw(this, &FTolgeeLocalizationProvider::CreateProjectSettingsWidget, InLocalizationTarget),
212 | NSLOCTEXT("Tolgee", "TolgeeSettings", "Tolgee Settings"),
213 | NSLOCTEXT("Tolgee", "TolgeeSettings", "Tolgee Settings"),
214 | FSlateIcon(FAppStyle::GetAppStyleSetName(), "LocalizationDashboard.CompileTextAllTargetsAllCultures")
215 | );
216 | }
217 |
218 | void FTolgeeLocalizationProvider::AddTargetSetToolbarButtons(FToolBarBuilder& ToolbarBuilder, TWeakObjectPtr InLocalizationTargetSet)
219 | {
220 | if (InLocalizationTargetSet.IsValid() && InLocalizationTargetSet->TargetObjects.Num() > 0 && InLocalizationTargetSet->TargetObjects[0]->IsMemberOfEngineTargetSet())
221 | {
222 | return;
223 | }
224 |
225 | ToolbarBuilder.AddToolBarButton(
226 | FUIAction(
227 | FExecuteAction::CreateRaw(this, &FTolgeeLocalizationProvider::ImportAllTargetsForSetFromTolgee, InLocalizationTargetSet)
228 | ),
229 | NAME_None,
230 | NSLOCTEXT("Tolgee", "TolgeeImportSet", "Tolgee Pull"),
231 | NSLOCTEXT("Tolgee", "TolgeeImportSetTip", "Imports all targets for this set from Tolgee"),
232 | FSlateIcon(FAppStyle::GetAppStyleSetName(), "LocalizationDashboard.ImportTextAllTargetsAllCultures")
233 | );
234 |
235 | ToolbarBuilder.AddToolBarButton(
236 | FUIAction(
237 | FExecuteAction::CreateRaw(this, &FTolgeeLocalizationProvider::ExportAllTargetsForSetToTolgee, InLocalizationTargetSet)
238 | ),
239 | NAME_None,
240 | NSLOCTEXT("Tolgee", "TolgeeExportSet", "Tolgee Push"),
241 | NSLOCTEXT("Tolgee", "TolgeeExportSetTip", "Exports all targets for this set to Tolgee"),
242 | FSlateIcon(FAppStyle::GetAppStyleSetName(), "LocalizationDashboard.ExportTextAllTargetsAllCultures")
243 | );
244 |
245 | }
246 |
247 | void FTolgeeLocalizationProvider::ImportAllCulturesForTargetFromTolgee(TWeakObjectPtr LocalizationTarget)
248 | {
249 | // Delete old files if they exists so we don't accidentally export old data
250 | const FString AbsoluteFolderPath = FPaths::ConvertRelativePathToFull(GetTempSubDirectory() / LocalizationTarget->Settings.Name);
251 | IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
252 | PlatformFile.DeleteDirectoryRecursively(*AbsoluteFolderPath);
253 |
254 | TArray CultureStats = LocalizationTarget->Settings.SupportedCulturesStatistics;
255 | FScopedSlowTask SlowTask(CultureStats.Num(), INVTEXT("Downloading Files from Localization Service..."));
256 | for (FCultureStatistics CultureStat : CultureStats)
257 | {
258 | ILocalizationServiceProvider& Provider = ILocalizationServiceModule::Get().GetProvider();
259 | TSharedRef DownloadTargetFileOp = ILocalizationServiceOperation::Create();
260 | DownloadTargetFileOp->SetInTargetGuid(LocalizationTarget->Settings.Guid);
261 | DownloadTargetFileOp->SetInLocale(CultureStat.CultureName);
262 |
263 | // NOTE: For some reason the base FDownloadLocalizationTargetFile prefers relative paths, so we will make it relative
264 | FString Path = AbsoluteFolderPath / CultureStat.CultureName / LocalizationTarget->Settings.Name + ".po";
265 | FPaths::MakePathRelativeTo(Path, *FPaths::ProjectDir());
266 | DownloadTargetFileOp->SetInRelativeOutputFilePathAndName(Path);
267 |
268 | Provider.Execute(DownloadTargetFileOp);
269 | SlowTask.EnterProgressFrame(1, FText::Format(INVTEXT("Downloading {0}"), FText::FromString(CultureStat.CultureName)));
270 | }
271 |
272 | IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame"));
273 | const TSharedPtr& MainFrameParentWindow = MainFrameModule.GetParentWindow();
274 | LocalizationCommandletTasks::ImportTextForTarget(MainFrameParentWindow.ToSharedRef(), LocalizationTarget.Get(), AbsoluteFolderPath);
275 | UpdateTargetFromReports(LocalizationTarget);
276 | }
277 |
278 | void FTolgeeLocalizationProvider::ExportAllCulturesForTargetToTolgee(TWeakObjectPtr LocalizationTarget)
279 | {
280 | // Delete old files if they exists so we don't accidentally export old data
281 | const FString AbsoluteFolderPath = FPaths::ConvertRelativePathToFull(GetTempSubDirectory() / LocalizationTarget->Settings.Name);
282 | IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
283 | PlatformFile.DeleteDirectoryRecursively(*AbsoluteFolderPath);
284 |
285 | // Currently Unreal's format uses msgctx which is not supported by Tolgee, so we will use Crowdin format instead which doesn't use it.
286 | // TODO: Revisit after https://github.com/tolgee/tolgee-platform/issues/3053
287 | LocalizationTarget->Settings.ExportSettings.POFormat = EPortableObjectFormat::Crowdin;
288 |
289 | IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame"));
290 | const TSharedPtr& MainFrameParentWindow = MainFrameModule.GetParentWindow();
291 | LocalizationCommandletTasks::ExportTextForTarget(MainFrameParentWindow.ToSharedRef(), LocalizationTarget.Get(), AbsoluteFolderPath);
292 |
293 | TArray CultureStats = LocalizationTarget->Settings.SupportedCulturesStatistics;
294 | FScopedSlowTask SlowTask(CultureStats.Num(), INVTEXT("Uploading Files to Localization Service..."));
295 | for (FCultureStatistics CultureStat : CultureStats)
296 | {
297 | ILocalizationServiceProvider& Provider = ILocalizationServiceModule::Get().GetProvider();
298 | TSharedRef UploadFileOp = ILocalizationServiceOperation::Create();
299 | UploadFileOp->SetInTargetGuid(LocalizationTarget->Settings.Guid);
300 | UploadFileOp->SetInLocale(CultureStat.CultureName);
301 |
302 | // NOTE: For some reason the base FUploadLocalizationTargetFile prefers relative paths, so we will make it relative
303 | FString Path = AbsoluteFolderPath / CultureStat.CultureName / LocalizationTarget->Settings.Name + ".po";
304 | FPaths::MakePathRelativeTo(Path, *FPaths::ProjectDir());
305 | UploadFileOp->SetInRelativeInputFilePathAndName(Path);
306 |
307 | Provider.Execute(UploadFileOp);
308 | SlowTask.EnterProgressFrame(1, FText::Format(INVTEXT("Uploading {0}"), FText::FromString(CultureStat.CultureName)));
309 | }
310 | }
311 |
312 | void FTolgeeLocalizationProvider::ImportAllTargetsForSetFromTolgee(TWeakObjectPtr LocalizationTargetSet)
313 | {
314 | for (ULocalizationTarget* LocalizationTarget : LocalizationTargetSet->TargetObjects)
315 | {
316 | ImportAllCulturesForTargetFromTolgee(LocalizationTarget);
317 | }
318 | }
319 |
320 | void FTolgeeLocalizationProvider::ExportAllTargetsForSetToTolgee(TWeakObjectPtr LocalizationTargetSet)
321 | {
322 | for (ULocalizationTarget* LocalizationTarget : LocalizationTargetSet->TargetObjects)
323 | {
324 | ExportAllCulturesForTargetToTolgee(LocalizationTarget);
325 | }
326 | }
327 |
328 | TSharedRef FTolgeeLocalizationProvider::CreateProjectSettingsWidget(TWeakObjectPtr InLocalizationTarget)
329 | {
330 | const FGuid& TargetGuid = InLocalizationTarget->Settings.Guid;
331 |
332 | UTolgeeEditorSettings* MutableSettings = GetMutableDefault();
333 | FTolgeePerTargetSettings ProjectSettings = MutableSettings->PerTargetSettings.FindOrAdd(TargetGuid);
334 |
335 | FDetailsViewArgs DetailsViewArgs;
336 | DetailsViewArgs.bAllowSearch = false;
337 | DetailsViewArgs.bShowObjectLabel = false;
338 | DetailsViewArgs.bShowScrollBar = false;
339 |
340 | FStructureDetailsViewArgs StructureViewArgs;
341 |
342 | TSharedPtr> Struct = MakeShared>();
343 | Struct->InitializeAs(ProjectSettings);
344 |
345 | FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor");
346 | TSharedPtr StructureDetailsView = PropertyEditorModule.CreateStructureDetailView(DetailsViewArgs, StructureViewArgs, Struct);
347 |
348 | StructureDetailsView->GetOnFinishedChangingPropertiesDelegate().AddLambda([MutableSettings, Struct, TargetGuid](const FPropertyChangedEvent& PropertyChangedEvent)
349 | {
350 | MutableSettings->PerTargetSettings[TargetGuid] = *Struct->Cast();
351 | MutableSettings->SaveConfig();
352 | });
353 |
354 | return StructureDetailsView->GetWidget().ToSharedRef();
355 | }
356 |
357 | void FTolgeeLocalizationProvider::UpdateTargetFromReports(TWeakObjectPtr InLocalizationTarget)
358 | {
359 | // NOTE: This function seems to be copy pasted in a lot of places FLocalizationTargetDetailCustomization/SLocalizationTargetEditorCultureRow(original), FLocalizationTargetSetDetailCustomizationm, UpdateTargetFromReports
360 | ULocalizationTarget* LocalizationTarget = InLocalizationTarget.Get();
361 | LocalizationTarget->UpdateWordCountsFromCSV();
362 | LocalizationTarget->UpdateStatusFromConflictReport();
363 | }
364 |
365 | void FTolgeeLocalizationProvider::OutputCommandMessages(const FTolgeeProviderLocalizationServiceCommand& InCommand) const
366 | {
367 | FMessageLog LocalizationServiceLog("LocalizationService");
368 |
369 | for (int32 ErrorIndex = 0; ErrorIndex < InCommand.ErrorMessages.Num(); ++ErrorIndex)
370 | {
371 | LocalizationServiceLog.Error(FText::FromString(InCommand.ErrorMessages[ErrorIndex]));
372 | }
373 |
374 | for (int32 InfoIndex = 0; InfoIndex < InCommand.InfoMessages.Num(); ++InfoIndex)
375 | {
376 | LocalizationServiceLog.Info(FText::FromString(InCommand.InfoMessages[InfoIndex]));
377 | }
378 | }
379 |
380 | TSharedPtr FTolgeeLocalizationProvider::CreateWorker(const FName& InOperationName) const
381 | {
382 | if (const FGetTolgeeProviderLocalizationServiceWorker* Operation = WorkersMap.Find(InOperationName))
383 | {
384 | return Operation->Execute();
385 | }
386 |
387 | return nullptr;
388 | }
389 |
390 | ELocalizationServiceOperationCommandResult::Type FTolgeeLocalizationProvider::ExecuteSynchronousCommand(FTolgeeProviderLocalizationServiceCommand& InCommand)
391 | {
392 | ELocalizationServiceOperationCommandResult::Type Result = ELocalizationServiceOperationCommandResult::Failed;
393 |
394 | // Display the progress dialog if a string was provided
395 | {
396 | // Issue the command asynchronously...
397 | IssueCommand(InCommand);
398 |
399 | // ... then wait for its completion (thus making it synchronous)
400 | while (!InCommand.bExecuteProcessed)
401 | {
402 | // Tick the command queue and update progress.
403 | Tick();
404 |
405 | // Sleep for a bit so we don't busy-wait so much.
406 | FPlatformProcess::Sleep(0.01f);
407 | }
408 |
409 | // always do one more Tick() to make sure the command queue is cleaned up.
410 | Tick();
411 |
412 | if (InCommand.bCommandSuccessful)
413 | {
414 | Result = ELocalizationServiceOperationCommandResult::Succeeded;
415 | }
416 | }
417 |
418 | // Delete the command now (asynchronous commands are deleted in the Tick() method)
419 | check(!InCommand.bAutoDelete);
420 |
421 | // ensure commands that are not auto deleted do not end up in the command queue
422 | if (CommandQueue.Contains(&InCommand))
423 | {
424 | CommandQueue.Remove(&InCommand);
425 | }
426 | delete &InCommand;
427 |
428 | return Result;
429 | }
430 |
431 | ELocalizationServiceOperationCommandResult::Type FTolgeeLocalizationProvider::IssueCommand(FTolgeeProviderLocalizationServiceCommand& InCommand)
432 | {
433 | if (GThreadPool != nullptr)
434 | {
435 | // Queue this to our worker thread(s) for resolving
436 | GThreadPool->AddQueuedWork(&InCommand);
437 | CommandQueue.Add(&InCommand);
438 | return ELocalizationServiceOperationCommandResult::Succeeded;
439 | }
440 | else
441 | {
442 | FText Message(INVTEXT("There are no threads available to process the localization service provider command."));
443 | FMessageLog("LocalizationService").Error(Message);
444 |
445 | InCommand.bCommandSuccessful = false;
446 | return InCommand.ReturnResults();
447 | }
448 | }
449 |
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Private/TolgeeProvider.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeProvider.h"
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include "TolgeeLog.h"
10 | #include "TolgeeProviderLocalizationServiceOperations.h"
11 |
12 | void FTolgeeProviderModule::StartupModule()
13 | {
14 | DenyEditorSettings();
15 |
16 | IModularFeatures::Get().RegisterModularFeature("LocalizationService", &TolgeeLocalizationProvider);
17 |
18 | TolgeeLocalizationProvider.RegisterWorker("UploadLocalizationTargetFile");
19 | TolgeeLocalizationProvider.RegisterWorker("DownloadLocalizationTargetFile");
20 |
21 | // Currently, there is a bug in 5.5 where the saved settings are not getting applied
22 | // because FLocalizationServiceSettings::LoadSettings reads from the `CoalescedSourceConfigs` config
23 | ReApplySettings();
24 | }
25 |
26 | void FTolgeeProviderModule::ShutdownModule()
27 | {
28 | IModularFeatures::Get().UnregisterModularFeature("LocalizationService", &TolgeeLocalizationProvider);
29 | }
30 |
31 | void FTolgeeProviderModule::ReApplySettings()
32 | {
33 | const FString& IniFile = LocalizationServiceHelpers::GetSettingsIni();
34 | if (FConfigFile* ConfigFile = GConfig->Find(IniFile))
35 | {
36 | ConfigFile->Read(IniFile);
37 | }
38 |
39 | ILocalizationServiceModule& LocalizationService = ILocalizationServiceModule::Get();
40 | FString SavedProvider;
41 | GConfig->GetString(TEXT("LocalizationService.LocalizationServiceSettings"), TEXT("Provider"), SavedProvider, IniFile);
42 | if (!SavedProvider.IsEmpty() && LocalizationService.GetProvider().GetName() != FName(SavedProvider))
43 | {
44 | UE_LOG(LogTolgee, Display, TEXT("Applying saved Provider: %s"), *SavedProvider);
45 | LocalizationService.SetProvider(FName(SavedProvider));
46 | }
47 |
48 | bool bSavedUseGlobalSettings = false;
49 | const FString& GlobalIniFile = LocalizationServiceHelpers::GetGlobalSettingsIni();
50 | GConfig->GetBool(TEXT("LocalizationService.LocalizationServiceSettings"), TEXT("UseGlobalSettings"), bSavedUseGlobalSettings, GlobalIniFile);
51 | if (LocalizationService.GetUseGlobalSettings() != bSavedUseGlobalSettings)
52 | {
53 | LocalizationService.SetUseGlobalSettings(bSavedUseGlobalSettings);
54 | }
55 | }
56 |
57 | void FTolgeeProviderModule::DenyEditorSettings()
58 | {
59 | const FString TolgeeProviderSettingsSection = TEXT("/Script/TolgeeProvider.TolgeeProviderSettings");
60 | UProjectPackagingSettings* ProjectPackagingSettings = GetMutableDefault();
61 | if (!ProjectPackagingSettings->IniSectionDenylist.Contains(TolgeeProviderSettingsSection))
62 | {
63 | UE_LOG(LogTolgee, Display, TEXT("Adding %s to ProjectPackagingSettings.IniSectionDenylist"), *TolgeeProviderSettingsSection);
64 | ProjectPackagingSettings->IniSectionDenylist.Add(TolgeeProviderSettingsSection);
65 | ProjectPackagingSettings->SaveConfig();
66 | }
67 | }
68 |
69 | IMPLEMENT_MODULE(FTolgeeProviderModule, TolgeeProvider)
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Private/TolgeeProviderLocalizationServiceCommand.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeProviderLocalizationServiceCommand.h"
4 |
5 | #include "TolgeeProviderLocalizationServiceWorker.h"
6 |
7 | FTolgeeProviderLocalizationServiceCommand::FTolgeeProviderLocalizationServiceCommand(const TSharedRef& InOperation, const TSharedRef& InWorker, const FLocalizationServiceOperationComplete& InOperationCompleteDelegate)
8 | : Operation(InOperation)
9 | , Worker(InWorker)
10 | , OperationCompleteDelegate(InOperationCompleteDelegate)
11 | , bExecuteProcessed(0)
12 | , bCommandSuccessful(false)
13 | , bAutoDelete(true)
14 | , Concurrency(ELocalizationServiceOperationConcurrency::Synchronous)
15 | {
16 | check(IsInGameThread());
17 | }
18 |
19 |
20 | bool FTolgeeProviderLocalizationServiceCommand::DoWork()
21 | {
22 | bCommandSuccessful = Worker->Execute(*this);
23 | FPlatformAtomics::InterlockedExchange(&bExecuteProcessed, 1);
24 |
25 | return bCommandSuccessful;
26 | }
27 |
28 | void FTolgeeProviderLocalizationServiceCommand::Abandon()
29 | {
30 | FPlatformAtomics::InterlockedExchange(&bExecuteProcessed, 1);
31 | }
32 |
33 | void FTolgeeProviderLocalizationServiceCommand::DoThreadedWork()
34 | {
35 | Concurrency = ELocalizationServiceOperationConcurrency::Asynchronous;
36 | DoWork();
37 | }
38 |
39 | ELocalizationServiceOperationCommandResult::Type FTolgeeProviderLocalizationServiceCommand::ReturnResults()
40 | {
41 | // NOTE: Everything in this class is copied from the SourceControl implementation (Other Localization providers copied it too).
42 | // Except this function where the Operation doesn't support adding messages, so we output them directly
43 |
44 | FMessageLog LocalizationServiceLog("LocalizationService");
45 |
46 | for (FString& String : InfoMessages)
47 | {
48 | LocalizationServiceLog.Error(FText::FromString(String));
49 | }
50 | for (FString& String : ErrorMessages)
51 | {
52 | LocalizationServiceLog.Info(FText::FromString(String));
53 | }
54 |
55 | // run the completion delegate if we have one bound
56 | ELocalizationServiceOperationCommandResult::Type Result = bCommandSuccessful ? ELocalizationServiceOperationCommandResult::Succeeded : ELocalizationServiceOperationCommandResult::Failed;
57 | (void)OperationCompleteDelegate.ExecuteIfBound(Operation, Result);
58 |
59 | return Result;
60 | }
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Private/TolgeeProviderLocalizationServiceOperations.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeProviderLocalizationServiceOperations.h"
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include "TolgeeProviderLocalizationServiceCommand.h"
10 | #include "TolgeeEditorSettings.h"
11 | #include "TolgeeLog.h"
12 | #include "TolgeeProviderUtils.h"
13 | #include "TolgeeUtils.h"
14 |
15 | FName FTolgeeProviderUploadFileWorker::GetName() const
16 | {
17 | return "UploadFileWorker";
18 | }
19 |
20 | bool FTolgeeProviderUploadFileWorker::Execute(FTolgeeProviderLocalizationServiceCommand& InCommand)
21 | {
22 | TSharedPtr UploadFileOp = StaticCastSharedRef(InCommand.Operation);
23 | if (!UploadFileOp || !UploadFileOp.IsValid())
24 | {
25 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderUploadFileWorker: Invalid operation"));
26 |
27 | InCommand.bCommandSuccessful = false;
28 | return InCommand.bCommandSuccessful;
29 | }
30 |
31 | const FString FilePathAndName = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir() / UploadFileOp->GetInRelativeInputFilePathAndName());
32 | FString FileContents;
33 | if (!FFileHelper::LoadFileToString(FileContents, *FilePathAndName))
34 | {
35 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderUploadFileWorker: Cannot load file %s"), *FilePathAndName);
36 |
37 | InCommand.bCommandSuccessful = false;
38 | return InCommand.bCommandSuccessful;
39 | }
40 |
41 | const FGuid TargetGuid = UploadFileOp->GetInTargetGuid();
42 | const FString Locale = UploadFileOp->GetInLocale();
43 |
44 | //NOTE: This is currently not set by the Localization dashbard, when it becomes available send it as "removeOtherKeys"
45 | const bool bRemoveMissingKeys = !UploadFileOp->GetPreserveAllText();
46 |
47 | TSharedRef FileMapping = MakeShared();
48 | const FString FileName = FPaths::GetCleanFilename(FilePathAndName);
49 | FileMapping->SetStringField(TEXT("fileName"), FileName);
50 |
51 | TSharedRef Params = MakeShared();
52 | Params->SetBoolField(TEXT("convertPlaceholdersToIcu"), false);
53 | Params->SetBoolField(TEXT("createNewKeys"), true);
54 | Params->SetArrayField(TEXT("fileMappings"), {MakeShared(FileMapping)});
55 | Params->SetStringField(TEXT("forceMode"), TEXT("KEEP"));
56 | Params->SetBoolField(TEXT("overrideKeyDescriptions"), true);
57 | Params->SetBoolField(TEXT("removeOtherKeys"), true);
58 | Params->SetArrayField(TEXT("tagNewKeys"), {MakeShared(TEXT("UnrealSDK"))});
59 |
60 | FString ParamsContents;
61 | TSharedRef> Writer = TJsonWriterFactory<>::Create(&ParamsContents);
62 | FJsonSerializer::Serialize(Params, Writer);
63 |
64 | const UTolgeeEditorSettings* ProviderSettings = GetDefault();
65 | const FTolgeePerTargetSettings* ProjectSettings = ProviderSettings->PerTargetSettings.Find(TargetGuid);
66 | const FString Url = FString::Printf(TEXT("%s/v2/projects/%s/single-step-import"), *ProviderSettings->ApiUrl, *ProjectSettings->ProjectId);
67 |
68 | FHttpRequestRef HttpRequest = FHttpModule::Get().CreateRequest();
69 | HttpRequest->SetURL(Url);
70 | HttpRequest->SetVerb(TEXT("POST"));
71 | HttpRequest->SetHeader(TEXT("X-API-Key"), ProviderSettings->ApiKey);
72 | TolgeeUtils::AddSdkHeaders(HttpRequest);
73 |
74 | const FString Boundary = "---------------------------" + FString::FromInt(FDateTime::Now().GetTicks());
75 | TolgeeProviderUtils::AddMultiRequestHeader(HttpRequest, Boundary);
76 | TolgeeProviderUtils::AddMultiRequestPart(HttpRequest, Boundary, TEXT("name=\"params\""), ParamsContents);
77 | TolgeeProviderUtils::AddMultiRequestPart(HttpRequest, Boundary, TEXT("name=\"files\"; filename=\"test.po\""), FileContents);
78 | TolgeeProviderUtils::FinishMultiRequest(HttpRequest, Boundary);
79 |
80 | HttpRequest->SetDelegateThreadPolicy(EHttpRequestDelegateThreadPolicy::CompleteOnHttpThread);
81 |
82 | HttpRequest->ProcessRequestUntilComplete();
83 |
84 | FHttpResponsePtr Response = HttpRequest->GetResponse();
85 | if (!Response)
86 | {
87 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderUploadFileWorker: Failed to upload file %s"), *FilePathAndName);
88 |
89 | InCommand.bCommandSuccessful = false;
90 | return InCommand.bCommandSuccessful;
91 | }
92 | if (!EHttpResponseCodes::IsOk(Response->GetResponseCode()))
93 | {
94 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderUploadFileWorker: Failed to upload file %s. Response code: %d"), *FilePathAndName, Response->GetResponseCode());
95 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderUploadFileWorker: Response: %s"), *Response->GetContentAsString());
96 |
97 | InCommand.bCommandSuccessful = false;
98 | return InCommand.bCommandSuccessful;
99 | }
100 |
101 | UE_LOG(LogTolgee, Display, TEXT("FTolgeeProviderUploadFileWorker: Successfully uploaded file %s. Content: %s"), *FilePathAndName, *Response->GetContentAsString());
102 |
103 | InCommand.bCommandSuccessful = true;
104 | return InCommand.bCommandSuccessful;
105 | }
106 |
107 | bool FTolgeeProviderUploadFileWorker::UpdateStates() const
108 | {
109 | return true;
110 | }
111 |
112 | FName FTolgeeProviderDownloadFileWorker::GetName() const
113 | {
114 | return "DownloadFileWorker";
115 | }
116 |
117 | bool FTolgeeProviderDownloadFileWorker::Execute(FTolgeeProviderLocalizationServiceCommand& InCommand)
118 | {
119 | TSharedPtr DownloadFileOp = StaticCastSharedRef(InCommand.Operation);
120 | if (!DownloadFileOp || !DownloadFileOp.IsValid())
121 | {
122 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderDownloadFileWorker: Invalid operation"));
123 |
124 | InCommand.bCommandSuccessful = false;
125 | return InCommand.bCommandSuccessful;
126 | }
127 |
128 | const FString FilePathAndName = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir() / DownloadFileOp->GetInRelativeOutputFilePathAndName());
129 | const FGuid TargetGuid = DownloadFileOp->GetInTargetGuid();
130 | const FString Locale = DownloadFileOp->GetInLocale();
131 |
132 | const UTolgeeEditorSettings* ProviderSettings = GetDefault();
133 | const FTolgeePerTargetSettings* ProjectSettings = ProviderSettings->PerTargetSettings.Find(TargetGuid);
134 |
135 | const FString Url = FString::Printf(TEXT("%s/v2/projects/%s/export"), *ProviderSettings->ApiUrl, *ProjectSettings->ProjectId);
136 |
137 | FHttpRequestRef HttpRequest = FHttpModule::Get().CreateRequest();
138 | HttpRequest->SetURL(Url);
139 | HttpRequest->SetVerb(TEXT("POST"));
140 | HttpRequest->SetHeader(TEXT("X-API-Key"), ProviderSettings->ApiKey);
141 | HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
142 | TolgeeUtils::AddSdkHeaders(HttpRequest);
143 |
144 | TSharedRef Body = MakeShared();
145 | Body->SetBoolField(TEXT("escapeHtml"), false);
146 | Body->SetStringField(TEXT("format"), "PO");
147 | Body->SetBoolField(TEXT("supportArrays"), false);
148 | Body->SetBoolField(TEXT("zip"), false);
149 | Body->SetArrayField(TEXT("languages"), {MakeShared(Locale)});
150 |
151 | FString RequestBody;
152 | TSharedRef> Writer = TJsonWriterFactory<>::Create(&RequestBody);
153 | FJsonSerializer::Serialize(Body, Writer);
154 |
155 | HttpRequest->SetContentAsString(RequestBody);
156 | HttpRequest->SetDelegateThreadPolicy(EHttpRequestDelegateThreadPolicy::CompleteOnHttpThread);
157 |
158 | HttpRequest->ProcessRequestUntilComplete();
159 |
160 | FHttpResponsePtr Response = HttpRequest->GetResponse();
161 | if (!Response)
162 | {
163 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderUploadFileWorker: Failed to download file %s"), *FilePathAndName);
164 |
165 | InCommand.bCommandSuccessful = false;
166 | return InCommand.bCommandSuccessful;
167 | }
168 | if (!EHttpResponseCodes::IsOk(Response->GetResponseCode()))
169 | {
170 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderUploadFileWorker: Failed to download file %s. Response code: %d"), *FilePathAndName, Response->GetResponseCode());
171 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderUploadFileWorker: Response: %s"), *Response->GetContentAsString());
172 |
173 | InCommand.bCommandSuccessful = false;
174 | return InCommand.bCommandSuccessful;
175 | }
176 |
177 | if (!FFileHelper::SaveStringToFile(Response->GetContentAsString(), *FilePathAndName, FFileHelper::EEncodingOptions::ForceUnicode))
178 | {
179 | UE_LOG(LogTolgee, Error, TEXT("FTolgeeProviderDownloadFileWorker: Failed to write file %s"), *FilePathAndName);
180 |
181 | InCommand.bCommandSuccessful = false;
182 | return InCommand.bCommandSuccessful;
183 | }
184 |
185 | UE_LOG(LogTolgee, Display, TEXT("FTolgeeProviderUploadFileWorker: Successfully downloaded file %s. Content: %s"), *FilePathAndName, *Response->GetContentAsString());
186 |
187 | InCommand.bCommandSuccessful = true;
188 | return InCommand.bCommandSuccessful;
189 | }
190 |
191 | bool FTolgeeProviderDownloadFileWorker::UpdateStates() const
192 | {
193 | return true;
194 | }
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Private/TolgeeProviderUtils.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #include "TolgeeProviderUtils.h"
4 |
5 | void TolgeeProviderUtils::AddMultiRequestHeader(const FHttpRequestRef& HttpRequest, const FString& Boundary)
6 | {
7 | HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("multipart/form-data; boundary =" + Boundary));
8 | }
9 |
10 | void TolgeeProviderUtils::AddMultiRequestPart(const FHttpRequestRef& HttpRequest, const FString& Boundary, const FString& ExtraHeaders, const FString& Value)
11 | {
12 | const FString BoundaryBegin = FString(TEXT("--")) + Boundary + FString(TEXT("\r\n"));
13 |
14 | FString Data = GetRequestContent(HttpRequest);
15 | Data += FString(TEXT("\r\n"))
16 | + BoundaryBegin
17 | + FString(TEXT("Content-Disposition: form-data;"))
18 | + ExtraHeaders
19 | + FString(TEXT("\r\n\r\n"))
20 | + Value;
21 |
22 | HttpRequest->SetContentAsString(Data);
23 | }
24 |
25 | void TolgeeProviderUtils::FinishMultiRequest(const FHttpRequestRef& HttpRequest, const FString& Boundary)
26 | {
27 | const FString BoundaryEnd = FString(TEXT("\r\n--")) + Boundary + FString(TEXT("--\r\n"));
28 |
29 | FString Data = GetRequestContent(HttpRequest);
30 | Data.Append(BoundaryEnd);
31 |
32 | HttpRequest->SetContentAsString(Data);
33 | }
34 |
35 | FString TolgeeProviderUtils::GetRequestContent(const FHttpRequestRef& HttpRequest)
36 | {
37 | TArray Content = HttpRequest->GetContent();
38 |
39 | FString Result;
40 | FFileHelper::BufferToString(Result, Content.GetData(), Content.Num());
41 |
42 | return Result;
43 | }
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Public/TolgeeLocalizationProvider.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "TolgeeProviderLocalizationServiceWorker.h"
6 |
7 | #include
8 |
9 | class FTolgeeProviderLocalizationServiceCommand;
10 |
11 | DECLARE_DELEGATE_RetVal(FTolgeeProviderLocalizationServiceWorkerRef, FGetTolgeeProviderLocalizationServiceWorker)
12 |
13 | class FTolgeeLocalizationProvider final : public ILocalizationServiceProvider
14 | {
15 | public:
16 | // ~Begin ILocalizationServiceProvider interface
17 | virtual void Init(bool bForceConnection = true) override;
18 | virtual void Close() override;
19 | virtual const FName& GetName() const override;
20 | virtual const FText GetDisplayName() const override;
21 | virtual FText GetStatusText() const override;
22 | virtual bool IsEnabled() const override;
23 | virtual bool IsAvailable() const override;
24 | virtual ELocalizationServiceOperationCommandResult::Type GetState(const TArray& InTranslationIds, TArray>& OutState, ELocalizationServiceCacheUsage::Type InStateCacheUsage) override;
25 | virtual ELocalizationServiceOperationCommandResult::Type Execute(const TSharedRef& InOperation, const TArray& InTranslationIds, ELocalizationServiceOperationConcurrency::Type InConcurrency = ELocalizationServiceOperationConcurrency::Synchronous, const FLocalizationServiceOperationComplete& InOperationCompleteDelegate = FLocalizationServiceOperationComplete()) override;
26 | virtual bool CanCancelOperation(const TSharedRef& InOperation) const override;
27 | virtual void CancelOperation(const TSharedRef& InOperation) override;
28 | virtual void Tick() override;
29 | virtual void CustomizeSettingsDetails(IDetailCategoryBuilder& DetailCategoryBuilder) const override;
30 | virtual void CustomizeTargetDetails(IDetailCategoryBuilder& DetailCategoryBuilder, TWeakObjectPtr LocalizationTarget) const override;
31 | virtual void CustomizeTargetToolbar(TSharedRef& MenuExtender, TWeakObjectPtr LocalizationTarget) const override;
32 | virtual void CustomizeTargetSetToolbar(TSharedRef& MenuExtender, TWeakObjectPtr LocalizationTargetSet) const override;
33 | // ~End ILocalizationServiceProvider interface
34 |
35 | /**
36 | * Add the buttons to the toolbar for this localization target
37 | */
38 | void AddTargetToolbarButtons(FToolBarBuilder& ToolbarBuilder, TWeakObjectPtr InLocalizationTarget);
39 | /**
40 | * Add the buttons to the toolbar for this set of localization targets
41 | */
42 | void AddTargetSetToolbarButtons(FToolBarBuilder& ToolbarBuilder, TWeakObjectPtr InLocalizationTargetSet);
43 | /**
44 | * Download and import all translations for all cultures for the specified target from Tolgee
45 | */
46 | void ImportAllCulturesForTargetFromTolgee(TWeakObjectPtr LocalizationTarget);
47 | /**
48 | * Export and upload all cultures for a localization target to Tolgee
49 | */
50 | void ExportAllCulturesForTargetToTolgee(TWeakObjectPtr LocalizationTarget);
51 | /**
52 | * Download and import all translations for all cultures for all targets for the specified target set from Tolgee
53 | */
54 | void ImportAllTargetsForSetFromTolgee(TWeakObjectPtr LocalizationTargetSet);
55 | /**
56 | * Export and upload all cultures for all targets for a localization target set to Tolgee
57 | */
58 | void ExportAllTargetsForSetToTolgee(TWeakObjectPtr LocalizationTargetSet);
59 | /**
60 | * Create a widget to configure the target's settings.
61 | * NOTE: This spawns a widget to edit the matching sub-property of the provider settings
62 | */
63 | TSharedRef CreateProjectSettingsWidget(TWeakObjectPtr InLocalizationTarget);
64 | /**
65 | * Refreshes the localization dashboard UI, specifically the word counter
66 | */
67 | void UpdateTargetFromReports(TWeakObjectPtr InLocalizationTarget);
68 | /**
69 | * Sends messages from the command to the output log
70 | */
71 | void OutputCommandMessages(const FTolgeeProviderLocalizationServiceCommand& InCommand) const;
72 | /**
73 | * Helper function to register a worker type with this provider
74 | */
75 | template
76 | void RegisterWorker(const FName& InName);
77 | /**
78 | * Create work instances for the specified operation
79 | */
80 | TSharedPtr CreateWorker(const FName& InOperationName) const;
81 | /**
82 | * Execute the command synchronously
83 | */
84 | ELocalizationServiceOperationCommandResult::Type ExecuteSynchronousCommand(FTolgeeProviderLocalizationServiceCommand& InCommand);
85 | /**
86 | * Execute the command asynchronously
87 | */
88 | ELocalizationServiceOperationCommandResult::Type IssueCommand(FTolgeeProviderLocalizationServiceCommand& InCommand);
89 |
90 | TMap WorkersMap;
91 |
92 | TArray CommandQueue;
93 | };
94 |
95 | template
96 | void FTolgeeLocalizationProvider::RegisterWorker(const FName& InName)
97 | {
98 | FGetTolgeeProviderLocalizationServiceWorker Delegate = FGetTolgeeProviderLocalizationServiceWorker::CreateLambda([]()
99 | {
100 | return MakeShared();
101 | });
102 |
103 | WorkersMap.Add(InName, Delegate);
104 | }
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Public/TolgeeProvider.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | #include "TolgeeLocalizationProvider.h"
8 |
9 | /**
10 | * Module responsible for introducing the Tolgee implementation of the LocalizationServiceProvider.
11 | */
12 | class FTolgeeProviderModule : public IModuleInterface
13 | {
14 | // ~Begin IModuleInterface interface
15 | virtual void StartupModule() override;
16 | virtual void ShutdownModule() override;
17 | // ~End IModuleInterface interface
18 |
19 | /**
20 | * Re-applys the settings from LocalizationService.LocalizationServiceSettings ini file.
21 | */
22 | void ReApplySettings();
23 | /**
24 | * Ensures editor settings are not packaged in the final game.
25 | */
26 | void DenyEditorSettings();
27 | /**
28 | * LocalizationServiceProvider instance for Tolgee.
29 | */
30 | FTolgeeLocalizationProvider TolgeeLocalizationProvider;
31 | };
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Public/TolgeeProviderLocalizationServiceCommand.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 | #include
7 |
8 | class ITolgeeProviderLocalizationServiceWorker;
9 |
10 | class FTolgeeProviderLocalizationServiceCommand : public IQueuedWork
11 | {
12 | public:
13 | FTolgeeProviderLocalizationServiceCommand(const TSharedRef& InOperation, const TSharedRef& InWorker, const FLocalizationServiceOperationComplete& InOperationCompleteDelegate = FLocalizationServiceOperationComplete());
14 |
15 | /**
16 | * This function handles the core threaded operations.
17 | * All tasks associated with this queued object should be executed within this method.
18 | */
19 | bool DoWork();
20 |
21 | /**
22 | * Notifies queued work of abandonment for cleanup. Invoked only if abandoned before completion.
23 | * The object must delete itself using its allocated heap.
24 | */
25 | virtual void Abandon() override;
26 |
27 | /**
28 | * Used to signal the object to clean up, but only after it has completed its work.
29 | */
30 | virtual void DoThreadedWork() override;
31 |
32 | /**
33 | * Save any results and call any registered callbacks.
34 | */
35 | ELocalizationServiceOperationCommandResult::Type ReturnResults();
36 |
37 | /**
38 | * Operation we want to perform - contains outward-facing parameters & results
39 | */
40 | TSharedRef Operation;
41 |
42 | /**
43 | * The object that will actually do the work
44 | */
45 | TSharedRef Worker;
46 |
47 | /**
48 | * Delegate to notify when this operation completes
49 | */
50 | FLocalizationServiceOperationComplete OperationCompleteDelegate;
51 |
52 | /**
53 | * If true, this command has been processed by the Localization service thread
54 | */
55 | volatile int32 bExecuteProcessed;
56 |
57 | /**
58 | * If true, the Localization service command succeeded
59 | */
60 | bool bCommandSuccessful;
61 |
62 | /**
63 | * If true, this command will be automatically cleaned up in Tick()
64 | */
65 | bool bAutoDelete;
66 |
67 | /**
68 | * Whether we are running multi-treaded or not
69 | */
70 | ELocalizationServiceOperationConcurrency::Type Concurrency;
71 |
72 | /**
73 | * Info and/or warning message storage
74 | */
75 | TArray InfoMessages;
76 |
77 | /**
78 | * Potential error message storage
79 | */
80 | TArray ErrorMessages;
81 | };
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Public/TolgeeProviderLocalizationServiceOperations.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "TolgeeProviderLocalizationServiceWorker.h"
6 |
7 | class FTolgeeProviderUploadFileWorker : public ITolgeeProviderLocalizationServiceWorker
8 | {
9 | // ~Begin ITolgeeProviderLocalizationServiceWorker
10 | virtual FName GetName() const override;
11 | virtual bool Execute(FTolgeeProviderLocalizationServiceCommand& InCommand) override;
12 | virtual bool UpdateStates() const override;
13 | // ~End ITolgeeProviderLocalizationServiceWorker
14 | };
15 |
16 | class FTolgeeProviderDownloadFileWorker : public ITolgeeProviderLocalizationServiceWorker
17 | {
18 | // ~Begin ITolgeeProviderLocalizationServiceWorker
19 | virtual FName GetName() const override;
20 | virtual bool Execute(FTolgeeProviderLocalizationServiceCommand& InCommand) override;
21 | virtual bool UpdateStates() const override;
22 | // ~End ITolgeeProviderLocalizationServiceWorker
23 | };
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Public/TolgeeProviderLocalizationServiceWorker.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | class FTolgeeProviderLocalizationServiceCommand;
6 |
7 | /**
8 | * Base class for worker implementation that will be run to perform the commands.
9 | */
10 | class ITolgeeProviderLocalizationServiceWorker
11 | {
12 | public:
13 | virtual ~ITolgeeProviderLocalizationServiceWorker() = default;
14 |
15 | /**
16 | * Used to uniquely identify the worker type.
17 | */
18 | virtual FName GetName() const = 0;
19 | /**
20 | * Used to perform any work that is necessary to complete the command.
21 | * NOTE: Make sure you block the execution until the command is completed.
22 | */
23 | virtual bool Execute(FTolgeeProviderLocalizationServiceCommand& InCommand) = 0;
24 | /**
25 | * Used to update the state of localization items after the command is completed.
26 | * NOTE: This will always run on the main thread.
27 | */
28 | virtual bool UpdateStates() const = 0;
29 | };
30 |
31 | using FTolgeeProviderLocalizationServiceWorkerRef = TSharedRef;
--------------------------------------------------------------------------------
/Source/TolgeeProvider/Public/TolgeeProviderUtils.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include
6 |
7 | namespace TolgeeProviderUtils
8 | {
9 | /**
10 | * Adds the multi-part request header to the HTTP request.
11 | */
12 | void AddMultiRequestHeader(const FHttpRequestRef& HttpRequest, const FString& Boundary);
13 | /**
14 | * Adds a multi-part request part to the HTTP with the given content based on the boundary and extra headers.
15 | */
16 | void AddMultiRequestPart(const FHttpRequestRef& HttpRequest, const FString& Boundary, const FString& ExtraHeaders, const FString& Value);
17 | /**
18 | * Finishes the multi-part request by appending the boundary end to the HTTP request.
19 | */
20 | void FinishMultiRequest(const FHttpRequestRef& HttpRequest, const FString& Boundary);
21 | /**
22 | * Gets the request content from the HTTP request as a string.
23 | */
24 | FString GetRequestContent(const FHttpRequestRef& HttpRequest);
25 | }
--------------------------------------------------------------------------------
/Source/TolgeeProvider/TolgeeProvider.Build.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2 |
3 | using UnrealBuildTool;
4 |
5 | public class TolgeeProvider : ModuleRules
6 | {
7 | public TolgeeProvider(ReadOnlyTargetRules Target) : base(Target)
8 | {
9 | PublicDependencyModuleNames.AddRange(
10 | new string[]
11 | {
12 | "Core",
13 | "CoreUObject",
14 | "DeveloperToolSettings",
15 | "DeveloperSettings",
16 | "Engine",
17 | "HTTP",
18 | "Json",
19 | "Localization",
20 | "LocalizationCommandletExecution",
21 | "LocalizationService",
22 | "Slate",
23 | "SlateCore",
24 |
25 | "Tolgee",
26 | "TolgeeEditor",
27 | }
28 | );
29 | }
30 | }
--------------------------------------------------------------------------------
/Tolgee.uplugin:
--------------------------------------------------------------------------------
1 | {
2 | "FileVersion": 3,
3 | "VersionName": "0.0",
4 | "EngineVersion": "5.5",
5 | "FriendlyName": "Tolgee",
6 | "Description": "Localization tool which makes the localization process simple. Easy to integrate to React, Angular and other applications.",
7 | "Category": "Localization",
8 | "CreatedBy": "Tolgee",
9 | "CreatedByURL": "https://tolgee.io/",
10 | "DocsURL": "https://tolgee.io/integrations/unreal",
11 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/2757e202f8f3408bbf66f65d26223398",
12 | "SupportURL": "https://tolg.ee/slack",
13 | "EnabledByDefault": false,
14 | "CanContainContent": false,
15 | "Modules": [
16 | {
17 | "Name": "Tolgee",
18 | "Type": "Runtime",
19 | "LoadingPhase": "Default",
20 | "WhitelistPlatforms": [
21 | "Android",
22 | "IOS",
23 | "Linux",
24 | "Mac",
25 | "TVOS",
26 | "Win64"
27 | ]
28 | },
29 | {
30 | "Name": "TolgeeEditor",
31 | "Type": "Editor",
32 | "LoadingPhase": "Default",
33 | "WhitelistPlatforms": [
34 | "Linux",
35 | "Mac",
36 | "Win64"
37 | ]
38 | },
39 | {
40 | "Name": "TolgeeProvider",
41 | "Type": "Editor",
42 | "LoadingPhase": "Default",
43 | "WhitelistPlatforms": [
44 | "Win64",
45 | "Mac",
46 | "Linux"
47 | ]
48 | }
49 | ],
50 | "Plugins": [
51 | {
52 | "Name": "WebBrowserWidget",
53 | "Enabled": true
54 | }
55 | ]
56 | }
--------------------------------------------------------------------------------