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