1+ // Copyright (c) Tolgee 2022-2025. All Rights Reserved.
2+
3+ #include " TolgeeLocalizationInjectorSubsystem.h"
4+
5+ #include < Async/Async.h>
6+ #include < Engine/World.h>
7+ #include < Internationalization/TextLocalizationResource.h>
8+
9+ #if WITH_LOCALIZATION_MODULE
10+ #include < PortableObjectFormatDOM.h>
11+ #include < PortableObjectPipeline.h>
12+ #endif
13+
14+ #include " TolgeeLog.h"
15+ #include " TolgeeTextSource.h"
16+
17+ void UTolgeeLocalizationInjectorSubsystem::OnGameInstanceStart (UGameInstance* GameInstance)
18+ {
19+ }
20+
21+ void UTolgeeLocalizationInjectorSubsystem::OnGameInstanceEnd (bool bIsSimulating)
22+ {
23+ }
24+
25+ void UTolgeeLocalizationInjectorSubsystem::GetLocalizedResources (const ELocalizationLoadFlags InLoadFlags, TArrayView<const FString> InPrioritizedCultures, FTextLocalizationResource& InOutNativeResource, FTextLocalizationResource& InOutLocalizedResource) const
26+ {
27+ TMap<FString, TArray<FTolgeeTranslationData>> DataToInject = GetDataToInject ();
28+ for (const TPair<FString, TArray<FTolgeeTranslationData>>& CachedTranslation : DataToInject)
29+ {
30+ if (!InPrioritizedCultures.Contains (CachedTranslation.Key ))
31+ {
32+ continue ;
33+ }
34+
35+ for (const FTolgeeTranslationData& TranslationData : CachedTranslation.Value )
36+ {
37+ const FTextKey InNamespace = TranslationData.ParsedNamespace ;
38+ const FTextKey InKey = TranslationData.ParsedKey ;
39+ const FString InLocalizedString = TranslationData.Translation ;
40+
41+ if (FTextLocalizationResource::FEntry* ExistingEntry = InOutLocalizedResource.Entries .Find (FTextId (InNamespace, InKey)))
42+ {
43+ // NOTE: -1 is a higher than usual priority, meaning this entry will override any existing one. See FTextLocalizationResource::ShouldReplaceEntry
44+ InOutLocalizedResource.AddEntry (InNamespace, InKey, ExistingEntry->SourceStringHash , InLocalizedString, -1 );
45+ }
46+ else
47+ {
48+ UE_LOG (LogTolgee, Warning, TEXT (" Failed to inject translation for %s:%s. Default entry not found." ), *InNamespace.ToString (), *InKey.ToString ());
49+ }
50+ }
51+ }
52+ }
53+
54+ TMap<FString, TArray<FTolgeeTranslationData>> UTolgeeLocalizationInjectorSubsystem::GetDataToInject () const
55+ {
56+ return {};
57+ }
58+
59+ void UTolgeeLocalizationInjectorSubsystem::Initialize (FSubsystemCollectionBase& Collection)
60+ {
61+ Super::Initialize (Collection);
62+
63+ TextSource = MakeShared<FTolgeeTextSource>();
64+ TextSource->GetLocalizedResources .BindUObject (this , &ThisClass::GetLocalizedResources);
65+ FTextLocalizationManager::Get ().RegisterTextSource (TextSource.ToSharedRef ());
66+
67+ FWorldDelegates::OnStartGameInstance.AddUObject (this , &ThisClass::OnGameInstanceStart);
68+
69+ #if WITH_EDITOR
70+ FEditorDelegates::PrePIEEnded.AddUObject (this , &ThisClass::OnGameInstanceEnd);
71+ #endif
72+ }
73+
74+ void UTolgeeLocalizationInjectorSubsystem::RefreshTranslationDataAsync ()
75+ {
76+ UE_LOG (LogTolgee, Verbose, TEXT (" RefreshTranslationDataAsync requested." ));
77+
78+ AsyncTask (
79+ ENamedThreads::AnyHiPriThreadHiPriTask,
80+ [=]()
81+ {
82+ TRACE_CPUPROFILER_EVENT_SCOPE (UTolgeeLocalizationInjectorSubsystem::RefreshResources)
83+
84+ UE_LOG (LogTolgee, Verbose, TEXT (" RefreshTranslationDataAsync executing." ));
85+
86+ FTextLocalizationManager::Get ().RefreshResources ();
87+ }
88+ );
89+ }
90+
91+ TArray<FTolgeeTranslationData> UTolgeeLocalizationInjectorSubsystem::ExtractTranslationsFromPO (const FString& PoContent)
92+ {
93+ TRACE_CPUPROFILER_EVENT_SCOPE (UTolgeeLocalizationInjectorSubsystem::ExtractTranslationsFromPO)
94+
95+ TArray<FTolgeeTranslationData> Result;
96+
97+ #if WITH_LOCALIZATION_MODULE
98+ FPortableObjectFormatDOM PortableObject;
99+ PortableObject.FromString (PoContent);
100+
101+ for (auto EntryPairIter = PortableObject.GetEntriesIterator (); EntryPairIter; ++EntryPairIter)
102+ {
103+ auto POEntry = EntryPairIter->Value ;
104+ if (POEntry->MsgId .IsEmpty () || POEntry->MsgStr .Num () == 0 || POEntry->MsgStr [0 ].IsEmpty ())
105+ {
106+ // We ignore the header entry or entries with no translation.
107+ continue ;
108+ }
109+
110+ FTolgeeTranslationData TranslationData;
111+
112+ constexpr ELocalizedTextCollapseMode InTextCollapseMode = ELocalizedTextCollapseMode::IdenticalTextIdAndSource;
113+ constexpr EPortableObjectFormat InPOFormat = EPortableObjectFormat::Crowdin;
114+
115+ PortableObjectPipeline::ParseBasicPOFileEntry (*POEntry, TranslationData.ParsedNamespace , TranslationData.ParsedKey , TranslationData.SourceText , TranslationData.Translation , InTextCollapseMode, InPOFormat);
116+
117+ Result.Add (TranslationData);
118+ }
119+ #else
120+ UE_LOG (LogTolgee, Error, TEXT (" Localization module is not available. Cannot extract translations from PO content." ));
121+ #endif
122+
123+ return Result;
124+ }
0 commit comments