// Copyright Epic Games, Inc. All Rights Reserved. #include "Commandlets/GenerateGatherArchiveCommandlet.h" #include "Internationalization/InternationalizationMetadata.h" DEFINE_LOG_CATEGORY_STATIC(LogGenerateArchiveCommandlet, Log, All); void ConditionTranslationMetadata(TSharedRef MetadataValue) { switch (MetadataValue->GetType()) { case ELocMetadataType::String: { TSharedPtr MetadataValuePtr = MetadataValue; TSharedPtr MetadataString = StaticCastSharedPtr(MetadataValuePtr); MetadataString->SetString(TEXT("")); } break; case ELocMetadataType::Array: { TArray> MetadataArray = MetadataValue->AsArray(); for (auto ArrayIter = MetadataArray.CreateIterator(); ArrayIter; ++ArrayIter) { TSharedPtr& Item = *ArrayIter; if (Item.IsValid()) { ConditionTranslationMetadata(Item.ToSharedRef()); } } } break; case ELocMetadataType::Object: { TSharedPtr MetadataObject = MetadataValue->AsObject(); for (auto ValueIter = MetadataObject->Values.CreateConstIterator(); ValueIter; ++ValueIter) { const FString Name = (*ValueIter).Key; TSharedPtr Value = (*ValueIter).Value; if (Value.IsValid()) { if (Value->GetType() == ELocMetadataType::String) { MetadataObject->SetStringField(Name, TEXT("")); } else { ConditionTranslationMetadata(Value.ToSharedRef()); } } } } default: break; } } void ConditionTranslation(FLocItem& LocItem) { // We clear out the translation text because this should only be entered by translators LocItem.Text = TEXT(""); // The translation might have metadata so we want to clear all the values of any string metadata if (LocItem.MetadataObj.IsValid()) { ConditionTranslationMetadata(MakeShareable(new FLocMetadataValueObject(LocItem.MetadataObj))); } } void ConditionSourceMetadata(TSharedRef MetadataValue) { switch (MetadataValue->GetType()) { case ELocMetadataType::Object: { TSharedPtr MetadataObject = MetadataValue->AsObject(); TArray NamesToBeReplaced; // We go through all the source metadata entries and look for any that have names prefixed with a '*'. If we // find any, we will replace them with a String type that contains an empty value for (auto Iter = MetadataObject->Values.CreateConstIterator(); Iter; ++Iter) { const FString& Name = (*Iter).Key; TSharedPtr Value = (*Iter).Value; if (Name.StartsWith(FLocMetadataObject::COMPARISON_MODIFIER_PREFIX)) { NamesToBeReplaced.Add(Name); } else { ConditionSourceMetadata(Value.ToSharedRef()); } } for (auto Iter = NamesToBeReplaced.CreateConstIterator(); Iter; ++Iter) { MetadataObject->RemoveField(*Iter); MetadataObject->SetStringField(*Iter, TEXT("")); } } default: break; } } void ConditionSource(FLocItem& LocItem) { if (LocItem.MetadataObj.IsValid()) { ConditionSourceMetadata(MakeShareable(new FLocMetadataValueObject(LocItem.MetadataObj))); } } /** * UGenerateGatherArchiveCommandlet */ UGenerateGatherArchiveCommandlet::UGenerateGatherArchiveCommandlet(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } int32 UGenerateGatherArchiveCommandlet::Main(const FString& Params) { // Parse command line - we're interested in the param vals TArray Tokens; TArray Switches; TMap ParamVals; UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals); // Set config file const FString* ParamVal = ParamVals.Find(FString(TEXT("Config"))); FString GatherTextConfigPath; if (ParamVal) { GatherTextConfigPath = *ParamVal; } else { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("No config specified.")); return -1; } // Set config section ParamVal = ParamVals.Find(FString(TEXT("Section"))); FString SectionName; if (ParamVal) { SectionName = *ParamVal; } else { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("No config section specified.")); return -1; } // Get manifest name. FString ManifestName; if (!GetStringFromConfig(*SectionName, TEXT("ManifestName"), ManifestName, GatherTextConfigPath)) { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("No manifest name specified.")); return -1; } // Get archive name. FString ArchiveName; if (!(GetStringFromConfig(*SectionName, TEXT("ArchiveName"), ArchiveName, GatherTextConfigPath))) { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("No archive name specified.")); return -1; } // Get native culture. FString NativeCulture; if (!GetStringFromConfig(*SectionName, TEXT("NativeCulture"), NativeCulture, GatherTextConfigPath)) { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("No native culture specified.")); return -1; } // Get cultures to generate. TArray CulturesToGenerate; GetStringArrayFromConfig(*SectionName, TEXT("CulturesToGenerate"), CulturesToGenerate, GatherTextConfigPath); if (CulturesToGenerate.Num() == 0) { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("No cultures specified for generation.")); return -1; } // Get destination path. FString DestinationPath; if (!GetPathFromConfig(*SectionName, TEXT("DestinationPath"), DestinationPath, GatherTextConfigPath)) { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("No destination path specified.")); return -1; } // Load the manifest and all archives FLocTextHelper LocTextHelper(DestinationPath, ManifestName, ArchiveName, NativeCulture, CulturesToGenerate, GatherManifestHelper->GetLocFileNotifies(), GatherManifestHelper->GetPlatformSplitMode()); { FText LoadError; if (!LocTextHelper.LoadAll(ELocTextHelperLoadFlags::LoadOrCreate, &LoadError)) { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("%s"), *LoadError.ToString()); return -1; } } // We need to make sure the native culture is processed first if (CulturesToGenerate.RemoveSingle(NativeCulture) > 0) { CulturesToGenerate.Insert(NativeCulture, 0); } for (const FString& CultureName: CulturesToGenerate) { // Add any missing manifest entries to the archive for this culture const bool bIsNativeCulture = CultureName == NativeCulture; LocTextHelper.EnumerateSourceTexts([&LocTextHelper, &CultureName, &NativeCulture, &bIsNativeCulture](TSharedRef InManifestEntry) -> bool { for (const FManifestContext& Context: InManifestEntry->Contexts) { if (!Context.bIsOptional) { TSharedPtr ArchiveEntry = LocTextHelper.FindTranslation(CultureName, InManifestEntry->Namespace, Context.Key, Context.KeyMetadataObj); if (ArchiveEntry.IsValid()) { // We only update existing entries for the native culture, as this lets us preserve stale translations in foreign archives so we can decide later whether we actually want to use them if (bIsNativeCulture && !ArchiveEntry->Source.IsExactMatch(InManifestEntry->Source)) { LocTextHelper.UpdateTranslation(CultureName, InManifestEntry->Namespace, Context.Key, Context.KeyMetadataObj, InManifestEntry->Source, InManifestEntry->Source); } } else { // Get the source we should use for the new entry FLocItem ArchiveSource = InManifestEntry->Source; if (!bIsNativeCulture) { TSharedPtr NativeArchiveEntry = LocTextHelper.FindTranslation(NativeCulture, InManifestEntry->Namespace, Context.Key, Context.KeyMetadataObj); if (NativeArchiveEntry.IsValid() && !NativeArchiveEntry->Source.IsExactMatch(NativeArchiveEntry->Translation)) { ArchiveSource = NativeArchiveEntry->Source; } } // We want to process the source before adding it to the archive ConditionSource(ArchiveSource); FLocItem ArchiveTranslation = ArchiveSource; if (!bIsNativeCulture) { // We want to process the translation before adding it to the archive // We skip this for native entries so that the source text is also added as the translation ConditionTranslation(ArchiveTranslation); } LocTextHelper.AddTranslation(CultureName, InManifestEntry->Namespace, Context.Key, Context.KeyMetadataObj, ArchiveSource, ArchiveTranslation, Context.bIsOptional); } } } return true; // continue enumeration }, true); // Trim any dead entries out of the archive LocTextHelper.TrimArchive(CultureName); // Save the new archive { FText SaveError; if (!LocTextHelper.SaveArchive(CultureName, &SaveError)) { UE_LOG(LogGenerateArchiveCommandlet, Error, TEXT("%s"), *SaveError.ToString()); return -1; } } } return 0; }