void UInternationalizationExportCommandlet::PreserveExtractedCommentsForPersistence(FPortableObjectFormatDOM& PortableObject) { // Preserve comments for later. for (auto EntriesIterator = PortableObject.GetEntriesIterator(); EntriesIterator; ++EntriesIterator) { const TSharedPtr< FPortableObjectEntry >& Entry = *EntriesIterator; // Preserve only non-procedurally generated extracted comments. const TArray<FString> CommentsToPreserve = Entry->ExtractedComments.FilterByPredicate([=](const FString& ExtractedComment) -> bool { return !ExtractedComment.StartsWith("Key:") && !ExtractedComment.StartsWith("SourceLocation:") && !ExtractedComment.StartsWith("InfoMetaData:"); }); if (CommentsToPreserve.Num()) { POEntryToCommentMap.Add(FPortableObjectEntryIdentity{ Entry->MsgCtxt, Entry->MsgId, Entry->MsgIdPlural }, CommentsToPreserve); } } HasPreservedComments = true; }
bool UInternationalizationExportCommandlet::DoImport(const FString& SourcePath, const FString& DestinationPath, const FString& Filename) { // Get manifest name. FString ManifestName; if( !GetStringFromConfig( *SectionName, TEXT("ManifestName"), ManifestName, ConfigPath ) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("No manifest name specified.") ); return false; } // Get archive name. FString ArchiveName; if( !( GetStringFromConfig(* SectionName, TEXT("ArchiveName"), ArchiveName, ConfigPath ) ) ) { UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("No archive name specified.")); return false; } // Get culture directory setting, default to true if not specified (used to allow picking of import directory with file open dialog from Translation Editor) bool bUseCultureDirectory = true; if (!(GetBoolFromConfig(*SectionName, TEXT("bUseCultureDirectory"), bUseCultureDirectory, ConfigPath))) { bUseCultureDirectory = true; } // Process the desired cultures for(int32 Culture = 0; Culture < CulturesToGenerate.Num(); Culture++) { // Load the Portable Object file if found const FString CultureName = CulturesToGenerate[Culture]; FString POFilePath = ""; if (bUseCultureDirectory) { POFilePath = SourcePath / CultureName / Filename; } else { POFilePath = SourcePath / Filename; } FPortableObjectFormatDOM PortableObject; const bool HasLoadedPOFile = LoadPOFile(POFilePath, PortableObject); if (!HasLoadedPOFile) { continue; } if (ShouldPersistComments) { PreserveExtractedCommentsForPersistence(PortableObject); } if (PortableObject.GetProjectName() != ManifestName.Replace(TEXT(".manifest"), TEXT(""))) { UE_LOG(LogInternationalizationExportCommandlet, Log, TEXT("The project name (%s) in the file (%s) did not match the target manifest project (%s)."), *POFilePath, *PortableObject.GetProjectName(), *ManifestName.Replace(TEXT(".manifest"), TEXT(""))); } const FString ManifestFileName = DestinationPath / ManifestName; TSharedPtr< FJsonObject > ManifestJsonObject = NULL; ManifestJsonObject = ReadJSONTextFile( ManifestFileName ); FJsonInternationalizationManifestSerializer ManifestSerializer; TSharedRef< FInternationalizationManifest > InternationalizationManifest = MakeShareable( new FInternationalizationManifest ); ManifestSerializer.DeserializeManifest( ManifestJsonObject.ToSharedRef(), InternationalizationManifest ); if( !FPaths::FileExists(ManifestFileName) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Failed to find manifest %s."), *ManifestFileName); continue; } const FString DestinationCulturePath = DestinationPath / CultureName; const FString ArchiveFileName = DestinationCulturePath / ArchiveName; if( !FPaths::FileExists(ArchiveFileName) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Failed to find destination archive %s."), *ArchiveFileName); continue; } TSharedPtr< FJsonObject > ArchiveJsonObject = NULL; ArchiveJsonObject = ReadJSONTextFile( ArchiveFileName ); FJsonInternationalizationArchiveSerializer ArchiveSerializer; TSharedRef< FInternationalizationArchive > InternationalizationArchive = MakeShareable( new FInternationalizationArchive ); ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive ); bool bModifiedArchive = false; { for( auto EntryIter = PortableObject.GetEntriesIterator(); EntryIter; ++EntryIter ) { auto POEntry = *EntryIter; if( POEntry->MsgId.IsEmpty() || POEntry->MsgStr.Num() == 0 || POEntry->MsgStr[0].Trim().IsEmpty() ) { // We ignore the header entry or entries with no translation. continue; } // Some warning messages for data we don't process at the moment if( !POEntry->MsgIdPlural.IsEmpty() || POEntry->MsgStr.Num() > 1 ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Portable Object entry has plural form we did not process. File: %s MsgCtxt: %s MsgId: %s"), *POFilePath, *POEntry->MsgCtxt, *POEntry->MsgId ); } FString Key; FString Namespace; ParsePOMsgCtxtForIdentity(POEntry->MsgCtxt, Namespace, Key); const FString& SourceText = ConditionPoStringForArchive(POEntry->MsgId); const FString& Translation = ConditionPoStringForArchive(POEntry->MsgStr[0]); TSharedPtr<FLocMetadataObject> KeyMetaDataObject; // Get key metadata from the manifest, using the namespace and key. if (!Key.IsEmpty()) { // Find manifest entry by namespace for (auto ManifestEntryIterator = InternationalizationManifest->GetEntriesByContextIdIterator(); ManifestEntryIterator; ++ManifestEntryIterator) { const FString& ManifestEntryNamespace = ManifestEntryIterator->Key; const TSharedRef<FManifestEntry>& ManifestEntry = ManifestEntryIterator->Value; if (ManifestEntry->Namespace == Namespace) { FContext* const MatchingContext = ManifestEntry->Contexts.FindByPredicate([&](FContext& Context) -> bool { return Context.Key == Key; }); if (MatchingContext) { KeyMetaDataObject = MatchingContext->KeyMetadataObj; } } } } //@TODO: Take into account optional entries and entries that differ by keymetadata. Ex. Each optional entry needs a unique msgCtxt const TSharedPtr< FArchiveEntry > FoundEntry = InternationalizationArchive->FindEntryBySource( Namespace, SourceText, KeyMetaDataObject ); if( !FoundEntry.IsValid() ) { UE_LOG(LogInternationalizationExportCommandlet, Warning, TEXT("Could not find corresponding archive entry for PO entry. File: %s MsgCtxt: %s MsgId: %s"), *POFilePath, *POEntry->MsgCtxt, *POEntry->MsgId ); continue; } if( FoundEntry->Translation != Translation ) { FoundEntry->Translation = Translation; bModifiedArchive = true; } } } if( bModifiedArchive ) { TSharedRef<FJsonObject> FinalArchiveJsonObj = MakeShareable( new FJsonObject ); ArchiveSerializer.SerializeArchive( InternationalizationArchive, FinalArchiveJsonObj ); if( !WriteJSONToTextFile(FinalArchiveJsonObj, ArchiveFileName, SourceControlInfo ) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Failed to write archive to %s."), *ArchiveFileName ); return false; } } } return true; }
bool UInternationalizationExportCommandlet::DoImport(const FString& SourcePath, const FString& DestinationPath, const FString& Filename) { // Get manifest name. FString ManifestName; if( !GetStringFromConfig( *SectionName, TEXT("ManifestName"), ManifestName, ConfigPath ) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("No manifest name specified.") ); return false; } // Get archive name. FString ArchiveName; if( !( GetStringFromConfig(* SectionName, TEXT("ArchiveName"), ArchiveName, ConfigPath ) ) ) { UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("No archive name specified.")); return false; } // Get culture directory setting, default to true if not specified (used to allow picking of import directory with file open dialog from Translation Editor) bool bUseCultureDirectory = true; if (!(GetBoolFromConfig(*SectionName, TEXT("bUseCultureDirectory"), bUseCultureDirectory, ConfigPath))) { bUseCultureDirectory = true; } // Process the desired cultures for(int32 Culture = 0; Culture < CulturesToGenerate.Num(); Culture++) { // Load the Portable Object file if found const FString CultureName = CulturesToGenerate[Culture]; FString POFilePath = ""; if (bUseCultureDirectory) { POFilePath = SourcePath / CultureName / Filename; } else { POFilePath = SourcePath / Filename; } if( !FPaths::FileExists(POFilePath) ) { UE_LOG( LogInternationalizationExportCommandlet, Warning, TEXT("Could not find file %s"), *POFilePath ); continue; } FString POFileContents; if ( !FFileHelper::LoadFileToString( POFileContents, *POFilePath ) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Failed to load file %s."), *POFilePath); continue; } FPortableObjectFormatDOM PortableObject; if( !PortableObject.FromString( POFileContents ) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Failed to parse Portable Object file %s."), *POFilePath); continue; } if( PortableObject.GetProjectName() != ManifestName.Replace(TEXT(".manifest"), TEXT("")) ) { UE_LOG( LogInternationalizationExportCommandlet, Warning, TEXT("The project name (%s) in the file (%s) did not match the target manifest project (%s)."), *POFilePath, *PortableObject.GetProjectName(), *ManifestName.Replace(TEXT(".manifest"), TEXT(""))); } const FString DestinationCulturePath = DestinationPath / CultureName; FString ArchiveFileName = DestinationCulturePath / ArchiveName; if( !FPaths::FileExists(ArchiveFileName) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Failed to find destination archive %s."), *ArchiveFileName); continue; } TSharedPtr< FJsonObject > ArchiveJsonObject = NULL; ArchiveJsonObject = ReadJSONTextFile( ArchiveFileName ); FJsonInternationalizationArchiveSerializer ArchiveSerializer; TSharedRef< FInternationalizationArchive > InternationalizationArchive = MakeShareable( new FInternationalizationArchive ); ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive ); bool bModifiedArchive = false; { for( auto EntryIter = PortableObject.GetEntriesIterator(); EntryIter; ++EntryIter ) { auto POEntry = *EntryIter; if( POEntry->MsgId.IsEmpty() || POEntry->MsgStr.Num() == 0 || POEntry->MsgStr[0].Trim().IsEmpty() ) { // We ignore the header entry or entries with no translation. continue; } // Some warning messages for data we don't process at the moment if( !POEntry->MsgIdPlural.IsEmpty() || POEntry->MsgStr.Num() > 1 ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Portable Object entry has plural form we did not process. File: %s MsgCtxt: %s MsgId: %s"), *POFilePath, *POEntry->MsgCtxt, *POEntry->MsgId ); } const FString& Namespace = POEntry->MsgCtxt; const FString& SourceText = ConditionPoStringForArchive(POEntry->MsgId); const FString& Translation = ConditionPoStringForArchive(POEntry->MsgStr[0]); //@TODO: Take into account optional entries and entries that differ by keymetadata. Ex. Each optional entry needs a unique msgCtxt TSharedPtr< FArchiveEntry > FoundEntry = InternationalizationArchive->FindEntryBySource( Namespace, SourceText, NULL ); if( !FoundEntry.IsValid() ) { UE_LOG(LogInternationalizationExportCommandlet, Warning, TEXT("Could not find corresponding archive entry for PO entry. File: %s MsgCtxt: %s MsgId: %s"), *POFilePath, *POEntry->MsgCtxt, *POEntry->MsgId ); continue; } if( FoundEntry->Translation != Translation ) { FoundEntry->Translation = Translation; bModifiedArchive = true; } } } if( bModifiedArchive ) { TSharedRef<FJsonObject> FinalArchiveJsonObj = MakeShareable( new FJsonObject ); ArchiveSerializer.SerializeArchive( InternationalizationArchive, FinalArchiveJsonObj ); if( !WriteJSONToTextFile(FinalArchiveJsonObj, ArchiveFileName, SourceControlInfo ) ) { UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Failed to write archive to %s."), *ArchiveFileName ); return false; } } } return true; }