From 65cb84f668f7afc821273e6cc17a8b1f51d54722 Mon Sep 17 00:00:00 2001
From: SMAP-git <333812182+SMAP-git@users.noreply.github.com>
Date: Fri, 13 Nov 2026 11:52:21 +0100
Subject: [PATCH 1/2] Brazilian to European Portuguese v1.9.0: reference-guided
idioms, possession and recipients
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Reference-confirmed fishing and literal idioms, police-place vocabulary,
ongoing actions with estar a, revenge and reply senses, prank senses,
pronominal forgetting, como se hypothetical comparisons, inalienable
possession with dative clitics and bare articles, telling-verb recipients,
reference second person after initial Não, arrival referents, parecer que
complements and a reviewed transport and mechanics conversion table.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../BigCompareObjectTests.cs | 6 +-
.../ConstructionTests.cs | 1 -
.../ContextualComplementsTests.cs | 191 +++++
.../ConverterTests.cs | 29 +-
.../CorpusConstructionTests.cs | 536 +++++++++++++
.../DurationAndWrappingTests.cs | 217 +++++
.../EmphaticAndPolicyDialogueTests.cs | 179 +++++
.../EnglishExpressionTests.cs | 125 +++
.../ExplanationTests.cs | 3 +-
.../FormalPersonalObjectTests.cs | 139 ++++
.../InterrogationAgreementTests.cs | 20 +
.../LocalChecksTests.cs | 14 +
.../PersonalObjectTests.cs | 2 +-
.../PorFamilyCliticTests.cs | 119 +++
.../ReferenceTests.cs | 153 +++-
.../ReviewedEpisodeDialogueTests.cs | 177 +++++
.../SeeingObjectTests.cs | 135 ++++
.../TemporalCliticTests.cs | 116 +++
.../TransportVocabularyTests.cs | 111 +++
.../AttractedEnclisis.cs | 14 +-
.../AutomaticGrammar.cs | 13 +
.../BrazilianToEuropeanPortuguese.csproj | 2 +
.../ContextualComplements.cs | 144 ++++
.../ConversionConstructions.cs | 745 +++++++++++++++++-
.../LocalChecks.cs | 4 +-
.../MainWindow.axaml | 12 +-
.../MainWindow.axaml.cs | 2 +-
.../MaybeSubjunctive.cs | 5 +-
.../PortugueseConverter.cs | 336 ++++++--
.../PortugueseVerbs.cs | 29 +-
.../PortugueseWordList.xml | 116 ++-
se5/BrazilianToEuropeanPortuguese/README.md | 315 +++++++-
.../README.pt-PT.md | 329 +++++++-
.../ReferenceSubtitle.cs | 47 +-
.../ReviewedDialogue.cs | 299 +++++++
.../SubtitleConversion.cs | 5 +
.../TransportVocabulary.cs | 47 ++
.../TransportVocabulary.pt-PT.md | 132 ++++
se5/BrazilianToEuropeanPortuguese/plugin.json | 2 +-
39 files changed, 4773 insertions(+), 98 deletions(-)
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/ContextualComplementsTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/DurationAndWrappingTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/EmphaticAndPolicyDialogueTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/FormalPersonalObjectTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/PorFamilyCliticTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/ReviewedEpisodeDialogueTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/SeeingObjectTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/TemporalCliticTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese.Tests/TransportVocabularyTests.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese/ContextualComplements.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese/TransportVocabulary.cs
create mode 100644 se5/BrazilianToEuropeanPortuguese/TransportVocabulary.pt-PT.md
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/BigCompareObjectTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/BigCompareObjectTests.cs
index 2582b5ec..43672215 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/BigCompareObjectTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/BigCompareObjectTests.cs
@@ -251,7 +251,7 @@ public void AttentionDistanceUsesItsAlignedEnglishTarget(string input, string? e
[InlineData("Ele esqueceu, talvez.", "Ele esqueceu-se, talvez.")]
[InlineData("Talvez porque ele sabe.", "Talvez porque ele sabe.")]
[InlineData("Talvez parte disso seja verdade.", "Talvez parte disso seja verdade.")]
- [InlineData("Espero que ele tenha esquecido o nome.", "Espero que ele tenha esquecido o nome.")]
+ [InlineData("Espero que ele tenha esquecido o nome.", "Espero que ele se tenha esquecido do nome.")]
[InlineData("Espero que você não tenha esquecido de mim.", "Espero que não te tenhas esquecido de mim.")]
[InlineData("É possível que ele tenha-lhe mentido.", "É possível que ele lhe tenha mentido.")]
public void MaybeBeforeTheVerbTakesTheSubjunctive(string input, string expected)
@@ -333,7 +333,7 @@ public void MatchPeriodsUsePortugueseNames(string input, string english, string
[InlineData("Eu tinha esquecido do nome dela.", "Eu tinha-me esquecido do nome dela.")]
[InlineData("Você tinha esquecido de mim?", "Tinhas-te esquecido de mim?")]
[InlineData("Ela teria esquecido de tudo.", "Ela ter-se-ia esquecido de tudo.")]
- [InlineData("Ele tinha esquecido o nome.", "Ele tinha esquecido o nome.")]
+ [InlineData("Ele tinha esquecido o nome.", "Ele tinha-se esquecido do nome.")]
[InlineData("Achei que ele tinha esquecido de mim.", "Achei que ele se tinha esquecido de mim.")]
[InlineData("O que ele tinha esquecido?", "O que ele tinha esquecido?")]
[InlineData("Eles tinham esquecido de trancar a porta.", "Eles tinham-se esquecido de trancar a porta.")]
@@ -418,7 +418,7 @@ public void BlueBloodsLeftoversUseEuropeanConstructions(string input, string exp
[InlineData("És bom e ganha sempre.", "És bom e ganhas sempre.")]
[InlineData("Só não ferva minhas bolas de novo.", "Só não fervas as minhas bolas de novo.")]
[InlineData("Não toque minhas bolas.", "Não toques nas minhas bolas.")]
- [InlineData("Não toque a minha mão.", "Não toques na minha mão.")]
+ [InlineData("Não toque a minha mão.", "Não me toques na mão.")]
[InlineData("Ele tocou sua música.", "Ele tocou a sua música.")]
[InlineData("Não corte a corda.", "Não cortes a corda.")]
[InlineData("O corte foi fundo.", "O corte foi fundo.")]
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/ConstructionTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/ConstructionTests.cs
index 947ea05d..026af3f7 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/ConstructionTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/ConstructionTests.cs
@@ -305,7 +305,6 @@ public void IdiomsNeedBothPortugueseConstructionAndEnglishEvidence(string source
[Theory]
[InlineData("Pode confiar em mim.")]
- [InlineData("Esqueci o nome.")]
[InlineData("Ele lembra o pai.")]
[InlineData("Vira à esquerda.")]
[InlineData("Ele virou a página.")]
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/ContextualComplementsTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/ContextualComplementsTests.cs
new file mode 100644
index 00000000..f71a6c99
--- /dev/null
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/ContextualComplementsTests.cs
@@ -0,0 +1,191 @@
+using System.Text.RegularExpressions;
+using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese;
+using SubtitleEdit.Plugins.Shared;
+
+namespace BrazilianToEuropeanPortuguese.Tests;
+
+public sealed class ContextualComplementsTests
+{
+ [Theory]
+ [InlineData("Bem, parece o juiz caiu do telhado.", "Bem, parece que o juiz caiu do telhado.")]
+ [InlineData("Parece ela chegou.", "Parece que ela chegou.")]
+ [InlineData("Parecia os agentes chegaram.", "Parecia que os agentes chegaram.")]
+ [InlineData("Pareceu a testemunha mentiu.", "Pareceu que a testemunha mentiu.")]
+ [InlineData("Parece-me o juiz caiu.", "Parece-me que o juiz caiu.")]
+ [InlineData("Não parece a testemunha mentiu.", "Não parece que a testemunha mentiu.")]
+ [InlineData("Parece a minha nova equipa não se opõe.", "Parece que a minha nova equipa não se opõe.")]
+ [InlineData("Parece o presidente da câmara caiu.", "Parece que o presidente da câmara caiu.")]
+ [InlineData("Parece ela o viu.", "Parece que ela o viu.")]
+ [InlineData("Parecerá o juiz foi enganado.", "Parecerá que o juiz foi enganado.")]
+ [InlineData("Pareceria o juiz tinha razão.", "Pareceria que o juiz tinha razão.")]
+ [InlineData("Não parece a testemunha disse tudo.", "Não parece que a testemunha disse tudo.")]
+ [InlineData("Parece ela chegou e parece ele saiu.", "Parece que ela chegou e parece que ele saiu.")]
+ [InlineData("PARECE ELA CHEGOU.", "PARECE QUE ELA CHEGOU.")]
+ [InlineData("Parece ela chegou.", "Parece que ela chegou.")]
+ public void FiniteComplementsRecoverQueAndRemainStable(string source, string expected)
+ {
+ var converter = new PortugueseConverter(new ConversionOptions(YouToTu: false));
+ var result = converter.Convert(source);
+ Assert.Equal(expected, result);
+ Assert.Equal(result, converter.Convert(result));
+ for (var i = 0; i < source.Length; i++)
+ {
+ if (source[i] != ' ') continue;
+ foreach (var newline in new[] { "\n", "\r\n" })
+ {
+ var wrapped = converter.Convert(source[..i] + newline + source[(i + 1)..]);
+ Assert.Equal(expected, Regex.Replace(wrapped, @"\s+", " "));
+ Assert.Equal(1, wrapped.Count(c => c == '\n'));
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData("Bem, parece o juiz.")]
+ [InlineData("Parece o juiz que caiu.")]
+ [InlineData("Parece o juiz reformado.")]
+ [InlineData("Parece a minha mãe.")]
+ [InlineData("Parece uma testemunha credível.")]
+ [InlineData("Parece um dia lento no escritório.")]
+ [InlineData("O juiz parece estar cansado.")]
+ [InlineData("Parece ter caído.")]
+ [InlineData("Parece que o juiz caiu.")]
+ [InlineData("Parece ela chegar.")]
+ [InlineData("Parece o juiz e o advogado.")]
+ [InlineData("Parece o juiz do caso.")]
+ [InlineData("Parece o que aconteceu.")]
+ [InlineData("Parece um rapaz seguro.")]
+ [InlineData("Parece um homem livre.")]
+ [InlineData("Parece um homem são.")]
+ [InlineData("-Parece o juiz.\n-Caiu do telhado.")]
+ public void ResemblanceInfinitivesRelativesAndDifferentSpeakersAreProtected(string source) =>
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(YouToTu: false)).Convert(source));
+
+ [Fact]
+ public void SplitFiniteComplementsUseOnlyARecentSelectedSameSpeakerContinuation()
+ {
+ var blocks = new[] {
+ new SrtBlock { StartMs = 297064, EndMs = 299887, Text = "Bem, parece o Juiz" },
+ new SrtBlock { StartMs = 299900, EndMs = 302191, Text = "caiu de cabeça\ndo telhado." },
+ };
+ var options = new ConversionOptions(YouToTu: false);
+ var result = SubtitleConversion.Convert(blocks, options);
+ Assert.Equal("Bem, parece que o Juiz", result[0].Text);
+ Assert.Equal("caiu de cabeça\ndo telhado.", result[1].Text);
+ Assert.Equal("Bem, parece o Juiz", SubtitleConversion.Convert(blocks, options, selected: new HashSet { 0 })[0].Text);
+ Assert.Equal("Bem, parece o Juiz", SubtitleConversion.Convert(blocks, options, speakers: new[] { "A", "B" })[0].Text);
+ blocks[1].StartMs = 310000;
+ Assert.Equal("Bem, parece o Juiz", SubtitleConversion.Convert(blocks, options)[0].Text);
+ Assert.Equal("Bem, parece o Juiz", blocks[0].Text);
+ Assert.Equal(299887, blocks[0].EndMs);
+ }
+
+ [Theory]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Mande-a entrar.")]
+ [InlineData("Ele está aqui.", "Mande entrar.", "Mande-o entrar.")]
+ [InlineData("Elas estão aqui.", "Mande entrar.", "Mande-as entrar.")]
+ [InlineData("Eles estão aqui.", "Mande entrar.", "Mande-os entrar.")]
+ [InlineData("Ela chegou.", "Deixe entrar.", "Deixe-a entrar.")]
+ [InlineData("Ela está à espera.", "Mande subir.", "Mande-a subir.")]
+ [InlineData("Ela está aqui.", "Não mande entrar.", "Não a mande entrar.")]
+ [InlineData("ELA ESTÁ AQUI.", "MANDE ENTRAR.", "MANDE-A ENTRAR.")]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Mande-a entrar.")]
+ public void ExplicitArrivalReferentsControlGenderAndNumberWithoutChangingCommandPerson(
+ string previous, string command, string expected)
+ {
+ var blocks = Pair(previous, command);
+ var options = new ConversionOptions(Address: AddressMode.Formal);
+ var result = SubtitleConversion.Convert(blocks, options);
+ Assert.Equal(expected, result[1].Text);
+ blocks[1].Text = result[1].Text;
+ Assert.Equal(expected, SubtitleConversion.Convert(blocks, options)[1].Text);
+ }
+
+ [Theory]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Let her in.", "Mande-a entrar.")]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Let's have her.", "Mande-a entrar.")]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Send him in.", "Mande entrar.")]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Send her and him in.", "Mande entrar.")]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Let her out.", "Mande entrar.")]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Send money in.", "Mande entrar.")]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Her father is coming.", "Mande entrar.")]
+ [InlineData("Ela está aqui.", "Não mande entrar.", "Don't let her in.", "Não a mande entrar.")]
+ [InlineData("Ela está aqui.", "Mande entrar.", "Don't let her in.", "Mande entrar.")]
+ [InlineData("Ela está aqui.", "Não mande entrar.", "Let her in.", "Não mande entrar.")]
+ [InlineData("Ela está aqui, com ele.", "Mande entrar.", "Send her in.", "Mande entrar.")]
+ [InlineData("Ela não está aqui.", "Mande entrar.", "Send her in.", "Mande entrar.")]
+ [InlineData("Ele disse que ela está aqui.", "Mande entrar.", "Send her in.", "Mande entrar.")]
+ [InlineData("Ela está aqui.", "Mande o agente entrar.", "Send him in.", "Mande o agente entrar.")]
+ [InlineData("Ela está aqui.", "Mande-a entrar.", "Send her in.", "Mande-a entrar.")]
+ [InlineData("Ela está aqui.", "Mande entrar o agente.", "Send him in.", "Mande entrar o agente.")]
+ [InlineData("Ela está aqui.", "Mande entrar dinheiro.", "Send money in.", "Mande entrar dinheiro.")]
+ public void ConflictsMultipleReferentsAndExpressedObjectsAreProtected(
+ string previous, string command, string english, string expected) =>
+ Assert.Equal(expected, SubtitleConversion.Convert(Pair(previous, command),
+ new ConversionOptions(Address: AddressMode.Formal), english: new string?[] { null, english })[1].Text);
+
+ [Fact]
+ public void ContextRecoveryRespectsSettingsSelectionAndLocalOverrides()
+ {
+ var blocks = Pair("Ela está aqui.", "Mande entrar.");
+ var options = new ConversionOptions(Address: AddressMode.Formal);
+ foreach (var disabled in new[] { options with { Pronouns = false }, options with { DisabledRules = "arrival-object" } })
+ Assert.Equal("Mande entrar.", SubtitleConversion.Convert(blocks, disabled)[1].Text);
+ Assert.Equal("Mande entrar.", SubtitleConversion.Convert(blocks, options, selected: new HashSet { 1 })[1].Text);
+ Assert.Equal("Manda-a entrar.", SubtitleConversion.Convert(blocks, options with { Address = AddressMode.Informal })[1].Text);
+ blocks[1].StartMs = 10000;
+ Assert.Equal("Mande entrar.", SubtitleConversion.Convert(blocks, options)[1].Text);
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("Mande entrar");
+ Assert.Equal("Mande entrar.", SubtitleConversion.Convert(Pair("Ela está aqui.", "Mande entrar."), options, words)[1].Text);
+ foreach (var disabled in new[] { new ConversionOptions(Colloquial: false),
+ new ConversionOptions(DisabledRules: "parecer-complement") })
+ Assert.Equal("Parece ela chegou.", new PortugueseConverter(disabled).Convert("Parece ela chegou."));
+ words.Ignored.Clear();
+ words.Ignored.Add("parece");
+ Assert.Equal("Parece ela chegou.", new PortugueseConverter(localWordList: words).Convert("Parece ela chegou."));
+ words.Ignored.Clear();
+ words.Words.Add(("parece", "aparenta"));
+ Assert.Equal("Aparenta ela chegou.", new PortugueseConverter(localWordList: words).Convert("Parece ela chegou."));
+ words.Words.Clear();
+ words.Words.Add(("mande", "peça"));
+ Assert.Equal("Peça entrar.", SubtitleConversion.Convert(Pair("Ela está aqui.", "Mande entrar."), options, words)[1].Text);
+ }
+
+ [Theory]
+ [InlineData("Mande", "entrar", "Mande-a entrar.")]
+ [InlineData("Mandem", "sair", "Mandem-na sair.")]
+ [InlineData("Deixem", "passar", "Deixem-na passar.")]
+ [InlineData("Deixe", "esperar", "Deixe-a esperar.")]
+ [InlineData("Mande", "descer", "Mande-a descer.")]
+ public void CommandsPreserveWrappingSoundChangesAndReviewNotes(string verb, string infinitive, string expected)
+ {
+ foreach (var newline in new[] { " ", "\n", "\r\n" })
+ {
+ var blocks = Pair("Ela está" + newline + "aqui.", verb + newline + infinitive + ".");
+ var result = SubtitleConversion.Convert(blocks, new ConversionOptions(Address: AddressMode.Formal));
+ Assert.Equal(expected, Regex.Replace(result[1].Text, @"\s+", " "));
+ Assert.Equal(newline.Count(c => c == '\n'), result[1].Text.Count(c => c == '\n'));
+ Assert.Contains("referent", result[1].ContextNote);
+ }
+ }
+
+ [Theory]
+ [InlineData("caiu.", "Parece que o juiz")]
+ [InlineData("não caiu.", "Parece que o juiz")]
+ [InlineData("se opõe.", "Parece que o juiz")]
+ [InlineData("-Caiu.", "Parece o juiz")]
+ [InlineData("parece cansado.", "Parece o juiz")]
+ public void ContinuationsRequireACompleteSubjectInTheOriginalCue(string next, string expected)
+ {
+ var blocks = Pair("Parece o juiz", next);
+ blocks[1].StartMs = 2500;
+ Assert.Equal(expected, SubtitleConversion.Convert(blocks, new ConversionOptions(YouToTu: false))[0].Text);
+ }
+
+ private static SrtBlock[] Pair(string previous, string command) =>
+ [
+ new SrtBlock { StartMs = 1000, EndMs = 2400, Text = previous },
+ new SrtBlock { StartMs = 4000, EndMs = 5500, Text = command },
+ ];
+}
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/ConverterTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/ConverterTests.cs
index 63983835..345d3417 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/ConverterTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/ConverterTests.cs
@@ -171,7 +171,7 @@ public void ScreenshotTellThingConvertsWithBothOrthographies(bool postAgreement)
[InlineData("Vou contar-te uma parada.", "Vou contar-te uma coisa.")]
[InlineData("Vou-te contar uma parada.", "Vou-te contar uma coisa.")]
[InlineData("Mas vou contar uma parada.", "Mas vou contar uma coisa.")]
- [InlineData("Não vou te contar uma parada.", "Não vou contar-te uma coisa.")]
+ [InlineData("Não vou te contar uma parada.", "Não te vou contar uma coisa.")]
[InlineData("Contar-lhe uma parada.", "Contar-lhe uma coisa.")]
public void TellThingComposesWithCliticPlacement(string source, string expected) => AssertConverts(source, expected);
@@ -708,7 +708,7 @@ public void HearingAboutDiffersFromDeliberateListening(string source, string? en
[InlineData("Um bar de topless.", "Um bar de topless.")]
[InlineData("O policial Olivet está aqui para vê-lo.", "O agente Olivet está aqui para vê-lo.")]
[InlineData("Sabia sobre isso?", "Sabia disto?")]
- [InlineData("Mande entrar.", "Mando-o entrar.")]
+ [InlineData("Mande entrar.", "Manda entrar.")]
[InlineData("Nossa foto de casamento?", "A nossa foto de casamento?")]
[InlineData("E ocupando nossa cadeira muito brevemente.", "E ocupando a nossa cadeira muito brevemente.")]
[InlineData("Nossa, que lindo!", "Caramba, que lindo!")]
@@ -859,7 +859,7 @@ public void ReflexiveImperativesRespectOptionsAndIgnoredPhrases()
[InlineData("Já lhe mandei suas coisas.", "Já lhe mandei as suas coisas.")]
[InlineData("Já lhes entreguei seus livros.", "Já lhes entreguei os seus livros.")]
[InlineData("Preciso do seu contato.", "Preciso do teu contacto.")]
- [InlineData("Você esqueceu suas coisas.", "Esqueceste as tuas coisas.")]
+ [InlineData("Você esqueceu suas coisas.", "Esqueceste-te das tuas coisas.")]
public void CueLevelPossessiveContext(string br, string pt) => AssertConverts(br, pt);
[Theory]
@@ -878,7 +878,14 @@ public void PreviousContextDoesNotMutateConverterState()
const string text = "Já mandei suas roupas.";
Assert.Equal("Já mandei as suas roupas.", Converter.Convert(text, "E me divorciando do cretino."));
Assert.Equal("Já mandei as tuas roupas.", Converter.Convert(text));
- Assert.Equal("Esqueceste as tuas coisas.", Converter.Convert("Você esqueceu suas coisas.", "E me divorciando do cretino."));
+ Assert.Equal("Esqueceste-te das tuas coisas.", Converter.Convert("Você esqueceu suas coisas.", "E me divorciando do cretino."));
+ }
+
+ [Fact]
+ public void ReferenceGuidedForgottenPossessiveUsesPronominalConstruction()
+ {
+ var result = Converter.Convert("Esqueceu seu café.", reference: ReferenceHint.FromText("You forgot your coffee."));
+ Assert.Equal("Esqueceste-te do teu café.", result);
}
[Theory]
@@ -1781,6 +1788,17 @@ public sealed class SlangTests
[InlineData("Nós lembramos de tudo.", "Nós lembramo-nos de tudo.")]
[InlineData("Ela não lembrava do rosto.", "Ela não se lembrava do rosto.")]
[InlineData("Ele esqueceu da reunião.", "Ele esqueceu-se da reunião.")]
+ [InlineData("Você esqueceu seu café.", "Esqueceste-te do teu café.")]
+ [InlineData("Esqueci a chave.", "Esqueci-me da chave.")]
+ [InlineData("Esqueci os documentos.", "Esqueci-me dos documentos.")]
+ [InlineData("Esqueci isto.", "Esqueci-me disto.")]
+ [InlineData("Esqueci tudo.", "Esqueci-me de tudo.")]
+ [InlineData("Você tinha esquecido o nome.", "Tinhas-te esquecido do nome.")]
+ [InlineData("Nós tínhamos esquecido o nome.", "Nós tínhamo-nos esquecido do nome.")]
+ [InlineData("Ela teria esquecido o nome.", "Ela ter-se-ia esquecido do nome.")]
+ [InlineData("Eu não tinha esquecido o nome.", "Eu não me tinha esquecido do nome.")]
+ [InlineData("Não esquece o café.", "Não te esqueças do café.")]
+ [InlineData("Ele esquece o passado.", "Ele esquece-se do passado.")]
[InlineData("Você esqueceu de mim?", "Esqueceste-te de mim?")]
[InlineData("Não esquece de mim.", "Não te esqueças de mim.")]
[InlineData("Não esqueça de ligar.", "Não te esqueças de ligar.")]
@@ -1791,7 +1809,8 @@ public sealed class SlangTests
[InlineData("Na verdade, nem me\nlembro dessa conversa.", "Na verdade, nem me\nlembro dessa conversa.")]
[InlineData("Não me lembro de nada.", "Não me lembro de nada.")]
[InlineData("Isso lembra o meu pai.", "Isso lembra o meu pai.")]
- [InlineData("Esqueci o nome dele.", "Esqueci o nome dele.")]
+ [InlineData("Esqueci o nome dele.", "Esqueci-me do nome dele.")]
+ [InlineData("Esquece o passado.", "Esquece o passado.")]
[InlineData("Esquece isso.", "Esquece isso.")]
[InlineData("Esquece!", "Esquece!")]
[InlineData("Tudo o que lembro.", "Tudo o que me lembro.")]
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/CorpusConstructionTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/CorpusConstructionTests.cs
index 0ce71744..6886f201 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/CorpusConstructionTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/CorpusConstructionTests.cs
@@ -5,6 +5,542 @@ namespace BrazilianToEuropeanPortuguese.Tests;
public sealed class CorpusConstructionTests
{
+ [Theory]
+ [InlineData("Ele pisou no meu rosto\ncom sua bota.", "He stepped on my face\nwith his boot.", "Ele pisou-me o rosto\ncom a bota.")]
+ [InlineData("Ele pisou no meu rosto com sua bota.", "He stepped on my face with his boot.", "Ele pisou-me o rosto com a bota.")]
+ [InlineData("Você tinha uma arma na sua mão.", "You did have a gun in your hand.", "Tinhas uma arma na mão.")]
+ [InlineData("Ela bateu na minha cara.", null, "Ela bateu-me na cara.")]
+ [InlineData("Ele agarrou o meu braço.", null, "Ele agarrou-me o braço.")]
+ [InlineData("Parti o meu braço.", null, "Parti o braço.")]
+ [InlineData("Toquei na minha cara.", null, "Toquei na cara.")]
+ [InlineData("Ele não pisou no meu pé.", null, "Ele não me pisou o pé.")]
+ [InlineData("Eu pisei no seu pé.", "I stepped on your foot.", "Eu pisei-te o pé.")]
+ [InlineData("Ela pisava no meu pé.", "She was stepping on my foot.", "Ela pisava-me o pé.")]
+ [InlineData("Eles torceram o meu pulso.", null, "Eles torceram-me o pulso.")]
+ public void InalienablePossessionUsesDativeOrBareArticle(string source, string? english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("Ele fugiu com a sua carteira.", "He ran off with your wallet.")]
+ [InlineData("Ele tocou na minha vida.", null)]
+ [InlineData("Ele bateu na minha porta.", null)]
+ [InlineData("Pisava no meu pé.", null)]
+ [InlineData("Ele pisou no seu pé.", null)]
+ [InlineData("Ele pisou no seu pé.", "He stepped on your foot, and her foot too.")]
+ public void InalienablePossessionDoesNotGuessOwnersOrOtherSenses(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Fact]
+ public void InalienablePossessionRespectsOptionsFormalityAndLocalChoices()
+ {
+ const string source = "Ele pisou no meu rosto com sua bota.";
+ var reference = ReferenceHint.FromText("He stepped on my face with his boot.");
+ var withoutRule = new PortugueseConverter(new ConversionOptions(DisabledRules: "inalienable-possession"));
+ Assert.DoesNotContain("pisou-me", withoutRule.Convert(source, reference: reference));
+ Assert.DoesNotContain("pisou-me", new PortugueseConverter(new ConversionOptions(Pronouns: false))
+ .Convert(source, reference: reference));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("pisar");
+ Assert.DoesNotContain("pisou-me", new PortugueseConverter(localWordList: words)
+ .Convert(source, reference: reference));
+ Assert.Equal("Eu pisei-lhe o pé.", new PortugueseConverter().Convert("Eu pisei no seu pé.",
+ reference: ReferenceHint.FromText("I stepped on your foot.")! with { FormalAddress = true }));
+ }
+
+ [Theory]
+ [InlineData("- Ele pisou\n- no meu rosto.")]
+ [InlineData("Ele pisou\n\nno meu rosto.")]
+ [InlineData("- arma\n- na sua mão.")]
+ [InlineData("arma\n\nna sua mão.")]
+ public void InalienablePossessionDoesNotCrossSpeakersOrBlankLines(string source)
+ {
+ var changes = new List();
+ new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText("He stepped on my face with his boot."),
+ trace: changes);
+ Assert.DoesNotContain(changes, c => c.Id == "inalienable-possession");
+ }
+
+ [Fact]
+ public void InalienablePossessionCueUsesAlignedReferenceAndPreservesSource()
+ {
+ var source = new[] { new SrtBlock { StartMs = 274590, EndMs = 276923, Text = "Ele pisou no meu rosto\ncom sua bota." } };
+ var english = new[] { new SrtBlock { StartMs = 274241, EndMs = 276977, Text = "He stepped on my face\nwith his boot." } };
+ var aligned = ReferenceAligner.AlignByTiming(source, english);
+ var converted = Assert.Single(SubtitleConversion.Convert(source, ConversionOptions.Default, english: aligned.Texts));
+ Assert.Equal("Ele pisou-me o rosto\ncom a bota.", converted.Text);
+ Assert.Equal((274590, 276923, "Ele pisou no meu rosto\ncom sua bota."), (source[0].StartMs, source[0].EndMs, source[0].Text));
+ }
+
+ [Theory]
+ [InlineData("e contou sobre isso.", "and told her about it.", "e contou-lhe sobre isso.")]
+ [InlineData("Ele contou sobre isso.", "He told me about it.", "Ele contou-me sobre isso.")]
+ [InlineData("Contou sobre isso.", "He told you about it.", "Contou-te sobre isso.")]
+ [InlineData("Ela não disse a verdade.", "She never told him the truth.", "Ela não lhe disse a verdade.")]
+ [InlineData("Ele vai contar tudo.", "He's going to tell them everything.", "Ele vai contar-lhes tudo.")]
+ [InlineData("Ele mentiu sobre isso.", "He lied to her about it.", "Ele mentiu-lhe sobre isso.")]
+ [InlineData("Ele mostrou isso.", "He showed us that.", "Ele mostrou-nos isso.")]
+ [InlineData("Ela perguntou sobre isso.", "She asked me about it.", "Ela perguntou-me sobre isso.")]
+ public void TellingVerbsRecoverTheReferenceConfirmedRecipient(string source, string english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("e contou sobre isso.", null)]
+ [InlineData("e contou sobre isso.", "and told her and told me about it.")]
+ [InlineData("e não contou sobre isso.", "and told her about it.")]
+ [InlineData("A conta sobre isso.", "He told her about it.")]
+ [InlineData("Ele contou à esposa sobre isso.", "He told her about it.")]
+ [InlineData("Ele me contou sobre isso.", "He told me about it.")]
+ [InlineData("Ele falou com ela. Ela falava que era inocente.", "He spoke to her. She said she was innocent.")]
+ [InlineData("- e contou\n- sobre isso.", "and told her about it.")]
+ [InlineData("e contou\n\nsobre isso.", "and told her about it.")]
+ public void TellingVerbsDoNotGuessRecipientsWithoutUnambiguousEvidence(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Fact]
+ public void TellingRecipientHonoursOptionsFormalityAndLocalChoices()
+ {
+ const string source = "e contou sobre isso.";
+ var reference = ReferenceHint.FromText("and told you about it.");
+ Assert.Equal("e contou-lhe sobre isso.", new PortugueseConverter()
+ .Convert(source, reference: reference! with { FormalAddress = true }));
+ Assert.DoesNotContain("-lhe", new PortugueseConverter(new ConversionOptions(Pronouns: false))
+ .Convert("e contou sobre isso.", reference: ReferenceHint.FromText("and told her about it.")));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("contar");
+ Assert.DoesNotContain("-lhe", new PortugueseConverter(localWordList: words)
+ .Convert("e contou sobre isso.", reference: ReferenceHint.FromText("and told her about it.")));
+ }
+
+ [Fact]
+ public void TellingRecipientCueUsesAlignedReferenceAndPreservesSource()
+ {
+ var source = new[] { new SrtBlock { StartMs = 111580, EndMs = 112736, Text = "e contou sobre isso." } };
+ var english = new[] { new SrtBlock { StartMs = 111579, EndMs = 112913, Text = "and told her about it." } };
+ var aligned = ReferenceAligner.AlignByTiming(source, english);
+ var converted = Assert.Single(SubtitleConversion.Convert(source, ConversionOptions.Default, english: aligned.Texts));
+ Assert.Equal("e contou-lhe sobre isso.", converted.Text);
+ Assert.Equal((111580, 112736, "e contou sobre isso."), (source[0].StartMs, source[0].EndMs, source[0].Text));
+ }
+
+ [Theory]
+ [InlineData("Não, mas não pensou", "No, but you didn't think...", "Não, mas não pensaste")]
+ [InlineData("Não, você não pensou", "No, you didn't think...", "Não, não pensaste")]
+ public void InitialNegationCommaStillTakesReferenceSecondPerson(string source, string english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("Não, mas não pensou", "No, but he didn't think...")]
+ [InlineData("Não, mas ele não pensou", "No, but you didn't think...")]
+ [InlineData("Não, mas não pensou", null)]
+ public void InitialNegationCommaDoesNotGuessWithoutSecondPersonEvidence(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Fact]
+ public void InitialNegationCueUsesAlignedReferenceAndPreservesSource()
+ {
+ var source = new[] { new SrtBlock { StartMs = 2322061, EndMs = 2323654, Text = "Não, mas não pensou" } };
+ var english = new[] { new SrtBlock { StartMs = 2321953, EndMs = 2323622, Text = "No, but you\ndidn't think..." } };
+ var aligned = ReferenceAligner.AlignByTiming(source, english);
+ var converted = Assert.Single(SubtitleConversion.Convert(source, ConversionOptions.Default, english: aligned.Texts));
+ Assert.Equal("Não, mas não pensaste", converted.Text);
+ Assert.Equal((2322061, 2323654, "Não, mas não pensou"), (source[0].StartMs, source[0].EndMs, source[0].Text));
+ }
+
+ [Theory]
+ [InlineData("Como divertisse você?", "Like I amuse you?", "Como se eu te divertisse?")]
+ [InlineData("Como eu fosse um palhaço?", "Like I'm a clown?", "Como se eu fosse um palhaço?")]
+ [InlineData("Como ela conhecesse você?", "As if she knew you?", "Como se ela te conhecesse?")]
+ [InlineData("Como nós ajudássemos você?", "As though we helped you?", "Como se nós te ajudássemos?")]
+ [InlineData("Como ele não visse você?", "Like he didn't see you?", "Como se ele não te visse?")]
+ [InlineData("Como ele divertisse você?", "Like he's amusing you?", "Como se ele te divertisse?")]
+ [InlineData("Como eu gostasse dele?", "As if I liked him?", "Como se eu gostasse dele?")]
+ [InlineData("Como eu nunca ajudasse você?", "Like I never helped you?", "Como se eu nunca te ajudasse?")]
+ public void HypotheticalComparisonsRestoreConjunctionAndSubject(string source, string english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("Como eu fosse um palhaço?", null)]
+ [InlineData("Como eu fosse um palhaço?", "I like clowns.")]
+ [InlineData("Como eu fosse um palhaço?", "Like he's a clown?")]
+ [InlineData("Como divertisse você?", "Like I amuse him?")]
+ [InlineData("Como divertisse você?", "Like I amuse you? He's lying.")]
+ [InlineData("Como se sentiu?", "How did he feel?")]
+ [InlineData("Como eu divertisse você?", "Like I didn't amuse you?")]
+ [InlineData("Como eu nunca ajudasse você?", "Like I didn't help you?")]
+ [InlineData("Como divertisse eu?", "Like I amuse you?")]
+ [InlineData("Como se eu fosse um palhaço?", "Like I'm a clown?")]
+ public void HypotheticalComparisonsDoNotGuessWithoutMatchingEvidence(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Fact]
+ public void HypotheticalComparisonsRespectOptionsAndLocalChoices()
+ {
+ const string source = "Como divertisse você?";
+ var reference = ReferenceHint.FromText("Like I amuse you?");
+ var withoutRule = new PortugueseConverter(new ConversionOptions(DisabledRules: "hypothetical-comparison"));
+ Assert.Equal(withoutRule.Convert(source), withoutRule.Convert(source, reference: reference));
+ var withoutColloquial = new PortugueseConverter(new ConversionOptions(Colloquial: false));
+ Assert.Equal(withoutColloquial.Convert(source), withoutColloquial.Convert(source, reference: reference));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("divertir");
+ var ignored = new PortugueseConverter(localWordList: words);
+ Assert.Equal(ignored.Convert(source), ignored.Convert(source, reference: reference));
+ Assert.Equal("Como se eu divertisse você?", new PortugueseConverter(new ConversionOptions(YouToTu: false))
+ .Convert(source, reference: reference));
+ Assert.Equal("Como se eu o divertisse?", new PortugueseConverter()
+ .Convert(source, reference: reference! with { FormalAddress = true }));
+ Assert.Equal("Como se eu te divertisse?", new PortugueseConverter(new ConversionOptions(Pronouns: false))
+ .Convert(source, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("- Como\n- divertisse você?")]
+ [InlineData("Como\n\ndivertisse você?")]
+ [InlineData("- Como eu\n- fosse um palhaço?")]
+ [InlineData("Como eu\n\nfosse um palhaço?")]
+ [InlineData("Como divertisse\n- você?")]
+ [InlineData("Como divertisse\n\nvocê?")]
+ public void HypotheticalComparisonsDoNotCrossSpeakersOrBlankLines(string source)
+ {
+ var changes = new List();
+ new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText("Like I amuse you?"), trace: changes);
+ Assert.DoesNotContain(changes, c => c.Id == "hypothetical-comparison");
+ }
+
+ [Fact]
+ public void HypotheticalComparisonCueUsesAlignedReferenceAndPreservesSource()
+ {
+ var source = new[] { new SrtBlock { StartMs = 2352514, EndMs = 2355504, Text = "Como divertisse você?" } };
+ var english = new[] { new SrtBlock { StartMs = 2352451, EndMs = 2355053, Text = "Like I amuse you?" } };
+ var aligned = ReferenceAligner.AlignByTiming(source, english);
+ var converted = Assert.Single(SubtitleConversion.Convert(source, ConversionOptions.Default, english: aligned.Texts));
+ Assert.Equal("Como se eu te divertisse?", converted.Text);
+ Assert.Equal((2352514, 2355504, "Como divertisse você?"), (source[0].StartMs, source[0].EndMs, source[0].Text));
+ }
+
+ [Fact]
+ public void HypotheticalComparisonSupportsWrappedEnglishReference()
+ {
+ const string english = "Like I amuse you?";
+ var converter = new PortugueseConverter();
+ for (var index = 0; index < english.Length; index++)
+ {
+ if (english[index] != ' ') continue;
+ foreach (var newline in new[] { "\n", "\r\n" })
+ {
+ var wrapped = english[..index] + newline + english[(index + 1)..];
+ Assert.Equal("Como se eu te divertisse?", converter.Convert("Como divertisse você?",
+ reference: ReferenceHint.FromText(wrapped)));
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData("Num trote de celular?", null, "Numa chamada de brincadeira?")]
+ [InlineData("O trote telefônico foi estranho.", null, "A chamada de brincadeira foi estranha.")]
+ [InlineData("Os trotes por telefone.", null, "As chamadas de brincadeira.")]
+ [InlineData("Uma ligação de trote.", null, "Uma chamada de brincadeira.")]
+ [InlineData("O trote estudantil.", null, "A praxe académica.")]
+ [InlineData("Os trotes universitários.", null, "As praxes académicas.")]
+ [InlineData("Era um trote.", "It was a prank.", "Era uma brincadeira.")]
+ [InlineData("São trotes.", "They're practical jokes.", "São brincadeiras.")]
+ [InlineData("O trote foi engraçado.", "The prank was funny.", "A brincadeira foi engraçada.")]
+ [InlineData("Ele passou um trote.", null, "Ele pregou uma partida.")]
+ [InlineData("Eles passaram uns trotes.", null, "Eles pregaram umas partidas.")]
+ public void PrankAndStudentInitiationVocabularyUsesTheRightSense(string source, string? english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("O cavalo passou a trote.", "The horse went at a trot.")]
+ [InlineData("O trote do cavalo.", "The horse's trot.")]
+ [InlineData("O trote foi rápido.", null)]
+ [InlineData("O trote do peludo.", "A prank.")]
+ [InlineData("Ele deu um trote no cavalo.", "He played a prank.")]
+ [InlineData("Ele passou um trote no colega.", "He played a prank.")]
+ [InlineData("O trote.", "The freshman hazing was a prank.")]
+ public void PrankVocabularyDoesNotGuessOtherOrUnsafeSenses(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Fact]
+ public void PrankVocabularyHonoursOptionsAndLocalChoices()
+ {
+ const string source = "Num trote de celular?";
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(DisabledRules: "word-list"))
+ .Convert(source));
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(Vocabulary: false)).Convert(source));
+ Assert.Equal("Ele passou um trote.", new PortugueseConverter(new ConversionOptions(DisabledRules: "prank-actions"))
+ .Convert("Ele passou um trote.", reference: ReferenceHint.FromText("It was a prank.")));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("trote");
+ Assert.Equal("Num trote de telemóvel?", new PortugueseConverter(localWordList: words).Convert(source));
+ words.Ignored.Clear();
+ words.Words.Add(("trote", "partida"));
+ Assert.Equal("Num partida de telemóvel?", new PortugueseConverter(localWordList: words).Convert(source));
+ }
+
+ [Theory]
+ [InlineData("Um trote\n\nde celular.")]
+ [InlineData("O trote\n\nestudantil.")]
+ [InlineData("- Um trote\n- de celular.")]
+ [InlineData("- O trote\n- estudantil.")]
+ public void QualifiedPrankTermsDoNotCrossBlankLinesOrSpeakers(string source)
+ {
+ var result = new PortugueseConverter().Convert(source);
+ Assert.DoesNotContain("chamada", result, StringComparison.OrdinalIgnoreCase);
+ Assert.DoesNotContain("praxe", result, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void PrankCallCueRetainsTimingAndSecondSpeaker()
+ {
+ var source = new[] { new SrtBlock { StartMs = 305552, EndMs = 307578, Text = "-Num trote de celular?\n-E deveriam" } };
+ var english = new[] { new SrtBlock { StartMs = 305739, EndMs = 307741, Text = "On a prank phone call?\nAnd they should" } };
+ var aligned = ReferenceAligner.AlignByTiming(source, english);
+ var converted = Assert.Single(SubtitleConversion.Convert(source, ConversionOptions.Default, english: aligned.Texts));
+ Assert.StartsWith("-Numa chamada de brincadeira?\n-", converted.Text);
+ Assert.Equal((305552, 307578, "-Num trote de celular?\n-E deveriam"), (source[0].StartMs, source[0].EndMs, source[0].Text));
+ }
+
+ [Theory]
+ [InlineData("Então decidiu revidar.", "So you decided to get back at him.", "Então decidiste vingar-te.")]
+ [InlineData("Eu revidarei.", "I will get back at him.", "Eu vingar-me-ei.")]
+ [InlineData("Eu revidava.", "I wanted revenge.", "Eu vingava-me.")]
+ [InlineData("Nós revidaríamos.", "We would get back at him.", "Nós vingar-nos-íamos.")]
+ [InlineData("Eles revidariam.", "They would get back at him.", "Eles vingar-se-iam.")]
+ [InlineData("Ela revidou.", "She got back at him.", "Ela vingou-se.")]
+ [InlineData("Eles revidaram.", "They got back at him.", "Eles vingaram-se.")]
+ [InlineData("Ela revidou.", "She got even with the witness.", "Ela vingou-se.")]
+ [InlineData("Ela não revidou.", "She did not get back at him.", "Ela não se vingou.")]
+ [InlineData("Ele quer revidar.", "He wants to get back at her.", "Ele quer vingar-se.")]
+ [InlineData("Ele está revidando.", "He is getting back at her.", "Ele está a vingar-se.")]
+ [InlineData("Eu estava revidando.", "I was getting back at him.", "Eu estava a vingar-me.")]
+ [InlineData("Decidi revidar.", "I decided to get back at him.", "Decidi vingar-me.")]
+ [InlineData("Ela retrucou.", "She replied.", "Ela respondeu.")]
+ [InlineData("Eles retrucaram.", "They retorted.", "Eles responderam.")]
+ [InlineData("Ela revidou.", "She replied.", "Ela respondeu.")]
+ public void RevengeAndRetortsUseReferenceQualifiedAlternatives(string source, string english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var hint = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: hint));
+ Assert.Equal(expected, converter.Convert(expected, reference: hint));
+ }
+
+ [Theory]
+ [InlineData("Ele revidou.", null)]
+ [InlineData("Ele revidou.", "He fought back.")]
+ [InlineData("Ele revidou.", "He replied and wanted revenge.")]
+ [InlineData("Ele revidou o golpe.", "He got back at him.")]
+ [InlineData("Ela revidou.", "He got back at her.")]
+ [InlineData("Ela decidiu revidar.", "He decided to get back at her.")]
+ [InlineData("Ela revidou.", "She did not get back at him.")]
+ [InlineData("Ela não revidou.", "She got back at him.")]
+ [InlineData("Ele retrucou.", "He played another card.")]
+ [InlineData("Ele revidava.", "They sought revenge.")]
+ [InlineData("A revida.", "She replied.")]
+ public void RevengeAndRetortsPreserveUnconfirmedOrDifferentSenses(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Fact]
+ public void RevengeRulesRespectOptionsAndLocalChoices()
+ {
+ var hint = ReferenceHint.FromText("She got back at him.");
+ const string source = "Ela revidou.";
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(Vocabulary: false)).Convert(source, reference: hint));
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(Pronouns: false)).Convert(source, reference: hint));
+ Assert.Equal("Ela respondeu.", new PortugueseConverter(new ConversionOptions(Pronouns: false))
+ .Convert("Ela retrucou.", reference: ReferenceHint.FromText("She replied.")));
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(DisabledRules: "retaliation-vocabulary")).Convert(source, reference: hint));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("revidar");
+ Assert.Equal(source, new PortugueseConverter(localWordList: words).Convert(source, reference: hint));
+ }
+
+ [Fact]
+ public void RevengeCueUsesAlignedReferenceWithoutChangingTimingOrMarkup()
+ {
+ var source = new[] { new SrtBlock { StartMs = 2186224, EndMs = 2187985, Text = "Então decidiu revidar." } };
+ var english = new[] { new SrtBlock { StartMs = 2186452, EndMs = 2187953, Text = "DETECTIVE: So you decided\nto get back at him." } };
+ var aligned = ReferenceAligner.AlignByTiming(source, english);
+ var converted = Assert.Single(SubtitleConversion.Convert(source, ConversionOptions.Default, english: aligned.Texts));
+ Assert.Equal("Então decidiste vingar-te.", converted.Text);
+ Assert.Equal((2186224, 2187985, "Então decidiu revidar."), (source[0].StartMs, source[0].EndMs, source[0].Text));
+ }
+
+ [Theory]
+ [InlineData("Seu advogado veio ao esquadrão,", "His lawyer came by the squad,", "O seu advogado veio à esquadra,")]
+ [InlineData("Veio para acompanhá-lo à sala do esquadrão.", "Came to escort you to the squad room.", "Veio para acompanhá-lo à sala da esquadra.")]
+ [InlineData("Ele está no esquadrão.", "He is at the squad.", "Ele está na esquadra.")]
+ [InlineData("Ele saiu do esquadrão.", "He left the precinct.", "Ele saiu da esquadra.")]
+ [InlineData("Vou para o distrito policial.", "I'm going to the precinct.", "Vou para a esquadra.")]
+ [InlineData("NO ESQUADRÃO.", "At the squad.", "NA ESQUADRA.")]
+ public void PolicePlacesNeedReferenceAndUseFeminineArticles(string source, string english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("O esquadrão chegou.", "The squad arrived.")]
+ [InlineData("Ele é do esquadrão.", "He works at the squad.")]
+ [InlineData("Está no esquadrão de resgate.", "He is at the squad.")]
+ [InlineData("Está no esquadrão.", "He is in the army squad.")]
+ [InlineData("O esquadrão está em formação.", "The squad is in formation.")]
+ [InlineData("Ele veio ao esquadrão.", null)]
+ public void PoliceTeamAndMilitaryMeaningsAreNotPoliceStations(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Theory]
+ [InlineData("Ele mente.", "He's lying.", "Ele está a mentir.")]
+ [InlineData("Ela mente.", "She's lying.", "Ela está a mentir.")]
+ [InlineData("Eu minto.", "I'm lying.", "Eu estou a mentir.")]
+ [InlineData("Eles mentem.", "They're lying.", "Eles estão a mentir.")]
+ [InlineData("Nós mentimos.", "We're lying.", "Nós estamos a mentir.")]
+ [InlineData("Ele não mente.", "He isn't lying.", "Ele não está a mentir.")]
+ [InlineData("Ele não mente.", "He's not lying.", "Ele não está a mentir.")]
+ [InlineData("Tu mentias.", "You were lying.", "Tu estavas a mentir.")]
+ [InlineData("Você mente.", "You're lying.", "Estás a mentir.")]
+ [InlineData("Ele mentia.", "He was lying.", "Ele estava a mentir.")]
+ [InlineData("Ela fala.", "She is talking.", "Ela está a falar.")]
+ [InlineData("Ele trabalha.", "He is working.", "Ele está a trabalhar.")]
+ [InlineData("Eles esperam.", "They are waiting.", "Eles estão a esperar.")]
+ [InlineData("Ela dorme.", "She is sleeping.", "Ela está a dormir.")]
+ [InlineData("Ela chora.", "She is crying.", "Ela está a chorar.")]
+ [InlineData("Ele corre.", "He is running.", "Ele está a correr.")]
+ [InlineData("Ela come.", "She is eating.", "Ela está a comer.")]
+ [InlineData("Ele bebe.", "He is drinking.", "Ele está a beber.")]
+ [InlineData("Ele está mentindo.", "He's lying.", "Ele está a mentir.")]
+ public void ExplicitOngoingActionsUseProgressiveWhenEnglishConfirmsThem(string source, string english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("Ele mente.", null)]
+ [InlineData("Ele mente.", "He lies.")]
+ [InlineData("Ele mente.", "She is lying.")]
+ [InlineData("Ele mente.", "He isn't lying.")]
+ [InlineData("Ele mente.", "He's lying. He isn't lying.")]
+ [InlineData("Ele mente. Ele mente.", "He's lying.")]
+ [InlineData("Ele não mente.", "He is lying.")]
+ [InlineData("Ele mente.", "He is lying in bed.")]
+ [InlineData("Ele mente.", "He is lying on a sofa.")]
+ [InlineData("Ele sempre mente.", "He is always lying.")]
+ [InlineData("Ele mentiu.", "He is lying.")]
+ [InlineData("Eles mentem.", "He is lying.")]
+ [InlineData("Ela trabalha.", "She was working.")]
+ public void ProgressiveDoesNotInventPersonPolarityOrAspect(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Fact]
+ public void PoliceAndProgressiveRulesRespectSettingsAndLocalWords()
+ {
+ var station = ReferenceHint.FromText("His lawyer came by the squad.");
+ var lying = ReferenceHint.FromText("He's lying.");
+ Assert.Equal("Ele veio ao esquadrão.", new PortugueseConverter(new ConversionOptions(Vocabulary: false))
+ .Convert("Ele veio ao esquadrão.", reference: station));
+ Assert.Equal("Ele veio ao esquadrão.", new PortugueseConverter(new ConversionOptions(DisabledRules: "police-station"))
+ .Convert("Ele veio ao esquadrão.", reference: station));
+ Assert.Equal("Ele mente.", new PortugueseConverter(new ConversionOptions(Progressive: false))
+ .Convert("Ele mente.", reference: lying));
+ Assert.Equal("Ele mente.", new PortugueseConverter(new ConversionOptions(DisabledRules: "reference-progressive"))
+ .Convert("Ele mente.", reference: lying));
+ Assert.Equal("Ele mente.", new PortugueseConverter(new ConversionOptions(DisabledRules: "progressive"))
+ .Convert("Ele mente.", reference: lying));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("esquadrão");
+ words.Ignored.Add("mentir");
+ var converter = new PortugueseConverter(localWordList: words);
+ Assert.Equal("Ele veio ao esquadrão.", converter.Convert("Ele veio ao esquadrão.", reference: station));
+ Assert.Equal("Ele mente.", converter.Convert("Ele mente.", reference: lying));
+ }
+
+ [Fact]
+ public void PoliceAndLyingCuesKeepTimingAndCorrespondingReference()
+ {
+ var blocks = new[]
+ {
+ new SrtBlock { StartMs = 1083239, EndMs = 1084315, Text = "Veio para acompanhá-lo\nà sala do esquadrão." },
+ new SrtBlock { StartMs = 1747969, EndMs = 1749413, Text = "Seu advogado\nveio ao esquadrão," },
+ new SrtBlock { StartMs = 2038809, EndMs = 2039878, Text = "Ele está mentindo." },
+ new SrtBlock { StartMs = 2039878, EndMs = 2041771, Text = "-Certo.\n-Não é verdade! Ele mente." },
+ new SrtBlock { StartMs = 2047346, EndMs = 2049166, Text = "-Entende? Ele mente.\n-Ótimo. Então não terá problemas" },
+ new SrtBlock { StartMs = 2049259, EndMs = 2052020, Text = "-em vir ao esquadrão.\n-Ele mente." },
+ };
+ var originals = blocks.Select(b => (b.Text, b.StartMs, b.EndMs)).ToArray();
+ var english = new[]
+ {
+ new SrtBlock { StartMs = 1082149, EndMs = 1084251, Text = "Came down to escort you\nto the squad room." },
+ new SrtBlock { StartMs = 1747914, EndMs = 1749381, Text = "His lawyer\ncame by the squad," },
+ new SrtBlock { StartMs = 2038570, EndMs = 2040072, Text = "He's lying." },
+ new SrtBlock { StartMs = 2040106, EndMs = 2041740, Text = "Okay...\nThat's not true!\nHe's lying." },
+ new SrtBlock { StartMs = 2047346, EndMs = 2049248, Text = "Do you understand? He's lying.\nThat's great.\nSo you won't have any problems" },
+ new SrtBlock { StartMs = 2049281, EndMs = 2051717, Text = "coming down to the squad.\nCome on.\nHe's lying." },
+ };
+ var aligned = ReferenceAligner.AlignByTiming(blocks, english);
+ Assert.Contains("He's lying", aligned.Texts[5]);
+ var converted = SubtitleConversion.Convert(blocks, ConversionOptions.Default, english: aligned.Texts).ToArray();
+ Assert.Contains("sala da esquadra", converted[0].Text.Replace("\n", " "));
+ Assert.Contains("à esquadra", converted[1].Text.Replace("\n", " "));
+ Assert.All(converted.Skip(2), cue => Assert.Contains("está a mentir", cue.Text.Replace("\n", " ")));
+ Assert.Contains("à esquadra", converted[5].Text.Replace("\n", " "));
+ Assert.Equal(originals, blocks.Select(b => (b.Text, b.StartMs, b.EndMs)).ToArray());
+ }
+
[Theory]
[InlineData("Alguém que você acha que é calmo.", "Someone you think is calm.", "Alguém que achas que é calmo.")]
[InlineData("O homem que você pensa que trabalha aqui.", "The man you think works here.", "O homem que pensas que trabalha aqui.")]
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/DurationAndWrappingTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/DurationAndWrappingTests.cs
new file mode 100644
index 00000000..564d1ee2
--- /dev/null
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/DurationAndWrappingTests.cs
@@ -0,0 +1,217 @@
+using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese;
+
+namespace BrazilianToEuropeanPortuguese.Tests;
+
+public sealed class DurationAndWrappingTests
+{
+ [Theory]
+ [InlineData("Bem, talvez passou\nmuitas longas noites", "Well, maybe she spent a lot of long nights",
+ "Bem, talvez tenha passado\nmuitas noites longas")]
+ [InlineData("Passei muitas longas noites.", null, "Passei muitas noites longas.")]
+ [InlineData("Passei muitas longas noites.", "I spent very long nights.", "Passei noites muito longas.")]
+ [InlineData("Passei muitas longas noites.", "I spent many very long nights.", "Passei muitas noites longas.")]
+ [InlineData("Passei muitas longas noites.", "I spent a lot of long nights, not very long nights.", "Passei muitas noites longas.")]
+ [InlineData("Passei muitas longas noites.", "They were not very long nights.", "Passei muitas noites longas.")]
+ [InlineData("Passei muito longas noites.", null, "Passei noites muito longas.")]
+ [InlineData("Foram muitos longos dias.", null, "Foram muitos dias longos.")]
+ [InlineData("Foram muitas difíceis semanas.", null, "Foram muitas semanas difíceis.")]
+ [InlineData("Foi uma das muitas curtas horas.", null, "Foi uma das muitas horas curtas.")]
+ [InlineData("PASSEI MUITAS LONGAS NOITES.", null, "PASSEI MUITAS NOITES LONGAS.")]
+ public void DurationOrderPreservesQuantityUnlessDegreeIsExplicit(string source, string? english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("Passei muitas noites longas.")]
+ [InlineData("Passei noites muito longas.")]
+ [InlineData("Passei muitas boas noites.")]
+ [InlineData("Muitos grandes homens.")]
+ [InlineData("Passei muitas longos noites.")]
+ public void OtherMeaningsAndUnresolvedAgreementStayProtected(string text) =>
+ Assert.Equal(text, new PortugueseConverter().Convert(text));
+
+ [Fact]
+ public void DurationPreferenceUsesExistingStyleSwitches()
+ {
+ const string text = "Passei muitas longas noites.";
+ Assert.Equal(text, new PortugueseConverter(new ConversionOptions(Colloquial: false)).Convert(text));
+ Assert.Equal(text, new PortugueseConverter(new ConversionOptions(DisabledRules: "natural-word-order")).Convert(text));
+ }
+
+ [Fact]
+ public void ProtectedWhitespaceAndIgnoredPhrasesStayVerbatim()
+ {
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("muitas longas noites");
+ Assert.Equal("Passei muitas\nlongas noites.", new PortugueseConverter(localWordList: words)
+ .Convert("Passei muitas\nlongas noites."));
+ Assert.Equal("É um autocarro.\u2028", new PortugueseConverter().Convert("É um ônibus.\u2028"));
+ Assert.Equal("Texto sem\nalterações.", new PortugueseConverter().Convert("Texto sem\nalterações."));
+ }
+
+ [Theory]
+ [InlineData("Espero que ambos precisam se conhecer de novo.")]
+ [InlineData("A perna esquerda do meu padrinho foi lesionada.")]
+ [InlineData("porque você sempre sabe quando eu tô mentindo.")]
+ [InlineData("Só lembra-te quem enfrentas.")]
+ [InlineData("Eu nunca vou te esquecer.")]
+ [InlineData("Ele virou médico.")]
+ [InlineData("Bem, talvez passou muitas longas noites.")]
+ [InlineData("O telefone está tocando.")]
+ [InlineData("Ela precisa se lembrar disso.")]
+ [InlineData("Você está certo que pode fazer isso.")]
+ [InlineData("Pergunto-me por que ela fez isso.")]
+ [InlineData("Ele falou algo.")]
+ [InlineData("Eu gabaritei as questões.")]
+ [InlineData("Vá até o fim.")]
+ [InlineData("É melhor você não contar.")]
+ [InlineData("Ele gostou que eu dirigi.")]
+ public void EveryWordBoundaryGetsTheSameCorrectionAcrossStages(string text)
+ {
+ var converter = new PortugueseConverter();
+ var expected = converter.Convert(text);
+ for (var index = 0; index < text.Length; index++)
+ {
+ if (text[index] != ' ') continue;
+ foreach (var newline in new[] { "\n", "\r\n" })
+ {
+ var wrapped = text[..index] + newline + text[(index + 1)..];
+ var converted = converter.Convert(wrapped);
+ Assert.Equal(expected, converted.Replace(newline, " "));
+ Assert.Equal(converted, converter.Convert(converted));
+ Assert.Equal(1, converted.Split('\n').Length - 1);
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData("Esqueceu seu café.", "You forgot your coffee.", "Esqueceste-te do teu café.")]
+ [InlineData("Eu não tinha esquecido o nome.", null, "Eu não me tinha esquecido do nome.")]
+ [InlineData("Você tinha esquecido o nome.", null, "Tinhas-te esquecido do nome.")]
+ [InlineData("Não esquece o café.", null, "Não te esqueças do café.")]
+ [InlineData("Ela esqueceu um documento.", null, "Ela esqueceu-se de um documento.")]
+ [InlineData("Ele está jogando verde.", "He's fishing.", "Ele está à pesca.")]
+ [InlineData("Ele está a jogar um verde.", "He's fishing.", "Ele está à pesca.")]
+ [InlineData("Ela estava jogando um verde.", "She was fishing.", "Ela estava à pesca.")]
+ [InlineData("Ele jogou verde.", "He fished for information.", "Ele pescou.")]
+ [InlineData("Ele joga um verde para colher maduro.", "He fishes for information.", "Ele pesca.")]
+ [InlineData("Espero que ele pegue a isca.", "I hope he takes the bait.", "Espero que ele morda o isco.")]
+ [InlineData("Ele pegou na isca.", "He took the bait.", "Ele mordeu o isco.")]
+ [InlineData("Uma xícara de Joe para viagem.", "A cup of joe to go.", "Uma chávena de café para levar.")]
+ [InlineData("Há algo que não soma.", "Something's not adding up.", "Há algo que não bate certo.")]
+ [InlineData("E não somos jogadores.", "And we ain't no fishermen.", "E não somos pescadores.")]
+ [InlineData("Ele está mentindo.", "He's lying.", "Ele está a mentir.")]
+ [InlineData("Ele mente.", "He's lying.", "Ele está a mentir.")]
+ [InlineData("Ele não mente.", "He isn't lying.", "Ele não está a mentir.")]
+ [InlineData("Ela estava falando.", "She was talking.", "Ela estava a falar.")]
+ [InlineData("Ele veio ao esquadrão.", "He came by the squad.", "Ele veio à esquadra.")]
+ [InlineData("Estou na sala do esquadrão.", "I'm in the squad room.", "Estou na sala da esquadra.")]
+ [InlineData("Vou para o distrito policial.", "I'm going to the precinct.", "Vou para a esquadra.")]
+ [InlineData("Então decidiu revidar.", "So you decided to get back at him.", "Então decidiste vingar-te.")]
+ [InlineData("Ela não revidou.", "She did not get back at him.", "Ela não se vingou.")]
+ [InlineData("Ela retrucou.", "She replied.", "Ela respondeu.")]
+ [InlineData("Ele está revidando.", "He is getting back at her.", "Ele está a vingar-se.")]
+ [InlineData("Num trote de celular?", null, "Numa chamada de brincadeira?")]
+ [InlineData("O trote estudantil.", null, "A praxe académica.")]
+ [InlineData("Era um trote.", "It was a prank.", "Era uma brincadeira.")]
+ [InlineData("Ele passou um trote.", null, "Ele pregou uma partida.")]
+ [InlineData("Como divertisse você?", "Like I amuse you?", "Como se eu te divertisse?")]
+ [InlineData("Como eu fosse um palhaço?", "Like I'm a clown?", "Como se eu fosse um palhaço?")]
+ [InlineData("Como ele não visse você?", "Like he didn't see you?", "Como se ele não te visse?")]
+ [InlineData("Ele pisou no meu rosto com sua bota.", "He stepped on my face with his boot.", "Ele pisou-me o rosto com a bota.")]
+ [InlineData("Você tinha uma arma na sua mão.", "You did have a gun in your hand.", "Tinhas uma arma na mão.")]
+ [InlineData("Ela bateu na minha cara.", null, "Ela bateu-me na cara.")]
+ [InlineData("Parti o meu braço.", null, "Parti o braço.")]
+ [InlineData("Não, mas não pensou", "No, but you didn't think...", "Não, mas não pensaste")]
+ [InlineData("e contou sobre isso.", "and told her about it.", "e contou-lhe sobre isso.")]
+ public void RecentRulesWorkAcrossEveryWordBoundary(string text, string? english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(text, reference: reference));
+ for (var index = 0; index < text.Length; index++)
+ {
+ if (text[index] != ' ') continue;
+ foreach (var newline in new[] { "\n", "\r\n" })
+ {
+ var wrapped = text[..index] + newline + text[(index + 1)..];
+ var converted = converter.Convert(wrapped, reference: reference);
+ Assert.True(expected == converted.Replace(newline, " "),
+ $"Input: {wrapped.Replace("\r", "\\r").Replace("\n", "\\n")}; output: {converted.Replace("\r", "\\r").Replace("\n", "\\n")}; expected: {expected}");
+ Assert.Equal(1, converted.Count(c => c == '\n'));
+ Assert.Equal(converted, converter.Convert(converted, reference: reference));
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData("- Ele está jogando\n- Verde.", "He's fishing.")]
+ [InlineData("Ele está jogando\n\nverde.", "He's fishing.")]
+ [InlineData("- Ele pegou\n- A isca.", "He took the bait.")]
+ [InlineData("Ele pegou a\n\nisca.", "He took the bait.")]
+ [InlineData("- Uma xícara\n- De Joe.", "A cup of joe.")]
+ [InlineData("Uma xícara de\n\nJoe.", "A cup of joe.")]
+ [InlineData("- Ele\n- mente.", "He's lying.")]
+ [InlineData("Ele\n\nmente.", "He's lying.")]
+ [InlineData("- na sala\n- do esquadrão.", "In the squad room.")]
+ [InlineData("na sala\n\ndo esquadrão.", "In the squad room.")]
+ [InlineData("- Ela\n- revidou.", "She got back at him.")]
+ [InlineData("Ela\n\nrevidou.", "She got back at him.")]
+ public void RecentIdiomsDoNotCrossSpeakersOrBlankLines(string source, string english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Theory]
+ [InlineData("Ele está jogando\nverde.", "He's\nfishing.", "Ele está\nà pesca.")]
+ [InlineData("Ele pegou a\nisca.", "He took\nthe bait.", "Ele mordeu o\nisco.")]
+ [InlineData("Há algo que não\nsoma.", "Something's not\nadding up.", "Há algo que não\nbate certo.")]
+ [InlineData("Uma xícara de\nJoe.", "A cup of\njoe.", "Uma chávena de\ncafé.")]
+ [InlineData("Ele\nmente.", "He's\nlying.", "Ele\nestá a mentir.")]
+ [InlineData("Ele veio\nao esquadrão.", "He came by\nthe squad.", "Ele veio\nà esquadra.")]
+ [InlineData("Então decidiu\nrevidar.", "So you decided to get\nback at him.", "Então decidiste\nvingar-te.")]
+ [InlineData("Como divertisse\nvocê?", "Like I\namuse you?", "Como se eu\nte divertisse?")]
+ [InlineData("Ele pisou no meu\nrosto com sua bota.", "He stepped on my face\nwith his boot.", "Ele pisou-me\no rosto com a bota.")]
+ public void ReferenceAndPortugueseMayBothWrap(string source, string english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(expected, converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Theory]
+ [InlineData("- Não\n- esquece o café.")]
+ [InlineData("Não\n\nesquece o café.")]
+ public void NegativeCommandDoesNotCrossSpeakersOrBlankLines(string source)
+ {
+ var rules = new List();
+ new PortugueseConverter().Convert(source, trace: rules);
+ Assert.DoesNotContain(rules, r => r.Id == "address");
+ }
+
+ [Theory]
+ [InlineData("- Espero que ele sabe.\n- Eu também.", "- Espero que ele saiba.\n- Eu também.")]
+ [InlineData("-Talvez\n-Passou muitas noites.", "-Talvez\n-Passou muitas noites.")]
+ [InlineData("Talvez.\nPassou muitas noites.", "Talvez.\nPassou muitas noites.")]
+ [InlineData("Ele\n\nvirou médico.", "Ele\n\ntornou-se médico.")]
+ [InlineData("Ele virou\nmédico.", "Ele tornou-se\nmédico.")]
+ [InlineData("Ele virou \n médico.", "Ele tornou-se \n médico.")]
+ public void SpeakerBoundariesBlankLinesAndTagsSurvive(string text, string expected) =>
+ Assert.Equal(expected, new PortugueseConverter().Convert(text));
+
+ [Theory]
+ [InlineData("Ele gostou que\neu dirigi.", "Ele gostou de ser\neu a conduzir.")]
+ [InlineData("Ele gostou de ser\neu a conduzir.", "Ele gostou de ser\neu a conduzir.")]
+ [InlineData("Ela gostou de ser\nela a pagar.", "Ela gostou de ser\nela a pagar.")]
+ [InlineData("Eu a vi.", "Eu vi-a.")]
+ public void RestoredBreakDoesNotTurnInfinitivalPrepositionIntoClitic(string text, string expected)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(expected, converter.Convert(text));
+ Assert.Equal(expected, converter.Convert(expected));
+ }
+}
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/EmphaticAndPolicyDialogueTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/EmphaticAndPolicyDialogueTests.cs
new file mode 100644
index 00000000..adb5990f
--- /dev/null
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/EmphaticAndPolicyDialogueTests.cs
@@ -0,0 +1,179 @@
+using System.Text.RegularExpressions;
+using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese;
+using SubtitleEdit.Plugins.Shared;
+
+namespace BrazilianToEuropeanPortuguese.Tests;
+
+public sealed class EmphaticAndPolicyDialogueTests
+{
+ public static TheoryData Examples => new()
+ {
+ { "-Por favor.\n-Como diabos ela saberia?", "Oh, please. How the hell would she know?",
+ "-Por favor.\n-Como diabos saberia ela?" },
+ { "Como diabos ele saberia?", null, "Como diabos saberia ele?" },
+ { "Como diabos elas saberiam?", null, "Como diabos saberiam elas?" },
+ { "Como raios ela sabe?", null, "Como raios sabe ela?" },
+ { "Como raio eles sabiam?", null, "Como raio sabiam eles?" },
+ { "Como caraças ela soube?", null, "Como caraças soube ela?" },
+ { "Como diabos ela faria?", null, "Como diabos faria ela?" },
+ { "Como diabos elas trabalharão?", null, "Como diabos trabalharão elas?" },
+ { "Como diabos ela o saberia?", null, "Como diabos o saberia ela?" },
+ { "Como diabos ela saberia?", null, "Como diabos saberia ela?" },
+ { "COMO DIABOS ELA SABERIA?", null, "COMO DIABOS SABERIA ELA?" },
+ { "Dutton foi uma burocrata\naté 15 minutos atrás.", "Dutton was a dog catcher till 15 minutes ago.",
+ "Dutton era uma burocrata\naté 15 minutos atrás." },
+ { "Ela foi médica até há duas horas.", "She was a doctor until two hours ago.",
+ "Ela era médica até há duas horas." },
+ { "Eles foram professores até três dias atrás.", "They were teachers until three days ago.",
+ "Eles eram professores até três dias atrás." },
+ { "Ele foi um engenheiro até há um minuto.", "He was an engineer until a minute ago.",
+ "Ele era um engenheiro até há um minuto." },
+ { "DUTTON FOI UMA BUROCRATA ATÉ 15 MINUTOS ATRÁS.", "Dutton was a bureaucrat till 15 minutes ago.",
+ "DUTTON ERA UMA BUROCRATA ATÉ 15 MINUTOS ATRÁS." },
+ { "prioriza visar ao\ncrime de rua...", "prioritizes targeting street crime...",
+ "dá prioridade ao combate\naos crimes de rua..." },
+ { "O programa prioriza visar ao crime de rua.", "The program prioritizes targeting street crime.",
+ "O programa dá prioridade ao combate aos crimes de rua." },
+ { "Os programas priorizam visar os crimes de rua.", "The programs prioritize targeting street crime.",
+ "Os programas dão prioridade ao combate aos crimes de rua." },
+ { "O programa priorizava visar o crime de rua.", "The program prioritized targeting street crime.",
+ "O programa dava prioridade ao combate aos crimes de rua." },
+ { "O programa priorizou visar à criminalidade de rua.", "The program prioritized targeting street crime.",
+ "O programa deu prioridade ao combate aos crimes de rua." },
+ { "O programa priorizará visar ao crime de rua.", "The program will prioritize targeting street crime.",
+ "O programa dará prioridade ao combate aos crimes de rua." },
+ { "O programa priorizaria visar ao crime de rua.", "The program would prioritize targeting street crime.",
+ "O programa daria prioridade ao combate aos crimes de rua." },
+ { "PRIORIZA VISAR AO CRIME DE RUA.", "prioritizes targeting street crime.",
+ "DÁ PRIORIDADE AO COMBATE AOS CRIMES DE RUA." },
+ };
+
+ [Theory]
+ [MemberData(nameof(Examples))]
+ public void GuardedConstructionsPreserveMeaningAndAreStable(string source, string? english, string expected)
+ {
+ var converter = new PortugueseConverter(new ConversionOptions(YouToTu: false));
+ var reference = ReferenceHint.FromText(english);
+ var result = converter.Convert(source, reference: reference);
+ Assert.Equal(expected, result);
+ Assert.Equal(result, converter.Convert(result, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("Ela perguntou como diabos ela saberia.", null)]
+ [InlineData("Ele perguntou\ncomo diabos ela saberia?", null)]
+ [InlineData("Como diabos é que ela saberia?", null)]
+ [InlineData("Como ela saberia?", null)]
+ [InlineData("Como diabos saberia ela?", null)]
+ [InlineData("Como diabos ela saber?", null)]
+ [InlineData("Como diabos ela sabem?", null)]
+ [InlineData("Dutton foi uma burocrata até 15 minutos atrás.", null)]
+ [InlineData("Dutton foi uma burocrata até 2010.", "Dutton was a bureaucrat until 2010.")]
+ [InlineData("Ela foi médica durante dez anos até há duas horas.", "She was a doctor for ten years until two hours ago.")]
+ [InlineData("Ela não foi médica até há duas horas.", "She was not a doctor until two hours ago.")]
+ [InlineData("Ela foi agredida até há duas horas.", "She was attacked until two hours ago.")]
+ [InlineData("A reunião foi útil até há duas horas.", "The meeting was useful until two hours ago.")]
+ [InlineData("Ela foi médica até duas horas.", "She was a doctor until two hours ago.")]
+ [InlineData("O programa prioriza visar ao crime de rua.", null)]
+ [InlineData("O programa prioriza visar ao crime de rua.", "The program aims to prioritize street crime.")]
+ [InlineData("O programa prioriza visar ao crime de rua.", "The program prioritizes targeting organized crime.")]
+ [InlineData("O programa prioriza visar ao crime de rua.", "The program prioritizes targeting street crime victims.")]
+ [InlineData("O programa prioriza visar ao crime de rua organizado.", "The program prioritizes targeting street crime.")]
+ [InlineData("O programa visa priorizar os crimes de rua.", "The program prioritizes targeting street crime.")]
+ [InlineData("O programa prioriza combater os crimes de rua.", "The program prioritizes targeting street crime.")]
+ [InlineData("O programa visa o mercado externo.", "The program targets foreign markets.")]
+ public void IndirectQuestionsCompletedServicePassivesAndDifferentPolicyScopesAreProtected(string source, string? english) =>
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(YouToTu: false))
+ .Convert(source, reference: ReferenceHint.FromText(english)));
+
+ [Theory]
+ [InlineData("Como diabos ela saberia?", null)]
+ [InlineData("Como diabos ela o saberia?", null)]
+ [InlineData("Dutton foi uma burocrata até 15 minutos atrás.", "Dutton was a dog catcher till 15 minutes ago.")]
+ [InlineData("O programa prioriza visar ao crime de rua.", "The program prioritizes targeting street crime.")]
+ public void EveryWordBoundaryPreservesMeaningAndLineCount(string source, string? english)
+ {
+ var converter = new PortugueseConverter(new ConversionOptions(YouToTu: false));
+ var reference = ReferenceHint.FromText(english);
+ var expected = converter.Convert(source, reference: reference);
+ for (var index = 0; index < source.Length; index++)
+ {
+ if (source[index] != ' ') continue;
+ foreach (var newline in new[] { "\n", "\r\n" })
+ {
+ var trace = new List();
+ var result = converter.Convert(source[..index] + newline + source[(index + 1)..], reference: reference, trace: trace);
+ Assert.True(expected == Regex.Replace(result, @"\s+", " "),
+ $"Wrap at {index}, newline length {newline.Length}: {result}; trace: {string.Join("; ", trace)}");
+ Assert.Equal(1, result.Count(c => c == '\n'));
+ Assert.Equal(result, converter.Convert(result, reference: reference));
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData("Como diabos ela saberia?", null, "natural-word-order", "saberia", "Como diabos compreenderia ela?")]
+ [InlineData("Dutton foi uma burocrata até 15 minutos atrás.", "Dutton was a dog catcher till 15 minutes ago.",
+ "natural-word-order", "foi", "Dutton tornou-se uma burocrata até 15 minutos atrás.")]
+ [InlineData("O programa prioriza visar ao crime de rua.", "The program prioritizes targeting street crime.",
+ "dialogue-vocabulary", "prioriza", "O programa prefere visar ao crime de rua.")]
+ public void StageSwitchesIgnoredPhrasesAndCustomWordsRemainAuthoritative(
+ string source, string? english, string stage, string word, string customExpected)
+ {
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(YouToTu: false, DisabledRules: stage))
+ .Convert(source, reference: reference));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add(word);
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(YouToTu: false), localWordList: words)
+ .Convert(source, reference: reference));
+ words.Ignored.Clear();
+ words.Words.Add((word, word == "saberia" ? "compreenderia" : word == "foi" ? "tornou-se" : "prefere"));
+ Assert.Equal(customExpected, new PortugueseConverter(new ConversionOptions(YouToTu: false), localWordList: words)
+ .Convert(source, reference: reference));
+ }
+
+ [Fact]
+ public void OptionsAndIndependentSpeakersRemainAuthoritative()
+ {
+ const string question = "Como diabos ela saberia?";
+ foreach (var options in new[] { new ConversionOptions(Colloquial: false),
+ new ConversionOptions(Pronouns: false) })
+ Assert.Equal(question, new PortugueseConverter(options).Convert(question));
+ const string policy = "O programa prioriza visar ao crime de rua.";
+ Assert.Equal(policy, new PortugueseConverter(new ConversionOptions(Vocabulary: false))
+ .Convert(policy, reference: ReferenceHint.FromText("The program prioritizes targeting street crime.")));
+ const string speakers = "-Como diabos?\n-Ela saberia.";
+ Assert.Equal(speakers, new PortugueseConverter(new ConversionOptions(YouToTu: false)).Convert(speakers));
+ }
+
+ [Theory]
+ [InlineData("Como\nela o vê?", "Como\nela o vê?")]
+ [InlineData("Como diabos\nela o saberia?", "Como diabos\no saberia ela?")]
+ [InlineData("Como diabos eu vi ela?", "Como diabos eu a vi?")]
+ [InlineData("Ela saberia isso.", "Ela saberia isso.")]
+ [InlineData("Ela o saberia.", "Ela sabê-lo-ia.")]
+ [InlineData("Ela disse.\nEla o vê.", "Ela disse.\nEla vê-o.")]
+ [InlineData("Ainda não significa que\neu o deixaria ficar lá.", "Ainda não significa que\neu o deixaria ficar lá.")]
+ public void SharedPronounGuardsKeepSubjectsAndObjectsDistinct(string source, string expected) =>
+ Assert.Equal(expected, new PortugueseConverter(new ConversionOptions(YouToTu: false)).Convert(source));
+
+ [Fact]
+ public void SharedSubtitleConversionUsesAlignedPolicyAndOccupationReferences()
+ {
+ var blocks = new[] {
+ new SrtBlock { StartMs = 1000, EndMs = 2000, Text = "-Por favor.\n-Como diabos ela saberia?" },
+ new SrtBlock { StartMs = 2100, EndMs = 3000, Text = "Dutton foi uma burocrata\naté 15 minutos atrás." },
+ new SrtBlock { StartMs = 3100, EndMs = 4000, Text = "prioriza visar ao\ncrime de rua..." },
+ };
+ var result = SubtitleConversion.Convert(blocks, ConversionOptions.Default, english: new string?[] {
+ "Oh, please. How the hell would she know?", "Dutton was a dog catcher till 15 minutes ago.",
+ "prioritizes targeting street crime..." });
+ Assert.Equal("-Por favor.\n-Como diabos saberia ela?", result[0].Text);
+ Assert.Contains("era uma burocrata", result[1].Text);
+ Assert.Equal("dá prioridade ao combate\naos crimes de rua...", result[2].Text);
+ Assert.Equal("prioriza visar ao\ncrime de rua...", blocks[2].Text);
+ Assert.Equal(3100, blocks[2].StartMs);
+ Assert.Equal(4000, blocks[2].EndMs);
+ }
+}
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/EnglishExpressionTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/EnglishExpressionTests.cs
index 60120f46..01cb63ec 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/EnglishExpressionTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/EnglishExpressionTests.cs
@@ -1,4 +1,5 @@
using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese;
+using SubtitleEdit.Plugins.Shared;
namespace BrazilianToEuropeanPortuguese.Tests;
@@ -6,6 +7,10 @@ public sealed class EnglishExpressionTests
{
public static TheoryData ReviewedExpressions => new()
{
+ { "He's fishing for information.", "trying to elicit information indirectly", "Ele está à pesca de informações." },
+ { "Trying to get me to take chump bait.", "fall for a provocation", "A tentar que eu morda o isco." },
+ { "A cup of joe to go.", "cup of coffee", "Uma chávena de café para levar." },
+ { "Something's not adding up.", "does not make sense", "Algo não bate certo." },
{ "I busted my hump.", "work extremely hard", "Dei cabo das costas." },
{ "Stop busting my chops.", "harass", "Para de me chatear." },
{ "It's a piece of cake.", "very easy task", "É canja." },
@@ -121,6 +126,11 @@ public void ReviewedExpressionsHaveMeaningsAndHints(string english, string meani
[InlineData("I'm beaten.")]
[InlineData("I'm beat up.")]
[InlineData("You bet on the horse.")]
+ [InlineData("You're fishing.")]
+ [InlineData("We're fishing for trout.")]
+ [InlineData("The fish took the bait.")]
+ [InlineData("Joe's cup is empty.")]
+ [InlineData("Add up these numbers.")]
public void AmbiguousOrUnrecognisedEnglishHasNoInventedMeaning(string? english) =>
Assert.Null(IdiomHints.MeaningReference(english));
@@ -173,4 +183,119 @@ public void CorrectionsRespectExistingSwitches(string disabled)
Assert.Equal(converter.Convert("Estou quebrado."),
converter.Convert("Estou quebrado.", null, ReferenceHint.FromText("I'm skint.")));
}
+
+ [Theory]
+ [InlineData("Está jogando verde.", "You're fishing.", "Estás à pesca.")]
+ [InlineData("Ele está jogando verde.", "He's fishing.", "Ele está à pesca.")]
+ [InlineData("Ela estava jogando um verde.", "She was fishing.", "Ela estava à pesca.")]
+ [InlineData("Nós estamos a jogar verde.", "We're fishing.", "Nós estamos à pesca.")]
+ [InlineData("Ele jogou verde.", "He fished for information.", "Ele pescou.")]
+ [InlineData("Ele joga um verde para colher maduro.", "He fishes for information.", "Ele pesca.")]
+ [InlineData("ESTÁ JOGANDO VERDE!", "YOU'RE FISHING!", "ESTÁS À PESCA!")]
+ [InlineData("Está\njogando verde.", "You're fishing.", "Estás\nà pesca.")]
+ [InlineData("Espero que ele pegue a isca.", "I hope he takes the bait.", "Espero que ele morda o isco.")]
+ [InlineData("Ele pegou na isca.", "He took the bait.", "Ele mordeu o isco.")]
+ [InlineData("Não peguei a isca.", "I didn't take chump bait.", "Não mordi o isco.")]
+ [InlineData("E não somos jogadores.\nTemos um vídeo seu", "And we ain't no fishermen. We got video of you",
+ "E não somos pescadores.\nTemos um vídeo teu")]
+ [InlineData("Uma xícara de Joe para viagem.", "A cup of joe to go.", "Uma chávena de café para levar.")]
+ [InlineData("Uma chávena\r\nde Joe.", "A cup of joe.", "Uma chávena\r\nde café.")]
+ [InlineData("Há algo que não soma.", "Something's not adding up.", "Há algo que não bate certo.")]
+ [InlineData("A história não somava.", "The story didn't add up.", "A história não batia certo.")]
+ public void ReferenceMetaphorsKeepTheirMeaning(string source, string english, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = ReferenceHint.FromText(english);
+ Assert.Equal(expected, converter.Convert(source, reference: reference));
+ Assert.Equal(expected, converter.Convert(expected, reference: reference));
+ }
+
+ [Theory]
+ [InlineData("Ele está jogando verde.", null)]
+ [InlineData("Ele está jogando verde.", "He is playing green.")]
+ [InlineData("Ele está jogando verde.", "He is fishing for trout in the river.")]
+ [InlineData("Ele joga verde claro.", "He's fishing.")]
+ [InlineData("Ele pegou a isca.", "He picked up the bait.")]
+ [InlineData("Ele pegou a isca.", "He took the bait off the hook.")]
+ [InlineData("Não somos jogadores.", null)]
+ [InlineData("Não somos jogadores.", "We aren't players.")]
+ [InlineData("Somos jogadores de futebol.", "We are fishermen.")]
+ [InlineData("Somos jogadores. Eles são jogadores.", "We are fishermen.")]
+ [InlineData("Somos jogadores.", "We're players, not fishermen.")]
+ [InlineData("A xícara de Joe está vazia.", "Joe's cup is empty.")]
+ [InlineData("Não soma dois e três.", "Add up two and three.")]
+ public void MetaphorsNeedBothLanguagesAndProtectLiteralComplements(string source, string? english)
+ {
+ var converter = new PortugueseConverter();
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: ReferenceHint.FromText(english)));
+ }
+
+ [Fact]
+ public void FishingGlossRequiresPortugueseEvidenceAndKeepsContext()
+ {
+ Assert.Equal("You're trying to elicit information indirectly.",
+ IdiomHints.MeaningReference("You're fishing.", "Está jogando verde."));
+ Assert.Null(IdiomHints.MeaningReference("You're fishing.", "Está a pescar."));
+ Assert.Null(IdiomHints.MeaningReference("You're fishing in the river.", "Está jogando verde."));
+ Assert.Equal("Something does not make sense.", IdiomHints.MeaningReference("Something's not adding up."));
+ Assert.NotNull(IdiomHints.Note("Está a jogar verde.", "You're fishing."));
+ Assert.Null(IdiomHints.Note("Estás à pesca.", "You're fishing."));
+ }
+
+ [Fact]
+ public void ReferenceMetaphorsRespectOptionsAndLocalVocabulary()
+ {
+ const string source = "Ele está jogando verde.";
+ var reference = ReferenceHint.FromText("He's fishing.");
+ foreach (var options in new[] { new ConversionOptions(Vocabulary: false),
+ new ConversionOptions(Colloquial: false), new ConversionOptions(DisabledRules: "reference-idioms") })
+ {
+ var converter = new PortugueseConverter(options);
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: reference));
+ }
+ foreach (var word in new[] { "jogar", "verde" })
+ {
+ var local = new PortugueseLocalWordList();
+ local.Ignored.Add(word);
+ var converter = new PortugueseConverter(localWordList: local);
+ Assert.Equal(converter.Convert(source), converter.Convert(source, reference: reference));
+ }
+ var custom = new PortugueseLocalWordList();
+ custom.Words.Add(("jogar", "brincar"));
+ var overridden = new PortugueseConverter(localWordList: custom);
+ Assert.Equal(overridden.Convert(source), overridden.Convert(source, reference: reference));
+ Assert.Equal("Está à pesca.", new PortugueseConverter(new ConversionOptions(YouToTu: false))
+ .Convert("Está jogando verde.", reference: ReferenceHint.FromText("You're fishing.")));
+ Assert.Equal("O senhor está à pesca.", new PortugueseConverter()
+ .Convert("O senhor está jogando verde.", reference: ReferenceHint.FromText("You're fishing.")));
+ }
+
+ [Fact]
+ public void FishingSequenceKeepsCueBoundariesTimesAndOriginals()
+ {
+ var blocks = new[]
+ {
+ new SrtBlock { StartMs = 1134014, EndMs = 1135676, Text = "Está jogando verde." },
+ new SrtBlock { StartMs = 1135678, EndMs = 1137105, Text = "Tentando que eu pegue\na isca," },
+ new SrtBlock { StartMs = 1137106, EndMs = 1138268, Text = "mas não sou tolo." },
+ new SrtBlock { StartMs = 1138270, EndMs = 1139815, Text = "E não somos jogadores.\nTemos um vídeo seu" },
+ };
+ var originals = blocks.Select(b => (b.Text, b.StartMs, b.EndMs)).ToArray();
+ var english = new[]
+ {
+ new SrtBlock { StartMs = 1133633, EndMs = 1135335, Text = "You're fishing." },
+ new SrtBlock { StartMs = 1135368, EndMs = 1136736, Text = "Trying to get me\nto take chump bait," },
+ new SrtBlock { StartMs = 1136769, EndMs = 1138238, Text = "but I ain't no dummy." },
+ new SrtBlock { StartMs = 1138271, EndMs = 1139772, Text = "And we ain't no fishermen.\nWe got video of you" },
+ };
+ var aligned = ReferenceAligner.AlignByTiming(blocks, english);
+ var converted = SubtitleConversion.Convert(blocks, ConversionOptions.Default, english: aligned.Texts).ToArray();
+ Assert.Equal(4, converted.Length);
+ Assert.Equal("Estás à pesca.", converted[0].Text);
+ Assert.Contains("morda\no isco,", converted[1].Text);
+ Assert.Equal("E não somos pescadores.\nTemos um vídeo teu", converted[3].Text);
+ Assert.Equal(originals, blocks.Select(b => (b.Text, b.StartMs, b.EndMs)).ToArray());
+ Assert.Contains(converted[0].Rules, rule => rule.Id == "reference-idioms");
+ Assert.Contains(converted[3].Rules, rule => rule.Id == "reference-idioms");
+ }
}
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/ExplanationTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/ExplanationTests.cs
index 2f761d58..13386fee 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/ExplanationTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/ExplanationTests.cs
@@ -45,7 +45,8 @@ public void AddressContinuesAcrossCuesWithoutRequiringSentenceContext(bool sente
var blocks = Scene();
var result = SubtitleConversion.Convert(blocks, new ConversionOptions(SentenceContext: sentenceContext),
english: new string?[] { "What if you got it in your head", "to seek revenge in kind?" });
- Assert.Equal("E se meteste\nna tua cabeça", result[0].Text);
+ // Inalienable possession: "in your head" with a "you" subject drops the possessive ("na cabeça").
+ Assert.Equal("E se meteste\nna cabeça", result[0].Text);
Assert.Equal("que terias a tua vingança?", result[1].Text);
Assert.Contains(result[1].Rules, r => r.Id == "address");
Assert.Equal(1195438, blocks[1].StartMs);
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/FormalPersonalObjectTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/FormalPersonalObjectTests.cs
new file mode 100644
index 00000000..0dfddcf6
--- /dev/null
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/FormalPersonalObjectTests.cs
@@ -0,0 +1,139 @@
+using System.Text.RegularExpressions;
+using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese;
+using SubtitleEdit.Plugins.Shared;
+
+namespace BrazilianToEuropeanPortuguese.Tests;
+
+public sealed class FormalPersonalObjectTests
+{
+ [Theory]
+ [InlineData("Vejo você no tribunal.", "Vejo-o no tribunal.", "Vejo-te no tribunal.")]
+ [InlineData("Vejo você amanhã.", "Vejo-o amanhã.", "Vejo-te amanhã.")]
+ [InlineData("Não vejo você no tribunal.", "Não o vejo no tribunal.", "Não te vejo no tribunal.")]
+ [InlineData("Nunca vi você aqui.", "Nunca o vi aqui.", "Nunca te vi aqui.")]
+ [InlineData("Sei que ele viu você.", "Sei que ele o viu.", "Sei que ele te viu.")]
+ [InlineData("Eu vi você.", "Eu vi-o.", "Eu vi-te.")]
+ [InlineData("Ela encontrou você.", "Ela encontrou-o.", "Ela encontrou-te.")]
+ [InlineData("Eles encontraram você.", "Eles encontraram-no.", "Eles encontraram-te.")]
+ [InlineData("Ele vê você.", "Ele vê-o.", "Ele vê-te.")]
+ [InlineData("Ele fez você rir.", "Ele fê-lo rir.", "Ele fez-te rir.")]
+ [InlineData("Quero ver você.", "Quero vê-lo.", "Quero ver-te.")]
+ [InlineData("Manterei você informado.", "Mantê-lo-ei informado.", "Manter-te-ei informado.")]
+ [InlineData("Não manterei você informado.", "Não o manterei informado.", "Não te manterei informado.")]
+ [InlineData("Ele considerou você inocente.", "Ele considerou-o inocente.", "Ele considerou-te inocente.")]
+ [InlineData("Acabei defendendo você.", "Acabei por defendê-lo.", "Acabei por defender-te.")]
+ [InlineData("VEJO VOCÊ NO TRIBUNAL.", "VEJO-O NO TRIBUNAL.", "VEJO-TE NO TRIBUNAL.")]
+ [InlineData("Vejo você no tribunal.", "Vejo-o no tribunal.", "Vejo-te no tribunal.")]
+ public void TheSameObjectRuleSupportsFormalAndInformalAddress(string source, string formal, string informal)
+ {
+ foreach (var (mode, expected) in new[] { (AddressMode.Formal, formal), (AddressMode.Informal, informal) })
+ {
+ var options = new ConversionOptions(Address: mode);
+ var block = new SrtBlock { StartMs = 1000, EndMs = 2000, Text = source };
+ var result = SubtitleConversion.Convert([block], options)[0].Text;
+ Assert.Equal(expected, result);
+ Assert.Equal(result, SubtitleConversion.Convert(
+ [new SrtBlock { StartMs = 1000, EndMs = 2000, Text = result }], options)[0].Text);
+ }
+ }
+
+ [Theory]
+ [InlineData("Doutora, vejo você no tribunal.", "Doutora, vejo-a no tribunal.")]
+ [InlineData("Senhora, vi você aqui.", "Senhora, vi-a aqui.")]
+ [InlineData("Vejo você no tribunal, doutora.", "Vejo-a no tribunal, doutora.")]
+ [InlineData("Vejo você no tribunal, senhora Silva.", "Vejo-a no tribunal, senhora Silva.")]
+ [InlineData("Doutor, vejo você no tribunal.", "Doutor, vejo-o no tribunal.")]
+ [InlineData("A doutora viu você.", "A doutora viu-o.")]
+ [InlineData("Falei com a senhora. Vejo você amanhã.", "Falei com a senhora. Vejo-o amanhã.")]
+ [InlineData("-Doutora, ouviu?\n-Vejo você no tribunal.", "-Doutora, ouviu?\n-Vejo-o no tribunal.")]
+ public void OnlyExplicitLocalFeminineAddressSelectsA(string source, string expected) =>
+ Assert.Equal(expected, new PortugueseConverter().Convert(source,
+ reference: new ReferenceHint(false, false, false, false) { FormalAddress = true }));
+
+ [Theory]
+ [InlineData("É você.")]
+ [InlineData("Parece você.")]
+ [InlineData("Vejo você mesma no tribunal.")]
+ [InlineData("A honra você.")]
+ [InlineData("O contacto você.")]
+ [InlineData("Esperei você pedir ajuda.")]
+ public void PredicatesEmphasisNominalHomographsAndInfinitiveSubjectsAreProtected(string source) =>
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(Colloquial: false)).Convert(source,
+ reference: new ReferenceHint(false, false, false, false) { FormalAddress = true }));
+
+ [Theory]
+ [InlineData("Agora, por que ajudaria você?", "Agora, porque o ajudaria?")]
+ [InlineData("Então não foi a sandes que realmente trouxe você aqui.", "Então não foi a sandes que realmente o trouxe aqui.")]
+ [InlineData("Também sei que a presidente da câmara confia e respeita você,", "Também sei que a presidente da câmara confia e o respeita,")]
+ [InlineData("Ele confia e respeita você.", "Ele confia e respeita-o.")]
+ [InlineData("- Espera lá!\n- Não, espera você!", "- Espera lá!\n- Não, espera você!")]
+ [InlineData("Manda você.", "Manda você.")]
+ [InlineData("Acabei defendendo-o.", "Acabei por defendê-lo.")]
+ [InlineData("Acabei defendendo-te.", "Acabei por defender-te.")]
+ public void CorpusInteractionsPreserveAttractionEmphaticSubjectsAndProgressiveConstructions(string source, string expected) =>
+ Assert.Equal(expected, new PortugueseConverter().Convert(source,
+ reference: new ReferenceHint(false, false, false, false) { FormalAddress = true }));
+
+ [Theory]
+ [InlineData("Vejo você no tribunal.", "Vejo-o no tribunal.")]
+ [InlineData("Não vejo você no tribunal.", "Não o vejo no tribunal.")]
+ [InlineData("Doutora, vejo você no tribunal.", "Doutora, vejo-a no tribunal.")]
+ [InlineData("Vejo você no tribunal, doutora.", "Vejo-a no tribunal, doutora.")]
+ [InlineData("Então não foi a sandes que realmente trouxe você aqui.", "Então não foi a sandes que realmente o trouxe aqui.")]
+ [InlineData("Sei que a presidente da câmara confia e respeita você.", "Sei que a presidente da câmara confia e o respeita.")]
+ public void EveryWordBoundaryPreservesFormalPlacement(string source, string expected)
+ {
+ var converter = new PortugueseConverter();
+ var reference = new ReferenceHint(false, false, false, false) { FormalAddress = true };
+ for (var index = 0; index < source.Length; index++)
+ {
+ if (source[index] != ' ') continue;
+ foreach (var newline in new[] { "\n", "\r\n" })
+ {
+ var result = converter.Convert(source[..index] + newline + source[(index + 1)..], reference: reference);
+ Assert.Equal(expected, Regex.Replace(result, @"\s+", " "));
+ Assert.Equal(1, result.Count(c => c == '\n'));
+ Assert.Equal(result, converter.Convert(result, reference: reference));
+ }
+ }
+ }
+
+ [Fact]
+ public void FormalConversionRespectsSettingsIgnoredTextAndLocalWords()
+ {
+ const string source = "Vejo você no tribunal.";
+ var reference = new ReferenceHint(false, false, false, false) { FormalAddress = true };
+ foreach (var options in new[] { new ConversionOptions(Pronouns: false),
+ new ConversionOptions(YouToTu: false), new ConversionOptions(DisabledRules: "address") })
+ Assert.Equal(source, new PortugueseConverter(options).Convert(source, reference: reference));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("você");
+ Assert.Equal(source, new PortugueseConverter(localWordList: words).Convert(source, reference: reference));
+ words.Ignored.Clear();
+ words.Words.Add(("vejo", "encontro"));
+ Assert.Equal("Encontro-o no tribunal.", new PortugueseConverter(localWordList: words)
+ .Convert(source, reference: reference));
+ Assert.Equal("Acabei defendendo-o.", new PortugueseConverter(new ConversionOptions(Progressive: false))
+ .Convert("Acabei defendendo você.", reference: reference));
+ Assert.Equal("Acabei defendendo-o.", new PortugueseConverter(new ConversionOptions(DisabledRules: "progressive,progressive-command"))
+ .Convert("Acabei defendendo você.", reference: reference));
+ }
+
+ [Fact]
+ public void NearbyFormalAddressExplainsTheSharedScreenshotFailure()
+ {
+ var blocks = new[] {
+ new SrtBlock { StartMs = 269000, EndMs = 271500, Text = "A reunião acabou, doutora." },
+ new SrtBlock { StartMs = 271664, EndMs = 273426, Text = "Vejo você no tribunal." },
+ };
+ var english = new string?[] { "The meeting is over, ma'am.", "I'll see you in court." };
+ var automatic = SubtitleConversion.Convert(blocks, ConversionOptions.Default, english: english);
+ Assert.True(automatic[1].Hint?.FormalAddress);
+ Assert.Equal("Vejo-o no tribunal.", automatic[1].Text);
+ var informal = SubtitleConversion.Convert(blocks, new ConversionOptions(Address: AddressMode.Informal), english: english);
+ Assert.Equal("Vejo-te no tribunal.", informal[1].Text);
+ Assert.Equal("Vejo você no tribunal.", blocks[1].Text);
+ Assert.Equal(271664, blocks[1].StartMs);
+ Assert.Equal(273426, blocks[1].EndMs);
+ }
+}
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/InterrogationAgreementTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/InterrogationAgreementTests.cs
index f9f56d03..ecce4a25 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/InterrogationAgreementTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/InterrogationAgreementTests.cs
@@ -59,6 +59,15 @@ public void OtherAndUncertainSubjectsStayProtected(string source, string? englis
[InlineData("Receio que ele não pode vir.", null, "Receio que ele não possa vir.")]
[InlineData("RECEIO QUE ESTÁS PRESO.", null, "RECEIO QUE ESTEJAS PRESO.")]
[InlineData("Receio que estás preso.", null, "Receio que estejas preso.")]
+ [InlineData("Espero que ambos precisam\nse conhecer de novo.", null, "Espero que ambos precisem\nde se conhecer de novo.")]
+ [InlineData("Espero que ambas precisam de ajuda.", null, "Espero que ambas precisem de ajuda.")]
+ [InlineData("Esperamos que eles podem vir.", null, "Esperamos que eles possam vir.")]
+ [InlineData("Espero que tu sabes.", null, "Espero que tu saibas.")]
+ [InlineData("Espero que a equipa chega cedo.", null, "Espero que a equipa chegue cedo.")]
+ [InlineData("Esperemos que todos estão bem.", null, "Esperemos que todos estejam bem.")]
+ [InlineData("Desejo que ambos ficam bem.", null, "Desejo que ambos fiquem bem.")]
+ [InlineData("Temo que ambas enganam-se.", null, "Temo que ambas se enganem.")]
+ [InlineData("ESPERO QUE AMBOS PRECISAM DE AJUDA.", null, "ESPERO QUE AMBOS PRECISEM DE AJUDA.")]
public void ComplementMoodPreservesSubject(string source, string? english, string expected)
{
var converter = new PortugueseConverter();
@@ -76,6 +85,12 @@ public void ComplementMoodPreservesSubject(string source, string? english, strin
[InlineData("Disse que ele está preso.")]
[InlineData("Acho que ele está preso.")]
[InlineData("Receio que estejas preso.")]
+ [InlineData("Espero que ambos precisem de ajuda.")]
+ [InlineData("Espero que ele precisava de ajuda.")]
+ [InlineData("Espero que sim.")]
+ [InlineData("Espero que não.")]
+ [InlineData("A espera que ele tem.")]
+ [InlineData("Ele espera o comboio.")]
public void NonTargetsStayUnchanged(string source) =>
Assert.Equal(source, new PortugueseConverter().Convert(source));
@@ -106,6 +121,11 @@ public void ComplementRuleCanBeDisabled() =>
Assert.Equal("Receio que estás preso.", new PortugueseConverter(
new ConversionOptions(DisabledRules: "complement-subjunctive")).Convert("Receio que estás preso."));
+ [Fact]
+ public void HopeComplementUsesTheSameRuleSwitch() =>
+ Assert.Equal("Espero que ambos precisam de ajuda.", new PortugueseConverter(
+ new ConversionOptions(DisabledRules: "complement-subjunctive")).Convert("Espero que ambos precisam de ajuda."));
+
[Fact]
public void IgnoredPhraseRemainsAuthoritative()
{
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/LocalChecksTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/LocalChecksTests.cs
index 8d5c5d13..e8e6f2fc 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/LocalChecksTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/LocalChecksTests.cs
@@ -68,6 +68,20 @@ public async Task IdiomMeaningChecksOriginalAndConvertedAgainstPlainEnglish()
Assert.Equal(new[] { "It's a very easy task.", line.Original, line.Converted }, seen);
}
+ [Fact]
+ public async Task FishingMeaningUsesOriginalPortugueseToDisambiguateReference()
+ {
+ var seen = new List();
+ Task Embed(string? text)
+ {
+ seen.Add(text);
+ return Task.FromResult(new[] { 1f, 0f });
+ }
+ var line = new CheckLine(0, "Está jogando verde.", "Estás à pesca.", null, "You're fishing.");
+ Assert.Null(await LocalChecks.IdiomMeaningNoteAsync(line, line.Converted, Embed));
+ Assert.Equal(new[] { "You're trying to elicit information indirectly.", line.Original, line.Converted }, seen);
+ }
+
[Theory]
[InlineData(.8f, .8f, false)]
[InlineData(.2f, .2f, true)]
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/PersonalObjectTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/PersonalObjectTests.cs
index 3fbfc08b..3a40ec09 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/PersonalObjectTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/PersonalObjectTests.cs
@@ -75,7 +75,7 @@ public void OptionsAndIgnoredFormsKeepPriority()
{
const string source = "Seu pai bateu você.";
Assert.Equal("O seu pai bateu você.", new PortugueseConverter(new ConversionOptions(YouToTu: false)).Convert(source));
- Assert.Equal("O seu pai bateu você.", new PortugueseConverter().Convert(source,
+ Assert.Equal("O seu pai bateu-o.", new PortugueseConverter().Convert(source,
reference: ReferenceHint.FromText("Your dad beat you.")! with { FormalAddress = true }));
var local = new PortugueseLocalWordList();
local.Ignored.Add("bateu você");
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/PorFamilyCliticTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/PorFamilyCliticTests.cs
new file mode 100644
index 00000000..64178654
--- /dev/null
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/PorFamilyCliticTests.cs
@@ -0,0 +1,119 @@
+using System.Text.RegularExpressions;
+using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese;
+using SubtitleEdit.Plugins.Shared;
+
+namespace BrazilianToEuropeanPortuguese.Tests;
+
+public sealed class PorFamilyCliticTests
+{
+ [Theory]
+ [InlineData("O prefeito interino\nse opõe ao plano.", "O presidente da câmara interino\nopõe-se ao plano.")]
+ [InlineData("O presidente da câmara interino se opõe ao plano.", "O presidente da câmara interino opõe-se ao plano.")]
+ [InlineData("A presidente da câmara interina se opõe ao plano.", "A presidente da câmara interina opõe-se ao plano.")]
+ [InlineData("Os presidentes se opõem ao plano.", "Os presidentes opõem-se ao plano.")]
+ [InlineData("O diretor se opunha ao plano.", "O diretor opunha-se ao plano.")]
+ [InlineData("Os diretores se opunham ao plano.", "Os diretores opunham-se ao plano.")]
+ [InlineData("O diretor se opôs ao plano.", "O diretor opôs-se ao plano.")]
+ [InlineData("Os diretores se opuseram ao plano.", "Os diretores opuseram-se ao plano.")]
+ [InlineData("O diretor se dispôs a ajudar.", "O diretor dispôs-se a ajudar.")]
+ [InlineData("O diretor se expõe ao perigo.", "O diretor expõe-se ao perigo.")]
+ [InlineData("O diretor se propõe a ajudar.", "O diretor propõe-se a ajudar.")]
+ [InlineData("O diretor se impõe.", "O diretor impõe-se.")]
+ [InlineData("O diretor se recompôs.", "O diretor recompôs-se.")]
+ [InlineData("A equipa se compõe de três pessoas.", "A equipa compõe-se de três pessoas.")]
+ [InlineData("O diretor se contrapõe ao plano.", "O diretor contrapõe-se ao plano.")]
+ [InlineData("O diretor se interpõe.", "O diretor interpõe-se.")]
+ [InlineData("O diretor se repôs.", "O diretor repôs-se.")]
+ [InlineData("O diretor se sobrepõe.", "O diretor sobrepõe-se.")]
+ [InlineData("O diretor lhe propôs um acordo.", "O diretor propôs-lhe um acordo.")]
+ [InlineData("O diretor me propôs um acordo.", "O diretor propôs-me um acordo.")]
+ [InlineData("O diretor o expôs.", "O diretor expô-lo.")]
+ [InlineData("Eles se opõem ao plano.", "Eles opõem-se ao plano.")]
+ [InlineData("Ela se opunha ao plano.", "Ela opunha-se ao plano.")]
+ [InlineData("Hoje se opõem ao plano.", "Hoje opõem-se ao plano.")]
+ [InlineData("E depois se recompôs.", "E depois recompôs-se.")]
+ [InlineData("O PRESIDENTE DA CÂMARA INTERINO SE OPÕE AO PLANO.", "O PRESIDENTE DA CÂMARA INTERINO OPÕE-SE AO PLANO.")]
+ [InlineData("O presidente se opõe ao plano.", "O presidente opõe-se ao plano.")]
+ [InlineData("O presidente não opõe-se ao plano.", "O presidente não se opõe ao plano.")]
+ [InlineData("O presidente nunca opôs-se ao plano.", "O presidente nunca se opôs ao plano.")]
+ [InlineData("Quando ele opõe-se ao plano.", "Quando ele se opõe ao plano.")]
+ [InlineData("Se ele propõe-se a ajudar.", "Se ele se propõe a ajudar.")]
+ public void ReviewedPorParadigmsUseTheExistingPlacementRules(string source, string expected)
+ {
+ var converter = new PortugueseConverter(new ConversionOptions(YouToTu: false));
+ var result = converter.Convert(source);
+ Assert.Equal(expected, result);
+ Assert.Equal(result, converter.Convert(result));
+ }
+
+ [Theory]
+ [InlineData("O presidente não se opõe ao plano.")]
+ [InlineData("O presidente já se opõe ao plano.")]
+ [InlineData("Só o presidente se opõe ao plano.")]
+ [InlineData("Todos se opõem ao plano.")]
+ [InlineData("Que ele se oponha ao plano.")]
+ [InlineData("Disse que o presidente da câmara interino se opõe ao plano.")]
+ [InlineData("Quando o presidente da câmara interino se opõe ao plano.")]
+ [InlineData("Se o presidente da câmara interino se opõe ao plano.")]
+ [InlineData("Acredito que a minha nova equipa se dispõe a ajudar.")]
+ [InlineData("Ele verifica se opõe resistência.")]
+ [InlineData("Ele perguntou se expõe os quadros.")]
+ [InlineData("O presidente da câmara interino opõe-se ao plano.")]
+ [InlineData("Quero saber se o presidente se opôs ao plano.")]
+ [InlineData("O diretor se opusesse ao plano.")]
+ [InlineData("O diretor se opor ao plano.")]
+ [InlineData("O diretor o posto.")]
+ [InlineData("O diretor a proposta.")]
+ public void AttractorsConditionalSeNonFiniteFormsAndAlreadyCorrectTextAreProtected(string source) =>
+ Assert.Equal(source, new PortugueseConverter(new ConversionOptions(YouToTu: false)).Convert(source));
+
+ [Theory]
+ [InlineData("O presidente da câmara interino se opõe ao plano.")]
+ [InlineData("Disse que o presidente da câmara interino se opõe ao plano.")]
+ [InlineData("O presidente não opõe-se ao plano.")]
+ [InlineData("O diretor lhe propôs um acordo.")]
+ public void EveryWordBoundaryPreservesPlacementAndLineCount(string source)
+ {
+ var converter = new PortugueseConverter(new ConversionOptions(YouToTu: false));
+ var expected = converter.Convert(source);
+ for (var index = 0; index < source.Length; index++)
+ {
+ if (source[index] != ' ') continue;
+ foreach (var newline in new[] { "\n", "\r\n" })
+ {
+ var result = converter.Convert(source[..index] + newline + source[(index + 1)..]);
+ Assert.Equal(expected, Regex.Replace(result, @"\s+", " "));
+ Assert.Equal(1, result.Count(c => c == '\n'));
+ Assert.Equal(result, converter.Convert(result));
+ }
+ }
+ }
+
+ [Fact]
+ public void OptionsIgnoredWordsAndCustomWordsRemainAuthoritative()
+ {
+ const string source = "O presidente se opõe ao plano.";
+ foreach (var options in new[] { new ConversionOptions(Pronouns: false),
+ new ConversionOptions(DisabledRules: "pronouns") })
+ Assert.Equal(source, new PortugueseConverter(options).Convert(source));
+ var words = new PortugueseLocalWordList();
+ words.Ignored.Add("opõe");
+ Assert.Equal(source, new PortugueseConverter(localWordList: words).Convert(source));
+ words.Ignored.Clear();
+ words.Words.Add(("opõe", "contrapõe"));
+ Assert.Equal("O presidente contrapõe-se ao plano.",
+ new PortugueseConverter(localWordList: words).Convert(source));
+ }
+
+ [Fact]
+ public void ExactSharedSubtitleKeepsAccentsTimingAndInput()
+ {
+ var block = new SrtBlock { StartMs = 135636, EndMs = 139191, Text = "O prefeito interino\nse opõe ao plano." };
+ var result = SubtitleConversion.Convert([block], ConversionOptions.Default,
+ english: new string?[] { "The interim mayor opposes the plan." });
+ Assert.Equal("O presidente da câmara interino\nopõe-se ao plano.", result[0].Text);
+ Assert.Equal("O prefeito interino\nse opõe ao plano.", block.Text);
+ Assert.Equal(135636, block.StartMs);
+ Assert.Equal(139191, block.EndMs);
+ }
+}
diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/ReferenceTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/ReferenceTests.cs
index 8b5a03f3..6d72429b 100644
--- a/se5/BrazilianToEuropeanPortuguese.Tests/ReferenceTests.cs
+++ b/se5/BrazilianToEuropeanPortuguese.Tests/ReferenceTests.cs
@@ -346,6 +346,142 @@ public void EmbeddingAlignmentDropsPairsThatAreNotTranslations()
Assert.Equal(1, alignment.Rejected);
}
+ [Theory]
+ [InlineData("You're fishing.", "You're fishing.")]
+ [InlineData("You are fishing for information.", "You are fishing for information.")]
+ [InlineData("You're fishing for trout.", null)]
+ [InlineData("You're fishing in the river.", null)]
+ [InlineData("You're not fishing.", null)]
+ [InlineData("Sit down.", null)]
+ public void LowSimilarityFishingIdiomNeedsMatchingEnglishAndOverlap(string english, string? expected)
+ {
+ var source = new[] { Cue(1134014, 1135676, "Está jogando verde.") };
+ var reference = new[] { Cue(1133633, 1135335, english) };
+ var alignment = ReferenceAligner.AlignWithEmbeddings(source, reference,
+ new[] { Basis(0, 2) }, new[] { Basis(1, 2) });
+ Assert.Equal(expected, alignment.Texts[0]);
+ Assert.Equal(expected == null ? 1 : 0, alignment.Rejected);
+ if (expected != null)
+ {
+ var result = SubtitleConversion.Convert(source, ConversionOptions.Default, english: alignment.Texts);
+ Assert.Equal("Estás à pesca.", Assert.Single(result).Text);
+ }
+ reference[0] = Cue(1140000, 1141500, english);
+ Assert.Null(ReferenceAligner.AlignWithEmbeddings(source, reference,
+ new[] { Basis(0, 2) }, new[] { Basis(1, 2) }).Texts[0]);
+ }
+
+ [Fact]
+ public void LowSimilarityIdiomFallbackDoesNotRecoverAmbiguousReferences()
+ {
+ var source = new[] { Cue(1000, 3000, "Está jogando verde.") };
+ var reference = new[] { Cue(1000, 2000, "You're fishing."), Cue(2000, 3000, "He's fishing.") };
+ var result = ReferenceAligner.AlignByTiming(source, reference, pairSimilarity: (_, _) => 0);
+ Assert.Null(result.Texts[0]);
+ Assert.Equal(1, result.Rejected);
+ }
+
+ [Fact]
+ public void LowSimilarityIdiomFallbackDoesNotAttachNeighbouringDialogue()
+ {
+ var source = new[] { Cue(1000, 3000, "Está jogando verde.") };
+ var reference = new[] { Cue(1000, 2000, "You're fishing."), Cue(2000, 3000, "Sit down.") };
+ var result = ReferenceAligner.AlignByTiming(source, reference, pairSimilarity: (_, _) => 0);
+ Assert.Equal("You're fishing.", result.Texts[0]);
+ }
+
+ [OptInFact("PTPT_TEST_IDIOM_MODEL", "real MiniLM fishing alignment")]
+ public async Task RealMiniLmFishingPairAlignment()
+ {
+ var source = new[] { Cue(1134014, 1135676, "Está jogando verde.") };
+ var reference = new[] { Cue(1133633, 1135335, "You're fishing.") };
+ using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(3));
+ await using var runtime = await EmbeddingRuntime.StartAsync(RealSettings(false),
+ new Progress(output.WriteLine), timeout.Token);
+ var pt = await runtime.EmbedAsync(source[0].Text, timeout.Token);
+ var en = await runtime.EmbedAsync(reference[0].Text, timeout.Token);
+ output.WriteLine($"Fishing pair similarity: {ReferenceAligner.Dot(pt, en):0.000} (floor {ReferenceAligner.PairSimilarityFloor:0.000}).");
+ var alignment = ReferenceAligner.AlignWithEmbeddings(source, reference, new[] { pt }, new[] { en });
+ Assert.Equal("You're fishing.", alignment.Texts[0]);
+ Assert.Equal("Estás à pesca.", Assert.Single(SubtitleConversion.Convert(source,
+ ConversionOptions.Default, english: alignment.Texts)).Text);
+ }
+
+ [OptInFact("PTPT_TEST_IDIOM_MODEL", "real MiniLM police and progressive alignment")]
+ public async Task RealMiniLmPoliceAndProgressiveAlignment()
+ {
+ var pairs = new[]
+ {
+ (Cue(1083239, 1084315, "Veio para acompanhá-lo\nà sala do esquadrão."),
+ Cue(1082149, 1084251, "Came down to escort you\nto the squad room."), "sala da esquadra"),
+ (Cue(1747969, 1749413, "Seu advogado\nveio ao esquadrão,"),
+ Cue(1747914, 1749381, "His lawyer\ncame by the squad,"), "à esquadra"),
+ (Cue(2039878, 2041771, "-Certo.\n-Não é verdade! Ele mente."),
+ Cue(2040106, 2041740, "Okay...\nThat's not true!\nHe's lying."), "está a mentir"),
+ (Cue(2047346, 2049166, "-Entende? Ele mente.\n-Ótimo. Então não terá problemas"),
+ Cue(2047346, 2049248, "Do you understand? He's lying.\nThat's great.\nSo you won't have any problems"), "está a mentir"),
+ (Cue(2049259, 2052020, "-em vir ao esquadrão.\n-Ele mente."),
+ Cue(2049281, 2051717, "coming down to the squad.\nCome on.\nHe's lying."), "está a mentir"),
+ };
+ using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(3));
+ await using var runtime = await EmbeddingRuntime.StartAsync(RealSettings(false),
+ new Progress(output.WriteLine), timeout.Token);
+ foreach (var (source, reference, expected) in pairs)
+ {
+ var pt = await runtime.EmbedAsync(source.Text, timeout.Token);
+ var en = await runtime.EmbedAsync(reference.Text, timeout.Token);
+ output.WriteLine($"Pair similarity: {ReferenceAligner.Dot(pt, en):0.000} (floor {ReferenceAligner.PairSimilarityFloor:0.000}).");
+ var alignment = ReferenceAligner.AlignWithEmbeddings(new[] { source }, new[] { reference }, new[] { pt }, new[] { en });
+ Assert.NotNull(alignment.Texts[0]);
+ var converted = Assert.Single(SubtitleConversion.Convert(new[] { source }, ConversionOptions.Default, english: alignment.Texts));
+ Assert.Contains(expected, converted.Text.Replace("\n", " "));
+ }
+ }
+
+ [OptInFact("PTPT_TEST_IDIOM_MODEL", "real MiniLM hypothetical comparison alignment")]
+ public async Task RealMiniLmHypotheticalComparisonAlignment()
+ {
+ var source = new[]
+ {
+ Cue(2350362, 2352514, "Como eu fosse um palhaço?"),
+ Cue(2352514, 2355504, "Como divertisse você?"),
+ };
+ var english = new[]
+ {
+ Cue(2350081, 2352418, "Like I'm a clown?"),
+ Cue(2352451, 2355053, "Like I amuse you?"),
+ };
+ using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(3));
+ await using var runtime = await EmbeddingRuntime.StartAsync(RealSettings(false),
+ new Progress(output.WriteLine), timeout.Token);
+ var pt = await runtime.EmbedCuesAsync(source.Select(c => c.Text).ToList(), new Progress(_ => { }), "pt", timeout.Token);
+ var en = await runtime.EmbedCuesAsync(english.Select(c => c.Text).ToList(), new Progress(_ => { }), "en", timeout.Token);
+ for (var i = 0; i < source.Length; i++)
+ output.WriteLine($"Comparison pair {i}: {ReferenceAligner.Dot(Assert.IsType(pt[i]), Assert.IsType(en[i])):0.000} (floor {ReferenceAligner.PairSimilarityFloor:0.000}).");
+ var alignment = ReferenceAligner.AlignWithEmbeddings(source, english, pt, en);
+ Assert.Equal(english.Select(c => c.Text), alignment.Texts);
+ var converted = SubtitleConversion.Convert(source, ConversionOptions.Default, english: alignment.Texts);
+ Assert.Equal("Como se eu fosse um palhaço?", converted[0].Text);
+ Assert.Equal("Como se eu te divertisse?", converted[1].Text);
+ }
+
+ [OptInFact("PTPT_TEST_IDIOM_MODEL", "real MiniLM revenge alignment")]
+ public async Task RealMiniLmRevengeAlignment()
+ {
+ var source = new[] { Cue(2186224, 2187985, "Então decidiu revidar.") };
+ var reference = new[] { Cue(2186452, 2187953, "DETECTIVE: So you decided\nto get back at him.") };
+ using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(3));
+ await using var runtime = await EmbeddingRuntime.StartAsync(RealSettings(false),
+ new Progress(output.WriteLine), timeout.Token);
+ var pt = await runtime.EmbedAsync(source[0].Text, timeout.Token);
+ var en = await runtime.EmbedAsync(reference[0].Text, timeout.Token);
+ output.WriteLine($"Revenge pair similarity: {ReferenceAligner.Dot(pt, en):0.000} (floor {ReferenceAligner.PairSimilarityFloor:0.000}).");
+ var alignment = ReferenceAligner.AlignWithEmbeddings(source, reference, new[] { pt }, new[] { en });
+ Assert.NotNull(alignment.Texts[0]);
+ Assert.Equal("Então decidiste vingar-te.", Assert.Single(SubtitleConversion.Convert(source,
+ ConversionOptions.Default, english: alignment.Texts)).Text);
+ }
+
private static float[] Basis(int index, int size)
{
var vector = new float[size];
@@ -1235,20 +1371,22 @@ void LayoutScreenshot()
styleWindow.FindControl("ReviewTools")!.IsExpanded = true;
styleWindow.FindControl("LayoutTools")!.IsExpanded = true;
styleWindow.FindControl("ReferencePanel")!.IsExpanded = true;
+ styleWindow.Width = 2200;
styleWindow.Show();
Avalonia.Threading.Dispatcher.UIThread.RunJobs();
- foreach (var width in new[] { styleWindow.MinWidth, 1120d, 1600d })
+ foreach (var width in new[] { 2200d, styleWindow.MinWidth, 1120d, 1600d })
{
styleWindow.Width = width;
var content = Assert.IsAssignableFrom(styleWindow.Content);
+ var columns = styleWindow.FindControl("SettingsColumns")!;
content.Measure(new Avalonia.Size(width - 40, 720));
content.Arrange(new Avalonia.Rect(0, 0, width - 40, 720));
Avalonia.Threading.Dispatcher.UIThread.RunJobs();
+ styleWindow.UpdateLayout();
var reviewTools = styleWindow.FindControl("ReviewTools")!;
var layoutTools = styleWindow.FindControl("LayoutTools")!;
Assert.Same(reviewTools.Parent, layoutTools.Parent);
Assert.True(layoutTools.Bounds.Left >= reviewTools.Bounds.Right);
- var columns = styleWindow.FindControl("SettingsColumns")!;
var dictionary = styleWindow.FindControl