bool FTextLocalizationResourceGenerator::Generate(const FString& SourcePath, const TSharedRef<FInternationalizationManifest>& InternationalizationManifest, const FString& NativeCulture, const FString& CultureToGenerate, FArchive* const DestinationArchive, IInternationalizationArchiveSerializer& ArchiveSerializer)
{
	FLocalizationEntryTracker LocalizationEntryTracker;

	// Find archives in the native culture-specific folder.
	const FString NativeCulturePath = SourcePath / *(NativeCulture);
	TArray<FString> NativeArchiveFileNames;
	IFileManager::Get().FindFiles(NativeArchiveFileNames, *(NativeCulturePath / TEXT("*.archive")), true, false);

	// Find archives in the culture-specific folder.
	const FString CulturePath = SourcePath / *(CultureToGenerate);
	TArray<FString> ArchiveFileNames;
	IFileManager::Get().FindFiles(ArchiveFileNames, *(CulturePath / TEXT("*.archive")), true, false);

	if(NativeArchiveFileNames.Num() == 0)
	{
		FString Message = FString::Printf(TEXT("No archives were found for native culture %s."), *(CultureToGenerate));
		UE_LOG(LogTextLocalizationResourceGenerator, Warning, TEXT("%s"), *Message);
	}

	if(ArchiveFileNames.Num() == 0)
	{
		FString Message = FString::Printf(TEXT("No archives were found for culture %s."), *(CultureToGenerate));
		UE_LOG(LogTextLocalizationResourceGenerator, Warning, TEXT("%s"), *Message);
	}

	TArray< TSharedPtr<FInternationalizationArchive> > NativeArchives;
	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);
#if 0 // @todo Json: Serializing from FArchive is currently broken
		FArchive* ArchiveFile = IFileManager::Get().CreateFileReader(*ArchiveFilePath);

		if (ArchiveFile == nullptr)
		{
			UE_LOG(LogTextLocalizationResourceGenerator, Error, TEXT("No archive found at %s."), *ArchiveFilePath);
			continue;
		}

		ArchiveSerializer.DeserializeArchive(*ArchiveFile, InternationalizationArchive);
#else
		FString ArchiveContent;

		if (!FFileHelper::LoadFileToString(ArchiveContent, *ArchiveFilePath))
		{
			UE_LOG(LogTextLocalizationResourceGenerator, Error, TEXT("Failed to load file %s."), *ArchiveFilePath);
			continue;
		}

		if (!ArchiveSerializer.DeserializeArchive(ArchiveContent, InternationalizationArchive))
		{
			UE_LOG(LogTextLocalizationResourceGenerator, Error, TEXT("Failed to serialize archive from file %s."), *ArchiveFilePath);
			continue;
		}
#endif

		NativeArchives.Add(InternationalizationArchive);
	}

	// For each archive:
	for (const FString& ArchiveFileName : ArchiveFileNames)
	{
		// Read each archive file from the culture-named directory in the source path.
		FString ArchiveFilePath = CulturePath / ArchiveFileName;
		ArchiveFilePath = FPaths::ConvertRelativePathToFull(ArchiveFilePath);
		TSharedRef<FInternationalizationArchive> InternationalizationArchive = MakeShareable(new FInternationalizationArchive);

#if 0 // @todo Json: Serializing from FArchive is currently broken
		FArchive* ArchiveFile = IFileManager::Get().CreateFileReader(*ArchiveFilePath);

		if (ArchiveFile == nullptr)
		{
			UE_LOG(LogTextLocalizationResourceGenerator, Error, TEXT("No archive found at %s."), *ArchiveFilePath);
			continue;
		}
			
		ArchiveSerializer.DeserializeArchive(*ArchiveFile, InternationalizationArchive);
#else
		FString ArchiveContent;

		if (!FFileHelper::LoadFileToString(ArchiveContent, *ArchiveFilePath))
		{
			UE_LOG(LogTextLocalizationResourceGenerator, Error, TEXT("Failed to load file %s."), *ArchiveFilePath);
			continue;
		}

		if (!ArchiveSerializer.DeserializeArchive(ArchiveContent, InternationalizationArchive))
		{
			UE_LOG(LogTextLocalizationResourceGenerator, Error, TEXT("Failed to serialize archive from file %s."), *ArchiveFilePath);
			continue;
		}
#endif

		// Generate text localization resource from manifest and archive entries.
		for(TManifestEntryByContextIdContainer::TConstIterator i = InternationalizationManifest->GetEntriesByContextIdIterator(); i; ++i)
		{
			// Gather relevant info from manifest entry.
			const TSharedRef<FManifestEntry>& ManifestEntry = i.Value();
			const FString& Namespace = ManifestEntry->Namespace;
			const FLocItem& Source = ManifestEntry->Source;
			const FString& SourceString = Source.Text;
			const FString UnescapedSourceString = SourceString;
			const uint32 SourceStringHash = FCrc::StrCrc32(*UnescapedSourceString);

			FLocalizationEntryTracker::FKeyTable& KeyTable = LocalizationEntryTracker.Namespaces.FindOrAdd(*Namespace);

			// Keeps track of the key strings of non-optional manifest entries that are missing a corresponding archive entry.
			FString MissingArchiveEntryKeyStrings;

			// Keeps track of the key strings of non-optional manifest entries that are missing a translation
			FString MissingArchiveTranslationKeyStrings;

			// Create a localization entry for each namespace and key combination.
			for(auto ContextIt = ManifestEntry->Contexts.CreateConstIterator(); ContextIt; ++ContextIt)
			{
				const FString& Key = ContextIt->Key;

				// Get proper source string to use - if the native source is different than the native translation, the native translation becomes the source.
				FLocItem SourceToUse = Source;
				if (CultureToGenerate != NativeCulture)
				{
					for (const auto& NativeArchive : NativeArchives)
					{
						if (NativeArchive.IsValid())
						{
							const TSharedPtr<FArchiveEntry> NativeArchiveEntry = NativeArchive->FindEntryBySource(Namespace, Source, ContextIt->KeyMetadataObj);
							if (NativeArchiveEntry.IsValid() && !NativeArchiveEntry->Source.IsExactMatch(NativeArchiveEntry->Translation))
							{
								SourceToUse = NativeArchiveEntry->Translation;
								break;
							}
						}
					}
				}
				// Find matching archive entry.
				TSharedPtr<FArchiveEntry> ArchiveEntry = InternationalizationArchive->FindEntryBySource(Namespace, SourceToUse, ContextIt->KeyMetadataObj);

				if(ContextIt->bIsOptional && (!ArchiveEntry.IsValid() || (ArchiveEntry.IsValid() && ArchiveEntry->Translation.Text.IsEmpty())))
				{
					// Skip any optional manifest entries that do not have a matching archive entry or if the matching archive entry does not have a translation
					continue;
				}

				FString UnescapedTranslatedString;
				if( ArchiveEntry.IsValid() )
				{
					UnescapedTranslatedString = ArchiveEntry->Translation.Text;
					
					if(UnescapedTranslatedString.IsEmpty())
					{
						if ( !MissingArchiveTranslationKeyStrings.IsEmpty() )
						{
							MissingArchiveTranslationKeyStrings += TEXT(", ");
						}
						MissingArchiveTranslationKeyStrings += Key;
					}
				}
				else
				{
					// If we're not using the actual source string, then embed the fake source string as the foreign translation when we have no actual foreign translation.
					if (!Source.IsExactMatch(SourceToUse))
					{
						UnescapedTranslatedString = SourceToUse.Text;
					}

					if ( !MissingArchiveEntryKeyStrings.IsEmpty() )
					{
						MissingArchiveEntryKeyStrings += TEXT(", ");
					}
					MissingArchiveEntryKeyStrings += Key;
				}

				FLocalizationEntryTracker::FEntryArray& EntryArray = KeyTable.FindOrAdd(*Key);
				if(ArchiveEntry.IsValid())
				{
					FLocalizationEntryTracker::FEntry NewEntry;
					NewEntry.ArchiveName = ArchiveFilePath;
					NewEntry.LocalizedString = UnescapedTranslatedString;
					NewEntry.SourceStringHash = SourceStringHash;
					EntryArray.Add(NewEntry);
				}
			}

			if(!MissingArchiveEntryKeyStrings.IsEmpty())
			{
				FString KeyListString = FString::Printf(TEXT("[%s]"), *MissingArchiveEntryKeyStrings);
				FString Message = FString::Printf( TEXT("Archive (%s) contains no translation for entry (Namespace:%s, Source:%s) for keys: %s."), *ArchiveFilePath, *Namespace, *SourceString, *KeyListString);
				UE_LOG(LogTextLocalizationResourceGenerator, Verbose, TEXT("%s"), *Message);
			}

			if(!MissingArchiveTranslationKeyStrings.IsEmpty())
			{
				FString KeyListString = FString::Printf(TEXT("[%s]"), *MissingArchiveTranslationKeyStrings);
				FString Message = FString::Printf( TEXT("Archive (%s) contains empty translation for entry (Namespace:%s, Source:%s) with keys: %s."), *ArchiveFilePath, *Namespace, *SourceString, *KeyListString);
				UE_LOG(LogTextLocalizationResourceGenerator, Verbose, TEXT("%s"), *Message);
			}
		}
	}

	LocalizationEntryTracker.ReportCollisions();

	// Write resource.
	if( !(LocalizationEntryTracker.WriteToArchive(DestinationArchive)) )
	{
		UE_LOG(LogTextLocalizationResourceGenerator, Error, TEXT("Failed to write localization entries to archive."));
		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;
		}

		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;
}
/**
*	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;
}
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;
}
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;
}
bool UInternationalizationConditioningCommandlet::ProcessArchive( const FString& PrimaryLangExt, const FString& SourcePath, const FString& DestinationPath )
{
	FString ArchiveName = TEXT("Archive.txt");
	TArray<FString> LanguagesToProcess;
	TArray<FString> TargetCultures;
	bool bAppendToExistingArchive = true;

	GetConfigString( *SectionName, TEXT("ArchiveName"), ArchiveName, GatherTextConfigPath );
	GetConfigArray( *SectionName, TEXT("ProcessLanguage"), LanguagesToProcess, GatherTextConfigPath );
	GetConfigArray( *SectionName, TEXT("TargetCulture"), TargetCultures, GatherTextConfigPath );
	GetConfigBool( *SectionName, TEXT("bAppendToExistingArchive"), bAppendToExistingArchive, GatherTextConfigPath );

	// Build info about the primary language
	TArray<FString> PrimaryFilenames;
	TArray<FString> PathPrimaryFilenames;
	FString PrimaryLocDirectory = SourcePath / PrimaryLangExt + TEXT("/");
	FString PrimaryWildcardName = PrimaryLocDirectory + TEXT("*.") + PrimaryLangExt;
	// Grab the list of primary language loc files
	IFileManager::Get().FindFiles(PathPrimaryFilenames, *PrimaryWildcardName, true, false);
	for ( int32 FileIndex = 0; FileIndex < PathPrimaryFilenames.Num(); FileIndex++ )
	{
		FString* CompleteFilename = new(PrimaryFilenames) FString(PrimaryLocDirectory + PathPrimaryFilenames[FileIndex]);
	}

	if ( PrimaryFilenames.Num() == 0 )
	{
		UE_LOG(LogInternationalizationConditioningCommandlet, Warning, TEXT("No primary language(%s) loc files found!"), *PrimaryLangExt);
		return false;
	}

	for( int32 LanguageIndex = 0; LanguageIndex < LanguagesToProcess.Num(); LanguageIndex++ )
	{
		FString ForeignLangExt = LanguagesToProcess[LanguageIndex];
		TArray<FString> ForeignFilenames;
		TArray<FString> PathForeignFilenames;
		FString ForeignLocDirectory = SourcePath / ForeignLangExt + TEXT("/");
		FString ForeignWildcardName = ForeignLocDirectory + TEXT("*.") + ForeignLangExt;
		FString TargetSubfolder = TargetCultures.Num() > LanguageIndex ? TargetCultures[LanguageIndex] : ForeignLangExt;

		// Get a list of foreign loc files
		IFileManager::Get().FindFiles(PathForeignFilenames, *ForeignWildcardName, true, false);

		for ( int32 FileIndex = 0; FileIndex < PathForeignFilenames.Num(); FileIndex++ )
		{
			FString* CompleteFilename = new(ForeignFilenames) FString(ForeignLocDirectory + PathForeignFilenames[FileIndex]);
		}

		if ( ForeignFilenames.Num() == 0 )
		{
			UE_LOG(LogInternationalizationConditioningCommandlet, Warning, TEXT("No foreign loc files found using language extension '%s'"), *ForeignLangExt);
			continue;
		}

		ReadLocFiles(PrimaryFilenames, ForeignFilenames);

		TArray<FLocalizationFileEntry> ArchiveProperties;

		// FSor each file in the list, 
		for ( int32 i = 0; i < LocPairs.Num(); i++ )
		{
			FLocalizationFilePair& Pair = LocPairs[i];
			Pair.CompareFiles();

			Pair.GetTranslatedProperties( ArchiveProperties );
			Pair.GetIdenticalProperties( ArchiveProperties );

		}

		TSharedRef< FInternationalizationArchive > InternationalizationArchive = MakeShareable( new FInternationalizationArchive );
		FInternationalizationArchiveJsonSerializer ArchiveSerializer;

		const FString DestinationArchiveFileName = DestinationPath / TargetSubfolder / ArchiveName;

		// If we want to append to an existing archive, we first read it into our data structure
		if( bAppendToExistingArchive )
		{
			FString ExistingArchiveFileName = DestinationArchiveFileName;

			if( FPaths::FileExists(ExistingArchiveFileName) )
			{
				TSharedPtr< FJsonObject > ExistingArchiveJsonObject = ReadJSONTextFile( ExistingArchiveFileName );
				if( ExistingArchiveJsonObject.IsValid() )
				{
					ArchiveSerializer.DeserializeArchive( ExistingArchiveJsonObject.ToSharedRef(), InternationalizationArchive );
				}
			}
		}

		for( int PropIndex = 0; PropIndex < ArchiveProperties.Num(); PropIndex++ )
		{
			FLocalizationFileEntry& Prop = ArchiveProperties[PropIndex];
			FString NewNamespace = Prop.Namespace;

			FLocItem Source( Prop.SourceText );
			FLocItem Translation( Prop.TranslatedText );

			if( !InternationalizationArchive->AddEntry( NewNamespace, Source, Translation, NULL, false ) )
			{
				TSharedPtr<FArchiveEntry> ExistingConflictEntry = InternationalizationArchive->FindEntryBySource( NewNamespace, Source, NULL );

				if( !ExistingConflictEntry.IsValid() )
				{
					// Looks like we failed to add for a reason beyond conflicting translation, display an error and continue.
					UE_LOG(LogInternationalizationConditioningCommandlet, Warning, TEXT("Failed to add entry to archive Namespace [%s]: (DEFAULT TEXT): %s (EXISTING TRANSLATION): %s"), 
						*NewNamespace, *Prop.SourceText, *ExistingConflictEntry->Translation.Text );
					continue;
				}

				// If we can't add the entry, we find the existing conflicting entry and see if the translation is empty.  If it is empty we will
				// just overwrite the translation.  If it is not empty we will display info about the conflict.
				if( ExistingConflictEntry->Translation.Text.IsEmpty() )
				{
					ExistingConflictEntry->Translation.Text =  Prop.TranslatedText;
				}
				else
				{
					UE_LOG(LogInternationalizationConditioningCommandlet, Warning, TEXT("Conflicting translation ignored in Namespace [%s]: (DEFAULT TEXT): %s (EXISTING TRANSLATION): %s  (REJECTED TRANSLATION): %s"), 
						*NewNamespace, *Prop.SourceText, *ExistingConflictEntry->Translation.Text, *Prop.TranslatedText );
				}
			}
		}

		TSharedRef<FJsonObject> FinalArchiveJsonObj = MakeShareable( new FJsonObject );
		ArchiveSerializer.SerializeArchive( InternationalizationArchive, FinalArchiveJsonObj );

		WriteJSONToTextFile( FinalArchiveJsonObj, DestinationArchiveFileName, SourceControlInfo );

		LocPairs.Empty();
	}
	return true;
}