bool FCollection::CheckinCollection(FText& OutError)
{
	if ( !ensure(SourceFilename.Len()) )
	{
		OutError = LOCTEXT("Error_Internal", "There was an internal error.");
		return false;
	}

	ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
	if ( !ISourceControlModule::Get().IsEnabled() )
	{
		OutError = LOCTEXT("Error_SCCDisabled", "Source control is not enabled. Enable source control in the preferences menu.");
		return false;
	}

	if ( !SourceControlProvider.IsAvailable() )
	{
		OutError = LOCTEXT("Error_SCCNotAvailable", "Source control is currently not available. Check your connection and try again.");
		return false;
	}

	const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SourceFilename);
	FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);

	if (SourceControlState.IsValid() && !SourceControlState->IsSourceControlled())
	{
		// Not yet in the depot. Add it.
		const bool bWasAdded = (SourceControlProvider.Execute(ISourceControlOperation::Create<FMarkForAdd>(), AbsoluteFilename) == ECommandResult::Succeeded);
		if (!bWasAdded)
		{
			OutError = FText::Format(LOCTEXT("Error_SCCAdd", "Failed to add collection '{0}' to source control."), FText::FromName(CollectionName));
			return false;
		}
		SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
	}

	if ( SourceControlState.IsValid() && !(SourceControlState->IsCheckedOut() || SourceControlState->IsAdded()) )
	{
		OutError = FText::Format(LOCTEXT("Error_SCCNotCheckedOut", "Collection '{0}' not checked out or open for add."), FText::FromName(CollectionName));
		return false;
	}

	// Form an appropriate summary for the changelist
	const FText CollectionNameText = FText::FromName( CollectionName );
	FTextBuilder ChangelistDescBuilder;

	if (SourceControlState.IsValid() && SourceControlState->IsAdded())
	{
		ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionAddedNewDesc", "Added collection '{0}'"), CollectionNameText);
	}
	else
	{
		if (IsDynamic())
		{
			// @todo collection Change description for dynamic collections
		}
		else
		{
			// Gather differences from disk
			TArray<FName> ObjectsAdded;
			TArray<FName> ObjectsRemoved;
			GetObjectDifferencesFromDisk(ObjectsAdded, ObjectsRemoved);

			ObjectsAdded.Sort();
			ObjectsRemoved.Sort();

			// Report added files
			FFormatNamedArguments Args;
			Args.Add(TEXT("FirstObjectAdded"), ObjectsAdded.Num() > 0 ? FText::FromName(ObjectsAdded[0]) : NSLOCTEXT("Core", "None", "None"));
			Args.Add(TEXT("NumberAdded"), FText::AsNumber(ObjectsAdded.Num()));
			Args.Add(TEXT("FirstObjectRemoved"), ObjectsRemoved.Num() > 0 ? FText::FromName(ObjectsRemoved[0]) : NSLOCTEXT("Core", "None", "None"));
			Args.Add(TEXT("NumberRemoved"), FText::AsNumber(ObjectsRemoved.Num()));
			Args.Add(TEXT("CollectionName"), CollectionNameText);

			if (ObjectsAdded.Num() == 1)
			{
				ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionAddedSingleDesc", "Added '{FirstObjectAdded}' to collection '{CollectionName}'"), Args);
			}
			else if (ObjectsAdded.Num() > 1)
			{
				ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionAddedMultipleDesc", "Added {NumberAdded} objects to collection '{CollectionName}':"), Args);

				ChangelistDescBuilder.Indent();
				for (const FName& AddedObjectName : ObjectsAdded)
				{
					ChangelistDescBuilder.AppendLine(FText::FromName(AddedObjectName));
				}
				ChangelistDescBuilder.Unindent();
			}

			if ( ObjectsRemoved.Num() == 1 )
			{
				ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionRemovedSingleDesc", "Removed '{FirstObjectRemoved}' from collection '{CollectionName}'"), Args);
			}
			else if (ObjectsRemoved.Num() > 1)
			{
				ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionRemovedMultipleDesc", "Removed {NumberRemoved} objects from collection '{CollectionName}'"), Args);

				ChangelistDescBuilder.Indent();
				for (const FName& RemovedObjectName : ObjectsRemoved)
				{
					ChangelistDescBuilder.AppendLine(FText::FromName(RemovedObjectName));
				}
				ChangelistDescBuilder.Unindent();
			}
		}

		// Parent change?
		if (DiskSnapshot.ParentCollectionGuid != ParentCollectionGuid)
		{
			ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionChangedParentDesc", "Changed the parent of collection '{0}'"), CollectionNameText);
		}

		// Version bump?
		if (FileVersion < ECollectionVersion::CurrentVersion)
		{
			ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionUpgradedDesc", "Upgraded collection '{0}' (was version {1}, now version {2})"), CollectionNameText, FText::AsNumber(FileVersion), FText::AsNumber(ECollectionVersion::CurrentVersion));
		}
	}

	FText ChangelistDesc = ChangelistDescBuilder.ToText();
	if (ChangelistDesc.IsEmpty())
	{
		// No changes could be detected
		ChangelistDesc = FText::Format(LOCTEXT("CollectionNotModifiedDesc", "Collection '{0}' not modified"), CollectionNameText);
	}

	// Finally check in the file
	TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOperation = ISourceControlOperation::Create<FCheckIn>();
	CheckInOperation->SetDescription( ChangelistDesc );
	if ( SourceControlProvider.Execute( CheckInOperation, AbsoluteFilename ) )
	{
		return true;
	}
	else 
	{
		OutError = FText::Format(LOCTEXT("Error_SCCCheckIn", "Failed to check in collection '{0}'."), FText::FromName(CollectionName));
		return false;
	}
}
bool LocalizationCommandletTasks::ReportLoadedAudioAssets(const TArray<ULocalizationTarget*>& Targets, const TOptional<FString>& CultureName)
{
	TSet<FString> LoadedDialogueWaveAssets;
	TSet<FString> LoadedSoundWaveAssets;

	for (const ULocalizationTarget* Target : Targets)
	{
		const FString RootAssetPath = Target->IsMemberOfEngineTargetSet() ? TEXT("/Engine") : TEXT("/Game");

		TArray<FString> CulturesToTest;
		{
			if (CultureName.IsSet())
			{
				CulturesToTest.Add(CultureName.GetValue());
			}
			else
			{
				CulturesToTest.Reserve(Target->Settings.SupportedCulturesStatistics.Num());
				for (const FCultureStatistics& CultureData : Target->Settings.SupportedCulturesStatistics)
				{
					CulturesToTest.Add(CultureData.CultureName);
				}
			}
		}

		TArray<FString> DialogueWavePathsToTest;
		TArray<FString> SoundWavePathsToTest;
		{
			const FString NativeCulture = Target->Settings.SupportedCulturesStatistics.IsValidIndex(Target->Settings.NativeCultureIndex) ? Target->Settings.SupportedCulturesStatistics[Target->Settings.NativeCultureIndex].CultureName : FString();
			const bool bImportNativeAsSource = Target->Settings.ImportDialogueSettings.bImportNativeAsSource && !NativeCulture.IsEmpty();
			if (bImportNativeAsSource)
			{
				DialogueWavePathsToTest.Add(RootAssetPath);
				SoundWavePathsToTest.Add(RootAssetPath / Target->Settings.ImportDialogueSettings.ImportedDialogueFolder);
			}

			for (const FString& Culture : CulturesToTest)
			{
				if (bImportNativeAsSource && Culture == NativeCulture)
				{
					continue;
				}

				DialogueWavePathsToTest.Add(RootAssetPath / TEXT("L10N") / Culture);
				SoundWavePathsToTest.Add(RootAssetPath / TEXT("L10N") / Culture / Target->Settings.ImportDialogueSettings.ImportedDialogueFolder);
			}
		}

		ForEachObjectOfClass(UDialogueWave::StaticClass(), [&](UObject* InObject)
		{
			const FString ObjectPath = InObject->GetPathName();

			auto FindAssetPathPredicate = [&](const FString& InAssetPath) -> bool
			{
				return ObjectPath.StartsWith(InAssetPath, ESearchCase::IgnoreCase);
			};

			if (DialogueWavePathsToTest.ContainsByPredicate(FindAssetPathPredicate))
			{
				LoadedDialogueWaveAssets.Add(ObjectPath);
			}
		});

		ForEachObjectOfClass(USoundWave::StaticClass(), [&](UObject* InObject)
		{
			const FString ObjectPath = InObject->GetPathName();

			auto FindAssetPathPredicate = [&](const FString& InAssetPath) -> bool
			{
				return ObjectPath.StartsWith(InAssetPath, ESearchCase::IgnoreCase);
			};

			if (SoundWavePathsToTest.ContainsByPredicate(FindAssetPathPredicate))
			{
				LoadedSoundWaveAssets.Add(ObjectPath);
			}
		});
	}

	if (LoadedDialogueWaveAssets.Num() > 0 || LoadedSoundWaveAssets.Num() > 0)
	{
		FTextBuilder MsgBuilder;
		MsgBuilder.AppendLine(LOCTEXT("Warning_LoadedAudioAssetsMsg", "The following audio assets have been loaded by the editor and may cause the dialogue import to fail as their files will be read-only."));
		MsgBuilder.AppendLine(FText::GetEmpty());
		MsgBuilder.AppendLine(LOCTEXT("Warning_LoadedAudioAssetsMsg_Continue", "Do you want to continue?"));
				
		if (LoadedDialogueWaveAssets.Num() > 0)
		{
			MsgBuilder.AppendLine(FText::GetEmpty());
			MsgBuilder.AppendLine(LOCTEXT("Warning_LoadedAudioAssetsMsg_DialogueWaves", "Dialogue Waves:"));

			MsgBuilder.Indent();
			for (const FString& LoadedDialogueWaveAsset : LoadedDialogueWaveAssets)
			{
				MsgBuilder.AppendLine(LoadedDialogueWaveAsset);
			}
			MsgBuilder.Unindent();
		}

		if (LoadedSoundWaveAssets.Num() > 0)
		{
			MsgBuilder.AppendLine(FText::GetEmpty());
			MsgBuilder.AppendLine(LOCTEXT("Warning_LoadedAudioAssetsMsg_SoundWaves", "Sound Waves:"));

			MsgBuilder.Indent();
			for (const FString& LoadedSoundWaveAsset : LoadedSoundWaveAssets)
			{
				MsgBuilder.AppendLine(LoadedSoundWaveAsset);
			}
			MsgBuilder.Unindent();
		}

		const FText MsgTitle = LOCTEXT("Warning_LoadedAudioAssetsTitle", "Warning - Loaded Audio Assets");
		return FMessageDialog::Open(EAppMsgType::YesNo, MsgBuilder.ToText(), &MsgTitle) == EAppReturnType::Yes;
	}

	return true;
}