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;
}