void FJsonInternationalizationArchiveSerializer::GenerateStructuredData( TSharedRef< const FInternationalizationArchive > InArchive, TSharedPtr<FStructuredArchiveEntry> RootElement )
{
	//Loop through all the unstructured archive entries and build up our structured hierarchy
	for(FArchiveEntryByStringContainer::TConstIterator It( InArchive->GetEntriesBySourceTextIterator() ); It; ++It)
	{
		const TSharedRef< FArchiveEntry > UnstructuredArchiveEntry = It.Value();

		TArray< FString > NamespaceTokens;

		// Tokenize the namespace by using '.' as a delimiter
		int32 NamespaceTokenCount = UnstructuredArchiveEntry->Namespace.ParseIntoArray( NamespaceTokens, *NAMESPACE_DELIMITER, true );

		TSharedPtr< FStructuredArchiveEntry > StructuredArchiveEntry = RootElement;
		//Loop through all the namespace tokens and find the appropriate structured entry, if it does not exist add it.  At the end StructuredArchiveEntry
		//  will point to the correct hierarchy entry for a given namespace
		for( int32 TokenIndex = 0; TokenIndex < NamespaceTokenCount; ++TokenIndex )
		{
			TSharedPtr<FStructuredArchiveEntry> FoundNamespaceEntry;
			for( int SubNamespaceIndex = 0; SubNamespaceIndex < StructuredArchiveEntry->SubNamespaces.Num(); SubNamespaceIndex++ )
			{
				if(  StructuredArchiveEntry->SubNamespaces[SubNamespaceIndex]->Namespace == NamespaceTokens[TokenIndex] )
				{
					FoundNamespaceEntry = StructuredArchiveEntry->SubNamespaces[SubNamespaceIndex];
					break;
				}
			}

			if( !FoundNamespaceEntry.IsValid() )
			{
				int32 index = StructuredArchiveEntry->SubNamespaces.Add( MakeShareable( new FStructuredArchiveEntry( NamespaceTokens[TokenIndex] ) ) );
				FoundNamespaceEntry = StructuredArchiveEntry->SubNamespaces[index];
			}
			StructuredArchiveEntry = FoundNamespaceEntry;
		}

		// We add the unstructured Archive entry to the hierarchy
		StructuredArchiveEntry->ArchiveEntries.AddUnique( UnstructuredArchiveEntry );
	}
}
bool UInternationalizationExportCommandlet::DoExport( const FString& SourcePath, const FString& DestinationPath, const FString& Filename )
{
	// Get native culture.
	FString NativeCultureName;
	if( !GetStringFromConfig( *SectionName, TEXT("NativeCulture"), NativeCultureName, ConfigPath ) )
	{
		UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("No native culture specified.") );
		return false;
	}

	// 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 export directory with windows file dialog from Translation Editor)
	bool bUseCultureDirectory = true;
	if (!(GetBoolFromConfig(*SectionName, TEXT("bUseCultureDirectory"), bUseCultureDirectory, ConfigPath)))
	{
		bUseCultureDirectory = true;
	}


	TSharedRef< FInternationalizationManifest > InternationalizationManifest = MakeShareable( new FInternationalizationManifest );
	// Load the manifest info
	{
		FString ManifestFilePath = SourcePath / ManifestName;
		if( !FPaths::FileExists(ManifestFilePath) )
		{
			UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Could not find manifest file %s."), *ManifestFilePath);
			return false;
		}

		TSharedPtr<FJsonObject> ManifestJsonObject = ReadJSONTextFile( ManifestFilePath );

		if( !ManifestJsonObject.IsValid() )
		{
			UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Could not read manifest file %s."), *ManifestFilePath);
			return false;
		}

		FJsonInternationalizationManifestSerializer ManifestSerializer;
		ManifestSerializer.DeserializeManifest( ManifestJsonObject.ToSharedRef(), InternationalizationManifest );
	}

	TArray< TSharedPtr<FInternationalizationArchive> > NativeArchives;
	{
		const FString NativeCulturePath = DestinationPath / *(NativeCultureName);
		TArray<FString> NativeArchiveFileNames;
		IFileManager::Get().FindFiles(NativeArchiveFileNames, *(NativeCulturePath / TEXT("*.archive")), true, false);

		for (const FString& NativeArchiveFileName : NativeArchiveFileNames)
		{
			// Read each archive file from the culture-named directory in the source path.
			FString ArchiveFilePath = NativeCulturePath / NativeArchiveFileName;
			ArchiveFilePath = FPaths::ConvertRelativePathToFull(ArchiveFilePath);
			TSharedRef<FInternationalizationArchive> InternationalizationArchive = MakeShareable(new FInternationalizationArchive);
			TSharedPtr< FJsonObject > ArchiveJsonObject = ReadJSONTextFile( ArchiveFilePath );
			FJsonInternationalizationArchiveSerializer ArchiveSerializer;
			ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive );

			NativeArchives.Add(InternationalizationArchive);
		}
	}

	// Process the desired cultures
	for(int32 Culture = 0; Culture < CulturesToGenerate.Num(); Culture++)
	{
		// Load the archive
		const FString CultureName = CulturesToGenerate[Culture];
		const FString CulturePath = SourcePath / CultureName;
		FString ArchiveFileName = CulturePath / ArchiveName;
		TSharedPtr< FJsonObject > ArchiveJsonObject = NULL;

		if( FPaths::FileExists(ArchiveFileName) )
		{
			ArchiveJsonObject = ReadJSONTextFile( ArchiveFileName );

			FJsonInternationalizationArchiveSerializer ArchiveSerializer;
			TSharedRef< FInternationalizationArchive > InternationalizationArchive = MakeShareable( new FInternationalizationArchive );
			ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive );

			{
				FPortableObjectFormatDOM PortableObj;

				FString LocLang;
				if( !PortableObj.SetLanguage( CultureName ) )
				{
					UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Skipping export of loc language %s because it is not recognized."), *LocLang );
					continue;
				}

				PortableObj.SetProjectName( FPaths::GetBaseFilename( ManifestName ) );
				PortableObj.CreateNewHeader();

				{
					for(TManifestEntryBySourceTextContainer::TConstIterator ManifestIter = InternationalizationManifest->GetEntriesBySourceTextIterator(); ManifestIter; ++ManifestIter)
					{
						// Gather relevant info from manifest entry.
						const TSharedRef<FManifestEntry>& ManifestEntry = ManifestIter.Value();
						const FString& Namespace = ManifestEntry->Namespace;
						const FLocItem& Source = ManifestEntry->Source;

						for( auto ContextIter = ManifestEntry->Contexts.CreateConstIterator(); ContextIter; ++ContextIter )
						{
							{
								const TSharedPtr<FArchiveEntry> ArchiveEntry = InternationalizationArchive->FindEntryBySource( Namespace, Source, ContextIter->KeyMetadataObj );
							if( ArchiveEntry.IsValid() )
							{
								const FString ConditionedArchiveSource = ConditionArchiveStrForPo(ArchiveEntry->Source.Text);
								const FString ConditionedArchiveTranslation = ConditionArchiveStrForPo(ArchiveEntry->Translation.Text);

								TSharedRef<FPortableObjectEntry> PoEntry = MakeShareable( new FPortableObjectEntry );
								//@TODO: We support additional metadata entries that can be translated.  How do those fit in the PO file format?  Ex: isMature
								PoEntry->MsgId = ConditionedArchiveSource;
								//@TODO: Take into account optional entries and entries that differ by keymetadata.  Ex. Each optional entry needs a unique msgCtxt
								PoEntry->MsgCtxt = Namespace;
								PoEntry->MsgStr.Add( ConditionedArchiveTranslation );

								FString PORefString = ConvertSrcLocationToPORef( ContextIter->SourceLocation );
								PoEntry->AddReference( PORefString ); // Source location.
								PoEntry->AddExtractedComment( ContextIter->Key ); // "Notes from Programmer" in the form of the Key.
								PoEntry->AddExtractedComment( PORefString ); // "Notes from Programmer" in the form of the Source Location, since this comes in handy too and OneSky doesn't properly show references, only comments.
								PortableObj.AddEntry( PoEntry );
							}
						}

							if (CultureName != NativeCultureName)
							{
								TSharedPtr<FArchiveEntry> NativeArchiveEntry;
								for (const auto& NativeArchive : NativeArchives)
								{
									const TSharedPtr<FArchiveEntry> PotentialNativeArchiveEntry = NativeArchive->FindEntryBySource( Namespace, Source, ContextIter->KeyMetadataObj );
									if (PotentialNativeArchiveEntry.IsValid())
									{
										NativeArchiveEntry = PotentialNativeArchiveEntry;
										break;
									}
								}

								if (NativeArchiveEntry.IsValid())
								{
									if (!NativeArchiveEntry->Source.IsExactMatch(NativeArchiveEntry->Translation))
									{
										const TSharedPtr<FArchiveEntry> ArchiveEntry = InternationalizationArchive->FindEntryBySource( Namespace, NativeArchiveEntry->Translation, NativeArchiveEntry->KeyMetadataObj );

										const FString ConditionedArchiveSource = ConditionArchiveStrForPo(ArchiveEntry->Source.Text);
										const FString ConditionedArchiveTranslation = ConditionArchiveStrForPo(ArchiveEntry->Translation.Text);

										TSharedRef<FPortableObjectEntry> PoEntry = MakeShareable( new FPortableObjectEntry );
										//@TODO: We support additional metadata entries that can be translated.  How do those fit in the PO file format?  Ex: isMature
										PoEntry->MsgId = ConditionedArchiveSource;
										//@TODO: Take into account optional entries and entries that differ by keymetadata.  Ex. Each optional entry needs a unique msgCtxt
										PoEntry->MsgCtxt = Namespace;
										PoEntry->MsgStr.Add( ConditionedArchiveTranslation );

										FString PORefString = ConvertSrcLocationToPORef( ContextIter->SourceLocation );
										PoEntry->AddReference( PORefString ); // Source location.
										PoEntry->AddExtractedComment( ContextIter->Key ); // "Notes from Programmer" in the form of the Key.
										PoEntry->AddExtractedComment( PORefString ); // "Notes from Programmer" in the form of the Source Location, since this comes in handy too and OneSky doesn't properly show references, only comments.
										PortableObj.AddEntry( PoEntry );
									}
								}
					}
				}
					}
				}

				// Write out the Portable Object to .po file.
				{
					PortableObj.SortEntries();
					FString OutputString = PortableObj.ToString();
					FString OutputFileName = "";
					if (bUseCultureDirectory)
					{
						OutputFileName = DestinationPath / CultureName / Filename;
					}
					else
					{
						OutputFileName = DestinationPath / Filename;
					}

					if( SourceControlInfo.IsValid() )
					{
						FText SCCErrorText;
						if (!SourceControlInfo->CheckOutFile(OutputFileName, SCCErrorText))
						{
							UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Check out of file %s failed: %s"), *OutputFileName, *SCCErrorText.ToString());
							return false;
						}
					}

					//@TODO We force UTF8 at the moment but we want this to be based on the format found in the header info.
					if( !FFileHelper::SaveStringToFile(OutputString, *OutputFileName, FFileHelper::EEncodingOptions::ForceUTF8) )
					{
						UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Could not write file %s"), *OutputFileName );
						return false;
					}
				}
			}
		}
	}
	return true;
}
/**
*	UInternationalizationExportCommandlet
*/
bool UInternationalizationExportCommandlet::DoExport( const FString& SourcePath, const FString& DestinationPath, const FString& Filename )
{
	// Get native culture.
	FString NativeCultureName;
	if( !GetStringFromConfig( *SectionName, TEXT("NativeCulture"), NativeCultureName, ConfigPath ) )
	{
		UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("No native culture specified.") );
		return false;
	}

	// 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 export directory with windows file dialog from Translation Editor)
	bool bUseCultureDirectory = true;
	if (!(GetBoolFromConfig(*SectionName, TEXT("bUseCultureDirectory"), bUseCultureDirectory, ConfigPath)))
	{
		bUseCultureDirectory = true;
	}

	bool ShouldAddSourceLocationsAsComments = true;
	GetBoolFromConfig(*SectionName, TEXT("ShouldAddSourceLocationsAsComments"), ShouldAddSourceLocationsAsComments, ConfigPath);

	TSharedRef< FInternationalizationManifest > InternationalizationManifest = MakeShareable( new FInternationalizationManifest );
	// Load the manifest info
	{
		FString ManifestFilePath = SourcePath / ManifestName;
		if( !FPaths::FileExists(ManifestFilePath) )
		{
			UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Could not find manifest file %s."), *ManifestFilePath);
			return false;
		}

		TSharedPtr<FJsonObject> ManifestJsonObject = ReadJSONTextFile( ManifestFilePath );

		if( !ManifestJsonObject.IsValid() )
		{
			UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Could not read manifest file %s."), *ManifestFilePath);
			return false;
		}

		FJsonInternationalizationManifestSerializer ManifestSerializer;
		ManifestSerializer.DeserializeManifest( ManifestJsonObject.ToSharedRef(), InternationalizationManifest );
	}

	TArray< TSharedPtr<FInternationalizationArchive> > NativeArchives;
	{
		const FString NativeCulturePath = SourcePath / *(NativeCultureName);
		TArray<FString> NativeArchiveFileNames;
		IFileManager::Get().FindFiles(NativeArchiveFileNames, *(NativeCulturePath / TEXT("*.archive")), true, false);

		for (const FString& NativeArchiveFileName : NativeArchiveFileNames)
		{
			// Read each archive file from the culture-named directory in the source path.
			FString ArchiveFilePath = NativeCulturePath / NativeArchiveFileName;
			ArchiveFilePath = FPaths::ConvertRelativePathToFull(ArchiveFilePath);
			TSharedRef<FInternationalizationArchive> InternationalizationArchive = MakeShareable(new FInternationalizationArchive);
			TSharedPtr< FJsonObject > ArchiveJsonObject = ReadJSONTextFile( ArchiveFilePath );
			FJsonInternationalizationArchiveSerializer ArchiveSerializer;
			ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive );

			NativeArchives.Add(InternationalizationArchive);
		}
	}

	// Process the desired cultures
	for(int32 Culture = 0; Culture < CulturesToGenerate.Num(); Culture++)
	{
		// Load the archive
		const FString CultureName = CulturesToGenerate[Culture];
		const FString CulturePath = SourcePath / CultureName;
		FString ArchiveFileName = CulturePath / ArchiveName;
		TSharedPtr< FJsonObject > ArchiveJsonObject = NULL;

		if( FPaths::FileExists(ArchiveFileName) )
		{
			ArchiveJsonObject = ReadJSONTextFile( ArchiveFileName );

			FJsonInternationalizationArchiveSerializer ArchiveSerializer;
			TSharedRef< FInternationalizationArchive > InternationalizationArchive = MakeShareable( new FInternationalizationArchive );
			ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive );

			{
				FPortableObjectFormatDOM NewPortableObject;

				FString LocLang;
				if( !NewPortableObject.SetLanguage( CultureName ) )
				{
					UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Skipping export of loc language %s because it is not recognized."), *LocLang );
					continue;
				}

				NewPortableObject.SetProjectName( FPaths::GetBaseFilename( ManifestName ) );
				NewPortableObject.CreateNewHeader();

				{
					for(TManifestEntryBySourceTextContainer::TConstIterator ManifestIter = InternationalizationManifest->GetEntriesBySourceTextIterator(); ManifestIter; ++ManifestIter)
					{
						// Gather relevant info from manifest entry.
						const TSharedRef<FManifestEntry>& ManifestEntry = ManifestIter.Value();
						const FString& Namespace = ManifestEntry->Namespace;
						const FLocItem& Source = ManifestEntry->Source;

						// For each context, we may need to create a different or even multiple PO entries.
						for( auto ContextIter = ManifestEntry->Contexts.CreateConstIterator(); ContextIter; ++ContextIter )
						{
							const FContext& Context = *ContextIter;

							// Create the typical PO entry from the archive entry which matches the exact same namespace, source, and key metadata, if it exists.
							{
								const TSharedPtr<FArchiveEntry> ArchiveEntry = InternationalizationArchive->FindEntryBySource( Namespace, Source, Context.KeyMetadataObj );
								if( ArchiveEntry.IsValid() )
								{
									const FString ConditionedArchiveSource = ConditionArchiveStrForPo(ArchiveEntry->Source.Text);
									const FString ConditionedArchiveTranslation = ConditionArchiveStrForPo(ArchiveEntry->Translation.Text);

									TSharedRef<FPortableObjectEntry> PoEntry = MakeShareable( new FPortableObjectEntry );
									//@TODO: We support additional metadata entries that can be translated.  How do those fit in the PO file format?  Ex: isMature
									PoEntry->MsgId = ConditionedArchiveSource;
									PoEntry->MsgCtxt = ConditionIdentityForPOMsgCtxt(Namespace, Context.Key, Context.KeyMetadataObj);
									PoEntry->MsgStr.Add( ConditionedArchiveTranslation );

									const FString PORefString = ConvertSrcLocationToPORef( Context.SourceLocation );
									PoEntry->AddReference(PORefString); // Source location.

									PoEntry->AddExtractedComment( GetConditionedKeyForExtractedComment(Context.Key) ); // "Notes from Programmer" in the form of the Key.

									if (ShouldAddSourceLocationsAsComments)
									{
										PoEntry->AddExtractedComment(GetConditionedReferenceForExtractedComment(PORefString)); // "Notes from Programmer" in the form of the Source Location, since this comes in handy too and OneSky doesn't properly show references, only comments.
									}

									TArray<FString> InfoMetaDataStrings;
									if (Context.InfoMetadataObj.IsValid())
									{
										for (auto InfoMetaDataPair : Context.InfoMetadataObj->Values)
										{
											const FString KeyName = InfoMetaDataPair.Key;
											const TSharedPtr<FLocMetadataValue> Value = InfoMetaDataPair.Value;
											InfoMetaDataStrings.Add(GetConditionedInfoMetaDataForExtractedComment(KeyName, Value->AsString()));
										}
									}
									if (InfoMetaDataStrings.Num())
									{
										PoEntry->AddExtractedComments(InfoMetaDataStrings);
									}

									NewPortableObject.AddEntry( PoEntry );
								}
							}

							// If we're exporting for something other than the native culture, we'll need to create PO entries for archive entries based on the native archive's translation.
							if (CultureName != NativeCultureName)
							{
								TSharedPtr<FArchiveEntry> NativeArchiveEntry;
								// Find the native archive entry which matches the exact same namespace, source, and key metadata, if it exists.
								for (const auto& NativeArchive : NativeArchives)
								{
									const TSharedPtr<FArchiveEntry> PotentialNativeArchiveEntry = NativeArchive->FindEntryBySource( Namespace, Source, Context.KeyMetadataObj );
									if (PotentialNativeArchiveEntry.IsValid())
									{
										NativeArchiveEntry = PotentialNativeArchiveEntry;
										break;
									}
								}

								if (NativeArchiveEntry.IsValid())
								{
									// Only need to create this PO entry if the native archive entry's translation differs from its source, in which case we need to find the our translation of the native translation.
									if (!NativeArchiveEntry->Source.IsExactMatch(NativeArchiveEntry->Translation))
									{
										const TSharedPtr<FArchiveEntry> ArchiveEntry = InternationalizationArchive->FindEntryBySource( Namespace, NativeArchiveEntry->Translation, NativeArchiveEntry->KeyMetadataObj );
										if (ArchiveEntry.IsValid())
										{
											const FString ConditionedArchiveSource = ConditionArchiveStrForPo(ArchiveEntry->Source.Text);
											const FString ConditionedArchiveTranslation = ConditionArchiveStrForPo(ArchiveEntry->Translation.Text);

											TSharedRef<FPortableObjectEntry> PoEntry = MakeShareable( new FPortableObjectEntry );
											//@TODO: We support additional metadata entries that can be translated.  How do those fit in the PO file format?  Ex: isMature
											PoEntry->MsgId = ConditionedArchiveSource;
											PoEntry->MsgCtxt = ConditionIdentityForPOMsgCtxt(Namespace, Context.Key, Context.KeyMetadataObj);
											PoEntry->MsgStr.Add( ConditionedArchiveTranslation );

											const FString PORefString = ConvertSrcLocationToPORef( Context.SourceLocation );
											PoEntry->AddReference( PORefString ); // Source location.

											PoEntry->AddExtractedComment( FString::Printf(TEXT("Key:\t%s"), *Context.Key) ); // "Notes from Programmer" in the form of the Key.
											PoEntry->AddExtractedComment( FString::Printf(TEXT("SourceLocation:\t%s"), *PORefString) ); // "Notes from Programmer" in the form of the Source Location, since this comes in handy too and OneSky doesn't properly show references, only comments.
											TArray<FString> InfoMetaDataStrings;
											if (Context.InfoMetadataObj.IsValid())
											{
												for (auto InfoMetaDataPair : Context.InfoMetadataObj->Values)
												{
													const FString KeyName = InfoMetaDataPair.Key;
													const TSharedPtr<FLocMetadataValue> Value = InfoMetaDataPair.Value;
													InfoMetaDataStrings.Add(FString::Printf(TEXT("InfoMetaData:\t\"%s\" : \"%s\""), *KeyName, *Value->AsString()));
												}
											}
											if (InfoMetaDataStrings.Num())
											{
												PoEntry->AddExtractedComments(InfoMetaDataStrings);
											}

											NewPortableObject.AddEntry( PoEntry );
										}
									}
								}
							}
						}
					}
				}

				// Write out the Portable Object to .po file.
				{
					FString OutputFileName;
					if (bUseCultureDirectory)
					{
						OutputFileName = DestinationPath / CultureName / Filename;
					}
					else
					{
						OutputFileName = DestinationPath / Filename;
					}

					// Persist comments if requested.
					if (ShouldPersistComments)
					{
						// Preserve comments from the specified file now, if they haven't already been.
						if (!HasPreservedComments)
						{
							FPortableObjectFormatDOM ExistingPortableObject;
							const bool HasLoadedPOFile = LoadPOFile(OutputFileName, ExistingPortableObject);
							if (!HasLoadedPOFile)
							{
								return false;
							}

							PreserveExtractedCommentsForPersistence(ExistingPortableObject);
						}

						// Persist the comments into the new portable object we're going to be saving.
						for (const auto& Pair : POEntryToCommentMap)
						{
							const TSharedPtr<FPortableObjectEntry> FoundEntry = NewPortableObject.FindEntry(Pair.Key.MsgId, Pair.Key.MsgIdPlural, Pair.Key.MsgCtxt);
							if (FoundEntry.IsValid())
							{
								FoundEntry->AddExtractedComments(Pair.Value);
							}
						}
					}

					NewPortableObject.SortEntries();

					if( SourceControlInfo.IsValid() )
					{
						FText SCCErrorText;
						if (!SourceControlInfo->CheckOutFile(OutputFileName, SCCErrorText))
						{
							UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Check out of file %s failed: %s"), *OutputFileName, *SCCErrorText.ToString());
							return false;
						}
					}

					//@TODO We force UTF8 at the moment but we want this to be based on the format found in the header info.
					const FString OutputString = NewPortableObject.ToString();
					if (!FFileHelper::SaveStringToFile(OutputString, *OutputFileName, FFileHelper::EEncodingOptions::ForceUTF8))
					{
						UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Could not write file %s"), *OutputFileName );
						return false;
					}
				}
			}
		}
	}
	return true;
}
void RepairManifestAndArchives(TSharedRef<FInternationalizationManifest> Manifest, TArray< TSharedRef<FInternationalizationArchive> > Archives)
{
	// Update source text if this manifest was saved before the escape sequence fixes.
	if (Manifest->GetFormatVersion() < FInternationalizationManifest::EFormatVersion::EscapeFixes)
	{
		TManifestEntryBySourceTextContainer::TConstIterator Iterator = Manifest->GetEntriesBySourceTextIterator();
		for(; Iterator; ++Iterator)
		{
			const FString& Source = Iterator.Key();
			const TSharedRef<FManifestEntry>& ManifestEntry = Iterator.Value();

			// Find double quotes and unescape them once.
			FLocItem UpdatedSource(ManifestEntry->Source);
			UpdatedSource.Text.ReplaceInline(TEXT("\\\""), TEXT("\""));

			TSharedRef<FManifestEntry> UpdatedManifestEntry = MakeShareable(new FManifestEntry(ManifestEntry->Namespace, UpdatedSource));
			UpdatedManifestEntry->Contexts = ManifestEntry->Contexts;

			Manifest->UpdateEntry(ManifestEntry, UpdatedManifestEntry);
		}
	}

	for (const TSharedRef<FInternationalizationArchive>& Archive : Archives)
	{
		// Update source text if this archive was saved before the escape sequence fixes.
		if (Archive->GetFormatVersion() < FInternationalizationArchive::EFormatVersion::EscapeFixes)
		{
			TArchiveEntryContainer::TConstIterator Iterator = Archive->GetEntryIterator();

			for (; Iterator; ++Iterator)
			{
				const FString& Source = Iterator.Key();
				const TSharedRef<FArchiveEntry>& ArchiveEntry = Iterator.Value();

				// Find double quotes and unescape them once.
				FLocItem UpdatedSource(ArchiveEntry->Source);
				UpdatedSource.Text.ReplaceInline(TEXT("\\\""), TEXT("\""));

				// Find double quotes and unescape them once.
				FLocItem UpdatedTranslation(ArchiveEntry->Translation);
				UpdatedTranslation.Text.ReplaceInline(TEXT("\\\""), TEXT("\""));

				TSharedRef<FArchiveEntry> UpdatedArchiveEntry = MakeShareable(new FArchiveEntry(ArchiveEntry->Namespace, UpdatedSource, UpdatedTranslation, ArchiveEntry->KeyMetadataObj, ArchiveEntry->bIsOptional));

				Archive->UpdateEntry(ArchiveEntry, UpdatedArchiveEntry);
			}
		}
	}

	const FRegexPattern Pattern(TEXT(".* - line \\d+"));

	TManifestEntryBySourceTextContainer::TConstIterator Iterator = Manifest->GetEntriesBySourceTextIterator();
	for(; Iterator; ++Iterator)
	{
		const FString& Source = Iterator.Key();
		const TSharedRef<FManifestEntry>& ManifestEntry = Iterator.Value();

		// Identify if this entry comes from source text only.
		bool AreAllContextsFromSource = ManifestEntry->Contexts.Num() > 0;
		for (const FContext& Context : ManifestEntry->Contexts)
		{
			FRegexMatcher Matcher(Pattern, Context.SourceLocation);
			AreAllContextsFromSource = Matcher.FindNext();

			if(!AreAllContextsFromSource)
			{
				break;
			}
		}

		// No updates needed for this entry if it isn't all from source.
		if(!AreAllContextsFromSource)
		{
			continue;
		}

		for (const TSharedRef<FInternationalizationArchive>& Archive : Archives)
		{
			// Update source text if this manifest was saved before the escape sequence fixes.
			if (Archive->GetFormatVersion() < FInternationalizationArchive::EFormatVersion::EscapeFixes)
			{
				const TSharedPtr<FArchiveEntry> ArchiveEntry = Archive->FindEntryBySource(ManifestEntry->Namespace, ManifestEntry->Source, /*KeyMetadataObj*/ nullptr);
				if (!ArchiveEntry.IsValid())
				{
					continue;
				}

				// Replace escape sequences with their associated character, once.
				FLocItem UpdatedSource(ArchiveEntry->Source);
				UpdatedSource.Text = UpdatedSource.Text.ReplaceEscapedCharWithChar();

				// Replace escape sequences with their associated character, once.
				FLocItem UpdatedTranslation(ArchiveEntry->Translation);
				UpdatedTranslation.Text = UpdatedTranslation.Text.ReplaceEscapedCharWithChar();

				TSharedRef<FArchiveEntry> UpdatedArchiveEntry = MakeShareable(new FArchiveEntry(ArchiveEntry->Namespace, UpdatedSource, UpdatedTranslation, ArchiveEntry->KeyMetadataObj, ArchiveEntry->bIsOptional));

				Archive->UpdateEntry(ArchiveEntry.ToSharedRef(), UpdatedArchiveEntry);
			}
		}

		// Update source text if this manifest was saved before the escape sequence fixes.
		if (Manifest->GetFormatVersion() < FInternationalizationManifest::EFormatVersion::EscapeFixes)
		{
			// Replace escape sequences with their associated character, once.
			FLocItem UpdatedSource(ManifestEntry->Source);
			UpdatedSource.Text = UpdatedSource.Text.ReplaceEscapedCharWithChar();

			TSharedRef<FManifestEntry> UpdatedManifestEntry = MakeShareable(new FManifestEntry(ManifestEntry->Namespace, UpdatedSource));
			UpdatedManifestEntry->Contexts = ManifestEntry->Contexts;

			Manifest->UpdateEntry(ManifestEntry, UpdatedManifestEntry);
		}
	}

	Manifest->SetFormatVersion(FInternationalizationManifest::EFormatVersion::Latest);
	for (const TSharedRef<FInternationalizationArchive>& Archive : Archives)
	{
		Archive->SetFormatVersion(FInternationalizationArchive::EFormatVersion::Latest);
	}
}