diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/ConverterTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/ConverterTests.cs index 6d88912a..3eabc1c4 100644 --- a/se5/BrazilianToEuropeanPortuguese.Tests/ConverterTests.cs +++ b/se5/BrazilianToEuropeanPortuguese.Tests/ConverterTests.cs @@ -515,7 +515,10 @@ public void ReferenceDoesNotTreatEmbeddedWhatAsYou(string source, string english Assert.Equal(expected, Converter.Convert(source, null, hint)); Assert.Equal(expected, Converter.Convert(expected, null, hint)); var noYou = new PortugueseConverter(new ConversionOptions(YouToTu: false)); - Assert.Equal(noYou.Convert(source), noYou.Convert(source, null, hint)); + if (source == "O que está acontecendo?") + Assert.Equal(expected, noYou.Convert(source, null, hint)); + else + Assert.Equal(noYou.Convert(source), noYou.Convert(source, null, hint)); } [Theory] @@ -1778,7 +1781,7 @@ public sealed class SlangTests [InlineData("Esqueci o nome dele.", "Esqueci o nome dele.")] [InlineData("Esquece isso.", "Esquece isso.")] [InlineData("Esquece!", "Esquece!")] - [InlineData("Tudo o que lembro.", "Tudo o que lembro.")] + [InlineData("Tudo o que lembro.", "Tudo o que me lembro.")] [InlineData("Botei o meu na reta para conseguir prender esse cara.", "Arrisquei o pescoço para conseguir prender esse tipo.")] [InlineData("Ele colocou o dele na reta por você.", "Ele arriscou o pescoço por ti.")] [InlineData("Não vou botar a minha na reta.", "Não vou arriscar o pescoço.")] diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/DialogueReplyTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/DialogueReplyTests.cs new file mode 100644 index 00000000..4a881563 --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/DialogueReplyTests.cs @@ -0,0 +1,166 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; +using SubtitleEdit.Plugins.Shared; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class DialogueReplyTests +{ + [Theory] + [InlineData("Não. Disse que\niria me deixar.", "No. You said you were gonna drop me off.", + "Não. Disseste que\nme ias deixar.")] + [InlineData("Não. Disse que iria me ajudar.", "No. You said you were going to help me.", + "Não. Disseste que me ias ajudar.")] + [InlineData("Sim. Disse que iria voltar.", "Yes. You said you were going to return.", + "Sim. Disseste que ias voltar.")] + [InlineData("Não. Disse que poderia ajudar.", "No. You said you could help.", + "Não. Disseste que poderias ajudar.")] + [InlineData("Não. Disse que iria me ajudar.", "No. You said you would help me.", + "Não. Disseste que me irias ajudar.")] + [InlineData("Disse que iria me proteger.", "You said you would protect me.", + "Disseste que me irias proteger.")] + [InlineData("Não. Disse que ela iria voltar.", "No. You said she would return.", + "Não. Disseste que ela iria voltar.")] + public void ReplyAndReportedClauseUseConfirmedSubjects(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("Ligue para João?", "Call John?", "Ligo para o João?")] + [InlineData("Ligue para Jamie?", "Call Jamie?", "Ligo para o Jamie?")] + [InlineData("Telefone para Maria?", "Phone Maria?", "Telefono para a Maria?")] + [InlineData("Pergunte ao João?", "Ask John?", "Pergunto ao João?")] + [InlineData("Espere aqui?", "Wait here?", "Espero aqui?")] + [InlineData("Faça isso?", "Do that?", "Faço isso?")] + [InlineData("Vá para casa?", "Go home?", "Vou para casa?")] + [InlineData("Ligue para o João?", "Call John?", "Ligo para o João?")] + [InlineData("Liga para João?", "Call John?", "Ligo para o João?")] + [InlineData("Ligue para Alex?", "Call Alex?", "Ligo para Alex?")] + public void ConditionalReplyQuestionAsksAboutOwnAction(string source, string english, string expected) + { + var blocks = SubRipParser.Parse("1\n00:00:01,000 --> 00:00:03,000\nE se precisar de ajuda...\n\n" + + "2\n00:00:03,010 --> 00:00:05,000\n" + source + "\n\n"); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(), + english: new string?[] { "And if you need help...", english }); + Assert.Equal(expected, result[1].Text); + } + + [Theory] + [InlineData("Ligue para João?", "Should I call John?", "Ligo para o João?")] + [InlineData("Telefone para Maria?", "Shall I phone Maria?", "Telefono para a Maria?")] + public void ExplicitSelfQuestionDoesNotNeedPreviousCue(string source, string english, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText(english))); + + [Theory] + [InlineData("É uma pequena parada.", "It's one little stop.", "É uma pequena paragem.")] + [InlineData("Faremos uma breve parada.", "We'll make a brief stop.", "Faremos uma breve paragem.")] + [InlineData("É a próxima parada.", "It's the next stop.", "É a próxima paragem.")] + [InlineData("São umas pequenas paradas.", "They're small stops.", "São umas pequenas paragens.")] + [InlineData("Uma parada militar.", "A military parade.", "Uma parada militar.")] + [InlineData("A mulher está parada.", "The woman is standing still.", "A mulher está parada.")] + [InlineData("É uma pequena parada.", null, "É uma pequena parada.")] + [InlineData("Vou contar uma parada.", "I'll tell you something.", "Vou contar uma coisa.")] + [InlineData("Ela contou uma parada.", "She counted one stop.", "Ela contou uma paragem.")] + public void StopSenseIsNotABlanketWordSwap(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)); + } + + [Fact] + public void FormalAndUncertainSubjectsStayUnchanged() + { + var converter = new PortugueseConverter(); + Assert.Equal("Não. Disse que iria voltar.", converter.Convert("Não. Disse que iria voltar.", + reference: ReferenceHint.FromText("No. She said she would return."))); + Assert.Equal("Ligue para João?", converter.Convert("Ligue para João?", + reference: ReferenceHint.FromText("Call John?"))); + Assert.Equal("Não. Disse que iria voltar.", converter.Convert("Não. Disse que iria voltar.", + reference: ReferenceHint.FromText("No. You said you were going to return.")! with { FormalAddress = true })); + } + + [Fact] + public void CliticMovementPreservesConditionalAndOtherPersons() + { + Assert.Equal("Ele disse que me iria ajudar.", new PortugueseConverter().Convert("Ele disse que iria me ajudar.")); + Assert.Equal("Ela disse que nos iria proteger.", new PortugueseConverter().Convert("Ela disse que iria nos proteger.")); + Assert.Equal("Disseste que iria me ajudar.", new PortugueseConverter(new ConversionOptions(Pronouns: false)) + .Convert("Disse que iria me ajudar.", reference: ReferenceHint.FromText("You said she would help me."))); + } + + [Fact] + public void OptionsAndIgnoredPhrasesProtectExistingBehaviour() + { + var reference = ReferenceHint.FromText("Should I call John."); + Assert.Equal("Ligue para João?", new PortugueseConverter().Convert("Ligue para João?", reference: reference)); + reference = ReferenceHint.FromText("Should I call John?"); + Assert.Equal("Ligue para João?", new PortugueseConverter(new ConversionOptions(YouToTu: false)) + .Convert("Ligue para João?", reference: reference)); + Assert.Equal("Ligue para João?", new PortugueseConverter(new ConversionOptions(DisabledRules: "action-questions")) + .Convert("Ligue para João?", reference: reference)); + Assert.Equal("Ligo para João?", new PortugueseConverter(new ConversionOptions(Pronouns: false)) + .Convert("Ligue para João?", reference: reference)); + Assert.Equal("É uma pequena parada.", new PortugueseConverter(new ConversionOptions(Vocabulary: false)) + .Convert("É uma pequena parada.", reference: ReferenceHint.FromText("It's one little stop."))); + Assert.Equal("É uma pequena parada.", new PortugueseConverter(new ConversionOptions(DisabledRules: "travel-stops")) + .Convert("É uma pequena parada.", reference: ReferenceHint.FromText("It's one little stop."))); + var list = new PortugueseLocalWordList(); + list.Ignored.Add("Ligue para João"); + list.Ignored.Add("pequena parada"); + Assert.Equal("Ligue para João?", new PortugueseConverter(localWordList: list) + .Convert("Ligue para João?", reference: reference)); + Assert.Equal("É uma pequena parada.", new PortugueseConverter(localWordList: list) + .Convert("É uma pequena parada.", reference: ReferenceHint.FromText("It's one little stop."))); + } + + [Theory] + [InlineData("E se precisar de ajuda.", 3010)] + [InlineData("E se precisar de ajuda...", 8000)] + [InlineData("Se ele precisar de ajuda...", 3010)] + public void BareQuestionNeedsABoundedSecondPersonConditional(string previous, long start) + { + var blocks = SubRipParser.Parse("1\n00:00:01,000 --> 00:00:03,000\n" + previous + + "\n\n2\n00:00:08,000 --> 00:00:10,000\nLigue para João?\n\n"); + blocks[1].StartMs = start; + var english = previous.StartsWith("Se ele", StringComparison.Ordinal) + ? "If he needs help..." : "And if you need help..."; + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(), + english: new string?[] { english, "Call John?" }); + Assert.Equal("Ligue para João?", result[1].Text); + } + + [Theory] + [InlineData("Vamos. É amiga do Paulo, Sra. Silva, certo?", + "Come on. It's Paul's friend, Mrs. Silva, all right?")] + [InlineData("É a minha vizinha, Sra. Silva.", "She's my neighbour, Mrs. Silva.")] + [InlineData("É o meu colega, Sr. Silva.", "He's my colleague, Mr. Silva.")] + public void NamedThirdPersonAppositionDoesNotImposeFormalRegister(string mention, string englishMention) + { + var blocks = SubRipParser.Parse("1\n00:00:01,000 --> 00:00:03,000\nVamos, João.\n\n" + + "2\n00:00:03,010 --> 00:00:06,000\nNão. Disse que\niria me deixar.\n\n" + + "3\n00:00:06,010 --> 00:00:09,000\n" + mention + "\n\n"); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(), + english: new string?[] { "Come on, John.", "No. You said you were gonna drop me off.", englishMention }); + Assert.False(result[1].Hint!.FormalAddress); + Assert.Equal("Não. Disseste que\nme ias deixar.", result[1].Text); + } + + [Theory] + [InlineData("Sra. Silva, pode ajudar?", "Mrs. Silva, can you help?")] + [InlineData("É a minha amiga, senhora.", "She's my friend, ma'am.")] + [InlineData("É a minha amiga, Sra. Silva, pode ajudar?", "She's my friend, Mrs. Silva, can you help?")] + public void ActualFormalVocativesKeepRegister(string address, string englishAddress) + { + var blocks = SubRipParser.Parse("1\n00:00:01,000 --> 00:00:03,000\nNão. Disse que iria voltar.\n\n" + + "2\n00:00:03,010 --> 00:00:06,000\n" + address + "\n\n"); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(), + english: new string?[] { "No. You said you were going to return.", englishAddress }); + Assert.True(result[0].Hint!.FormalAddress); + Assert.Equal("Não. Disse que iria voltar.", result[0].Text); + } +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/DroppedSubjunctiveSubjectTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/DroppedSubjunctiveSubjectTests.cs new file mode 100644 index 00000000..52c9c7eb --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/DroppedSubjunctiveSubjectTests.cs @@ -0,0 +1,120 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; +using SubtitleEdit.Plugins.Shared; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class DroppedSubjunctiveSubjectTests +{ + [Theory] + [InlineData("Bem, talvez precise\ndo trabalho.", "Well, maybe you need the job.", + "Bem, talvez precises\ndo trabalho.")] + [InlineData("Talvez queira sair.", "Maybe you want to leave.", "Talvez queiras sair.")] + [InlineData("Talvez possa ajudar.", "Perhaps you can help.", "Talvez possas ajudar.")] + [InlineData("Bem, talvez tenha razão.", "Well, maybe you are right.", "Bem, talvez tenhas razão.")] + [InlineData("Talvez saiba.", "Maybe you know.", "Talvez saibas.")] + [InlineData("Talvez venha amanhã.", "Maybe you'll come tomorrow.", "Talvez venhas amanhã.")] + [InlineData("Talvez consiga resolver.", "Maybe you can solve it.", "Talvez consigas resolver.")] + [InlineData("Talvez encontre trabalho.", "Maybe you will find work.", "Talvez encontres trabalho.")] + [InlineData("Talvez não queira sair.", "Maybe you don't want to leave.", "Talvez não queiras sair.")] + [InlineData("E se precisar de um ombro\npara chorar...", "And if you ever need a shoulder to cry on...", + "E se precisares de um ombro\npara chorar...")] + [InlineData("Se precisar de ajuda.", "If you need help.", "Se precisares de ajuda.")] + [InlineData("E se precisar de ajuda?", "What if you need help?", "E se precisares de ajuda?")] + [InlineData("Mas se comprar uma casa.", "But if you buy a house.", "Mas se comprares uma casa.")] + [InlineData("Ou se encontrar trabalho.", "Or if you find work.", "Ou se encontrares trabalho.")] + [InlineData("E se conseguir entrar.", "And if you manage to enter.", "E se conseguires entrar.")] + [InlineData("Quando precisar de ajuda.", "When you need help.", "Quando precisares de ajuda.")] + [InlineData("Quando tiver tempo.", "When you have time.", "Quando tiveres tempo.")] + [InlineData("E se puder ajudar.", "And if you can help.", "E se puderes ajudar.")] + [InlineData("Se fizer isso.", "If you do that.", "Se fizeres isso.")] + [InlineData("Se disser isso.", "If you say that.", "Se disseres isso.")] + [InlineData("Quando vier.", "When you come.", "Quando vieres.")] + [InlineData("Quando atinge certa idade,\nfaz esse tipo de coisa.", + "When you reach a certain age, you do that sort of thing.", + "Quando atinges certa idade,\nfazes esse tipo de coisa.")] + [InlineData("Se precisar de ajuda, sabe onde estou.", + "If you need help, you know where I am.", + "Se precisares de ajuda, sabes onde estou.")] + [InlineData("Se souber.", "If you know.", "Se souberes.")] + [InlineData("A menos que precise de ajuda.", "Unless you need help.", "A menos que precises de ajuda.")] + [InlineData("Assim que terminar.", "As soon as you finish.", "Assim que terminares.")] + [InlineData("E se não precisar de ajuda.", "And if you don't need help.", "E se não precisares de ajuda.")] + [InlineData("E se precisar de ajuda...", "And if you need help...", "E se precisares de ajuda...")] + [InlineData("E SE PRECISAR DE AJUDA.", "And if you need help.", "E SE PRECISARES DE AJUDA.")] + public void ReferenceConfirmedDroppedSubjectsAgree(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 trabalho não precisa\nmais de mim.", "The job doesn't need me anymore.", + "O trabalho não precisa\nmais de mim.")] + [InlineData("Bem, talvez precise\ndo trabalho.", "Well, maybe she needs the job.", + "Bem, talvez precise\ndo trabalho.")] + [InlineData("Talvez ele precise de ajuda.", "Maybe you should help him.", "Talvez ele precise de ajuda.")] + [InlineData("E se precisar de ajuda.", "And if she needs help.", "E se precisar de ajuda.")] + [InlineData("Se ele precisar de ajuda.", "If you help him.", "Se ele precisar de ajuda.")] + [InlineData("Se Maria comprar uma casa.", "If you help Maria buy a house.", "Se Maria comprar uma casa.")] + [InlineData("Quando o trabalho terminar.", "When you finish the job.", "Quando o trabalho terminar.")] + [InlineData("E se precisar de ajuda.", null, "E se precisar de ajuda.")] + [InlineData("Talvez precise de ajuda.", null, "Talvez precise de ajuda.")] + [InlineData("E se precisares de ajuda.", null, "E se precisares de ajuda.")] + [InlineData("E se precisarem de ajuda.", null, "E se precisarem de ajuda.")] + [InlineData("Se precisar de ajuda.", "If you and Maria need help.", "Se precisar de ajuda.")] + [InlineData("Ouça, não vai contar à minha mãe, certo?", + "You're not going to tell my mom, right?", "Ouça, não vai contar à minha mãe, certo?")] + public void DifferentAndUncertainSubjectsAreNotGuessed(string source, string? english, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText(english))); + + [Theory] + [InlineData("E se precisar de ajuda.", "And if you need help.")] + [InlineData("Bem, talvez precise do trabalho.", "Well, maybe you need the job.")] + public void FormalAddressAndDisabledRulesStayProtected(string source, string english) + { + var reference = ReferenceHint.FromText(english)!; + Assert.Equal(source, new PortugueseConverter().Convert(source, + reference: reference with { FormalAddress = true })); + Assert.Equal(source, new PortugueseConverter(new ConversionOptions(YouToTu: false)) + .Convert(source, reference: reference)); + Assert.Equal(source, new PortugueseConverter(new ConversionOptions(DisabledRules: "address")) + .Convert(source, reference: reference)); + } + + [Fact] + public void ConditionalConjunctionIsPreservedWithPronounsDisabled() => + Assert.Equal("E se precisares de ajuda.", + new PortugueseConverter(new ConversionOptions(Pronouns: false)).Convert("E se precisar de ajuda.", + reference: ReferenceHint.FromText("And if you need help."))); + + [Fact] + public void IgnoredComplementsStayUntouched() + { + var words = new PortugueseLocalWordList(); + words.Ignored.Add("se precisar de ajuda"); + Assert.Equal("E se precisar de ajuda.", new PortugueseConverter(localWordList: words) + .Convert("E se precisar de ajuda.", reference: ReferenceHint.FromText("And if you need help."))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SharedCuePipelinePreservesSubjectsAndTimes(bool sentenceContext) + { + var source = "689\n00:35:37,468 --> 00:35:40,486\nO trabalho não precisa\nmais de mim.\n\n" + + "690\n00:35:40,490 --> 00:35:43,642\nBem, talvez precise\ndo trabalho.\n\n" + + "693\n00:35:53,818 --> 00:35:58,204\nE se precisar de um ombro\npara chorar...\n\n"; + var blocks = SubRipParser.Parse(source); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(SentenceContext: sentenceContext), + english: new string?[] { "The job doesn't need me anymore.", "Well, maybe you need the job.", + "And if you ever need a shoulder to cry on..." }); + Assert.Equal("O trabalho não precisa\nmais de mim.", result[0].Text); + Assert.Equal("Bem, talvez precises\ndo trabalho.", result[1].Text); + Assert.Equal("E se precisares de um ombro\npara chorar...", result[2].Text); + Assert.Equal(3, result.Count); + Assert.Equal(2153818L, blocks[2].StartMs); + Assert.Equal(2158204L, blocks[2].EndMs); + } +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/EmergencyReferenceTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/EmergencyReferenceTests.cs new file mode 100644 index 00000000..9425a961 --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/EmergencyReferenceTests.cs @@ -0,0 +1,92 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; +using SubtitleEdit.Plugins.Shared; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class EmergencyReferenceTests +{ + [Theory] + [InlineData("Bem, quando atenderam\nno clube, tiveram informação", + "Well, when you responded to the club, you got some information", + "Bem, quando responderam\nno clube, tiveram informação")] + [InlineData("Nós atendemos na escola.", "We responded to the school.", "Nós respondemos na escola.")] + [InlineData("Ele atendeu no acidente.", "He responded to the accident.", "Ele respondeu no acidente.")] + [InlineData("Estavam a atender no local.", "They were responding to the scene.", + "Estavam a responder no local.")] + [InlineData("de um rapaz cuja namorada\nteve uma parada.", + "from a kid whose girlfriend went into arrest.", + "de um rapaz cuja namorada\nteve uma paragem cardíaca.")] + [InlineData("Ela sofreu uma parada.", "She suffered a cardiac arrest.", + "Ela sofreu uma paragem cardíaca.")] + [InlineData("Ele teve uma paragem.", "He had a cardiorespiratory arrest.", + "Ele teve uma paragem cardiorrespiratória.")] + [InlineData("Ela teve uma parada.", "She went into cardiac arrest.", + "Ela teve uma paragem cardíaca.")] + [InlineData("ELA TEVE UMA PARADA.", "She went into arrest.", "ELA TEVE UMA PARAGEM CARDÍACA.")] + [InlineData("Ele tinha uma parada.", "He was going into arrest.", "Ele tinha uma paragem cardíaca.")] + [InlineData("Sim, isto funcionaria?", "Yeah, would that work?", "Sim, isso funcionaria?")] + [InlineData("Isto é ótimo.", "That's great.", "Isso é ótimo.")] + [InlineData("O que isto tem a ver connosco?", "What's that got to do with us?", + "O que isso tem a ver connosco?")] + [InlineData("Não sabia disto.", "I didn't know about that.", "Não sabia disso.")] + [InlineData("Pensei nisto.", "I thought of that.", "Pensei nisso.")] + public void ConfirmedMeaningsAreGeneralizedAndStable(string source, string english, string expected) + { + var hint = ReferenceHint.FromText(english); + var converter = new PortugueseConverter(); + Assert.Equal(expected, converter.Convert(source, reference: hint)); + Assert.Equal(expected, converter.Convert(expected, reference: hint)); + } + + [Theory] + [InlineData("Ela atendeu no clube.", "She answered the phone at the club.", "Ela atendeu no clube.")] + [InlineData("Ele atendeu no hotel.", "He responded to the letter at the hotel.", "Ele atendeu no hotel.")] + [InlineData("Ela atendeu no hospital.", null, "Ela atendeu no hospital.")] + [InlineData("Ela teve uma parada.", null, "Ela teve uma parada.")] + [InlineData("Ela teve uma parada.", "She had a stop.", "Ela teve uma paragem.")] + [InlineData("Ela teve uma parada.", "She was under arrest.", "Ela teve uma parada.")] + [InlineData("Isto funcionaria?", null, "Isto funcionaria?")] + [InlineData("Isto funcionaria?", "Would this work?", "Isto funcionaria?")] + [InlineData("Isto é o que ele disse.", "This is what he said that day.", "Isto é o que ele disse.")] + [InlineData("Ele disse isto.", "He said that he was tired.", "Ele disse isto.")] + [InlineData("Isto é bom.", "This is good, that is bad.", "Isto é bom.")] + [InlineData("Este plano funcionaria?", "Would that plan work?", "Este plano funcionaria?")] + [InlineData("Ele compara isto ao plano.", "He compares it to that plan.", "Ele compara isto ao plano.")] + [InlineData("Isto faz aquele plano funcionar.", "It makes that plan work.", "Isto faz aquele plano funcionar.")] + public void OtherReadingsStayProtected(string source, string? english, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText(english))); + + [Fact] + public void OptionsAndIgnoredPhrasesAreRespected() + { + var arrest = ReferenceHint.FromText("She went into arrest."); + Assert.Equal("Ela teve uma parada.", new PortugueseConverter(new ConversionOptions(Vocabulary: false)) + .Convert("Ela teve uma parada.", reference: arrest)); + Assert.Equal("Ela teve uma parada.", new PortugueseConverter(new ConversionOptions(DisabledRules: "emergency-constructions")) + .Convert("Ela teve uma parada.", reference: arrest)); + Assert.Equal("Isto funcionaria?", new PortugueseConverter(new ConversionOptions(DisabledRules: "reference-clitics")) + .Convert("Isto funcionaria?", reference: ReferenceHint.FromText("Would that work?"))); + var list = new PortugueseLocalWordList(); + list.Ignored.Add("teve uma parada"); + list.Ignored.Add("isto"); + var converter = new PortugueseConverter(localWordList: list); + Assert.Equal("Ela teve uma parada.", converter.Convert("Ela teve uma parada.", reference: arrest)); + Assert.Equal("Isto funcionaria?", converter.Convert("Isto funcionaria?", + reference: ReferenceHint.FromText("Would that work?"))); + } + + [Fact] + public void SubtitlePipelineUsesMedicalSenseWithoutCrossCueGuessing() + { + var blocks = SubRipParser.Parse( + "1\n00:00:01,000 --> 00:00:03,000\nBem, quando atenderam\nno clube, tiveram informação\n\n" + + "2\n00:00:03,010 --> 00:00:05,000\nde um rapaz cuja namorada\nteve uma parada.\n\n" + + "3\n00:01:01,000 --> 00:01:03,000\nSim, isto funcionaria?\n\n"); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(), english: new string?[] { + "Well, when you responded to the club, you got some information", + "from a kid whose girlfriend went into arrest.", "Yeah, would that work?" }); + Assert.Equal("Bem, quando responderam\nno clube, tiveram informação", result[0].Text); + Assert.Equal("de um rapaz cuja namorada\nteve uma paragem cardíaca.", result[1].Text); + Assert.Equal("Sim, isso funcionaria?", result[2].Text); + } +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/FamilyObjectTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/FamilyObjectTests.cs new file mode 100644 index 00000000..5046119b --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/FamilyObjectTests.cs @@ -0,0 +1,88 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; +using SubtitleEdit.Plugins.Shared; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class FamilyObjectTests +{ + [Theory] + [InlineData("Mãe e tio Pedro", "Mom and Uncle Peter", "A Mãe e o tio Pedro")] + [InlineData("Pai e tia Ana", "Dad and Aunt Anne", "O Pai e a tia Ana")] + [InlineData("Mãe chegou.", "Mom arrived.", "A Mãe chegou.")] + [InlineData("Pai está acordado.", "Dad's awake.", "O Pai está acordado.")] + [InlineData("MÃE E TIO PEDRO", "Mom and Uncle Peter", "A MÃE E O TIO PEDRO")] + [InlineData("Mãe e tio Pedro\no encontraram e\no levaram ao hospital.", + "Mom and Uncle Peter found you and got you to the hospital.", + "A Mãe e o tio Pedro\nencontraram-te e\nlevaram-te ao hospital.")] + [InlineData("o encontraram e\no levaram ao hospital.", + "are the ones that found you and got you to the hospital.", + "encontraram-te e\nlevaram-te ao hospital.")] + [InlineData("Eles o ajudaram.", "They helped you.", "Eles ajudaram-te.")] + [InlineData("Ela a viu.", "She saw you.", "Ela viu-te.")] + [InlineData("Não o encontraram.", "They didn't find you.", "Não te encontraram.")] + [InlineData("Ela disse que o encontraram.", "She said they found you.", "Ela disse que te encontraram.")] + [InlineData("Quando o encontraram, chamaram ajuda.", "When they found you, they called for help.", + "Quando te encontraram, chamaram ajuda.")] + [InlineData("Ela o levou ao hospital.", "She brought you to the hospital.", + "Ela trouxe-te ao hospital.")] + public void ConfirmedSubjectsAndObjectsAreStable(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("Mãe!", "Mom!", "Mãe!")] + [InlineData("Mãe, ajuda!", "Mom, help!", "Mãe, ajuda!")] + [InlineData("Mãe e filho", "Mother and child", "Mãe e filho")] + [InlineData("Mãe e tio Pedro", null, "Mãe e tio Pedro")] + [InlineData("A Mãe e o tio Pedro", "Mom and Uncle Peter", "A Mãe e o tio Pedro")] + [InlineData("o encontraram.", "They found him.", "o encontraram.")] + [InlineData("o encontraram.", null, "o encontraram.")] + [InlineData("o encontraram.", "They found you and him.", "o encontraram.")] + [InlineData("o encontraram e a ajudaram.", "They found you and helped her.", + "o encontraram e a ajudaram.")] + [InlineData("o levaram ao hospital.", "They took him to the hospital.", "o levaram ao hospital.")] + [InlineData("o levaram ao hospital.", "They got you a room.", "o levaram ao hospital.")] + [InlineData("o encontraram.", "They found you a room.", "o encontraram.")] + public void OtherSubjectsAndVocativesArePreserved(string source, string? english, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText(english))); + + [Fact] + public void OptionsAndIgnoredPhrasesAreRespected() + { + const string source = "o encontraram."; + var hint = ReferenceHint.FromText("They found you.")!; + foreach (var options in new[] { new ConversionOptions(YouToTu: false), + new ConversionOptions(Pronouns: false), new ConversionOptions(DisabledRules: "reference-object-clitics") }) + { + var result = new PortugueseConverter(options).Convert(source, reference: hint); + Assert.DoesNotContain("te", result); + } + Assert.Equal("o encontraram.", new PortugueseConverter().Convert(source, + reference: hint with { FormalAddress = true })); + Assert.Equal("Mãe e tio Pedro", new PortugueseConverter(new ConversionOptions(Pronouns: false)) + .Convert("Mãe e tio Pedro", reference: ReferenceHint.FromText("Mom and Uncle Peter"))); + var words = new PortugueseLocalWordList(); + words.Ignored.Add("Mãe e tio Pedro"); + words.Ignored.Add("o encontraram"); + var converter = new PortugueseConverter(localWordList: words); + Assert.Equal("Mãe e tio Pedro", converter.Convert("Mãe e tio Pedro", + reference: ReferenceHint.FromText("Mom and Uncle Peter"))); + Assert.Equal(source, converter.Convert(source, reference: hint)); + } + + [Fact] + public void SplitFamilySubjectAndPastObjectsWorkWithoutSentenceJoining() + { + var blocks = SubRipParser.Parse( + "1\n00:00:01,000 --> 00:00:03,000\nMãe e tio Pedro\n\n" + + "2\n00:00:03,010 --> 00:00:05,000\no encontraram e\no levaram ao hospital.\n\n"); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(), english: new string?[] { + "Mom and Uncle Peter", "are the ones that found you and got you to the hospital." }); + Assert.Equal("A Mãe e o tio Pedro", result[0].Text); + Assert.Equal("encontraram-te e\nlevaram-te ao hospital.", result[1].Text); + } +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/InformalPredicateTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/InformalPredicateTests.cs new file mode 100644 index 00000000..13aa65ee --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/InformalPredicateTests.cs @@ -0,0 +1,143 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; +using SubtitleEdit.Plugins.Shared; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class InformalPredicateTests +{ + [Theory] + [InlineData("no que Maria diria se\ncontasse que iria parar.", + "what Maria would say if you told her you were gonna quit.", + "no que Maria diria se\ncontasses que irias parar.")] + [InlineData("se dissesse que iria sair.", "if you said you were going to leave.", + "se dissesses que irias sair.")] + [InlineData("se soubesse que poderia ajudar.", "if you knew you could help.", + "se soubesses que poderias ajudar.")] + [InlineData("se explicasse que teria tempo.", "if you explained you would have time.", + "se explicasses que terias tempo.")] + [InlineData("se contasse que ia voltar.", "if you said you were going to return.", + "se contasses que ias voltar.")] + [InlineData("se pensasse que conseguiria resolver.", "if you thought you could solve it.", + "se pensasses que conseguirias resolver.")] + [InlineData("se dissesse que odiava golfe.", "if you said you hated golf.", + "se dissesses que odiavas golfe.")] + [InlineData("se dissesse que sabia.", "if you said you knew.", + "se dissesses que sabias.")] + [InlineData("se explicasse que precisava sair.", "if you explained you needed to leave.", + "se explicasses que precisavas de sair.")] + [InlineData("Você é péssimo pescando,\nodeia golfe.", "You suck at fishing, you hate golf.", + "És péssimo a pescar,\nodeias golfe.")] + [InlineData("Você é calmo, trabalha bem, ajuda todos.", "You are calm, you work well, you help everyone.", + "És calmo, trabalhas bem, ajudas todos.")] + [InlineData("Você gosta de nadar, prefere correr.", "You like swimming, you prefer running.", + "Gostas de nadar, preferes correr.")] + [InlineData("Você trabalha aqui e mora perto.", "You work here and you live nearby.", + "Trabalhas aqui e vives perto.")] + [InlineData("Você sabe nadar, mas tem medo.", "You can swim, but you are afraid.", + "Sabes nadar, mas tens medo.")] + public void ReferenceConfirmedPredicatesAgree(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("Você é péssimo pescando,\nodeia golfe.", "És péssimo a pescar,\nodeias golfe.")] + [InlineData("Você é calmo e trabalha bem.", "És calmo e trabalhas bem.")] + [InlineData("Você é calmo e trabalha bem, ajuda todos.", "És calmo e trabalhas bem, ajuda todos.")] + public void UnambiguousSharedSubjectsDoNotRequireAReference(string source, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source)); + + [Theory] + [InlineData("Você a deixaria louca\nem casa.", "Irias deixá-la louca\nem casa.")] + [InlineData("Você o ajudaria.", "Irias ajudá-lo.")] + [InlineData("Você os protegeria.", "Irias protegê-los.")] + [InlineData("Você as veria.", "Irias vê-las.")] + [InlineData("Você o faria.", "Irias fazê-lo.")] + [InlineData("Você o diria.", "Irias dizê-lo.")] + [InlineData("Você a traria.", "Irias trazê-la.")] + [InlineData("Você o manteria.", "Irias mantê-lo.")] + [InlineData("Você a ajudaria.", "Irias ajudá-la.")] + [InlineData("VOCÊ A AJUDARIA.", "IRIAS AJUDÁ-LA.")] + public void InformalObjectConditionalsUseEncliticInfinitives(string source, string expected) + { + var converter = new PortugueseConverter(); + Assert.Equal(expected, converter.Convert(source)); + Assert.Equal(expected, converter.Convert(expected)); + } + + [Theory] + [InlineData("Você não a deixaria.", "Não a deixarias.")] + [InlineData("Se você a ajudaria.", "Se a ajudarias.")] + [InlineData("Você te ajudaria.", "Ajudar-te-ias.")] + public void ProclisisTriggersAndOtherCliticsKeepExistingBehaviour(string source, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source)); + + [Theory] + [InlineData("se contasse que iria parar.", null, "se contasse que iria parar.")] + [InlineData("se contasse que iria parar.", "if she told you she would quit.", "se contasse que iria parar.")] + [InlineData("se contasse que ela iria parar.", "if you told her she would quit.", "se contasses que ela iria parar.")] + [InlineData("Você gosta dela, odeia o irmão.", "You like her, she hates her brother.", + "Gostas dela, odeia o irmão.")] + [InlineData("Você gosta dela, Maria odeia o irmão.", null, + "Gostas dela, Maria odeia o irmão.")] + [InlineData("Você sabe que ela trabalha, mora perto.", null, + "Sabes que ela trabalha, vive perto.")] + [InlineData("Você procura emprego, ajuda financeira.", null, + "Procuras emprego, ajuda financeira.")] + [InlineData("Você tem fruta, banana madura.", null, "Tens fruta, banana madura.")] + [InlineData("se dissesse que saberia.", "if you said she would know.", "se dissesses que saberia.")] + public void ChangedOrUncertainSubjectsStayThirdPerson(string source, string? english, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText(english))); + + [Fact] + public void AddressAndPronounOptionsAreRespected() + { + const string source = "Você é péssimo pescando,\nodeia golfe."; + Assert.Equal("É péssimo a pescar,\nodeia golfe.", + new PortugueseConverter().Convert(source, reference: + ReferenceHint.FromText("You suck at fishing, you hate golf.")! with { FormalAddress = true })); + Assert.Equal("Você é péssimo a pescar,\nodeia golfe.", + new PortugueseConverter(new ConversionOptions(YouToTu: false)).Convert(source)); + Assert.Equal("A deixarias louca.", + new PortugueseConverter(new ConversionOptions(Pronouns: false)).Convert("Você a deixaria louca.")); + Assert.Equal("A deixarias louca.", + new PortugueseConverter(new ConversionOptions(Colloquial: false)).Convert("Você a deixaria louca.")); + Assert.Equal("Você deixá-la-ia louca.", + new PortugueseConverter(new ConversionOptions(DisabledRules: "address")).Convert("Você a deixaria louca.")); + } + + [Fact] + public void LocalListsKeepPriority() + { + var list = new PortugueseLocalWordList(); + list.Ignored.Add("odeia golfe"); + Assert.Equal("És péssimo a pescar, odeia golfe.", + new PortugueseConverter(localWordList: list).Convert("Você é péssimo pescando, odeia golfe.")); + list.Words.Add(("louca", "furiosa")); + Assert.Equal("Irias deixá-la furiosa.", new PortugueseConverter(localWordList: list) + .Convert("Você a deixaria louca.")); + list.Ignored.Add("Você a deixaria"); + Assert.Equal("Você a deixaria furiosa.", new PortugueseConverter(localWordList: list) + .Convert("Você a deixaria louca.")); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SplitConditionalSentenceKeepsCueBoundaries(bool sentenceContext) + { + var blocks = SubRipParser.Parse("680\n00:35:16,053 --> 00:35:17,613\nTalvez você deva pensar\n\n" + + "681\n00:35:17,627 --> 00:35:20,616\nno que Maria diria se\ncontasse que iria parar.\n\n"); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(SentenceContext: sentenceContext), + english: new string?[] { "Maybe you should think about", + "what Maria would say if you told her you were gonna quit." }); + Assert.Equal("Talvez devas pensar", result[0].Text); + Assert.Equal("no que Maria diria se\ncontasses que irias parar.", result[1].Text); + Assert.Equal(2, blocks.Count); + Assert.Equal(2117627L, blocks[1].StartMs); + Assert.Equal(2120616L, blocks[1].EndMs); + } +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/LinkedInfinitiveTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/LinkedInfinitiveTests.cs new file mode 100644 index 00000000..befc9ff0 --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/LinkedInfinitiveTests.cs @@ -0,0 +1,58 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class LinkedInfinitiveTests +{ + [Theory] + [InlineData("mas me derrubar", "mas derrubar-me")] + [InlineData("mas me ajudar", "mas ajudar-me")] + [InlineData("mas te proteger", "mas proteger-te")] + [InlineData("a se vestir", "a vestir-se")] + [InlineData("mas nos ouvir", "mas ouvir-nos")] + [InlineData("mas vos dizer", "mas dizer-vos")] + [InlineData("mas lhe oferecer", "mas oferecer-lhe")] + [InlineData("mas lhes permitir", "mas permitir-lhes")] + [InlineData("mas me pôr", "mas pôr-me")] + [InlineData("depois se compor", "depois compor-se")] + [InlineData("mas nos vermos", "mas vermo-nos")] + [InlineData("mas me verem", "mas verem-me")] + [InlineData("mas te protegeres", "mas protegeres-te")] + [InlineData("mas me fotografar", "mas fotografar-me")] + [InlineData("MAS ME DERRUBAR", "MAS DERRUBAR-ME")] + [InlineData("mas\nme derrubar", "mas\nderrubar-me")] + [InlineData("mas me derrubar", "mas derrubar-me")] + public void LinkedInfinitivesUseEnclisis(string source, string expected) + { + var converter = new PortugueseConverter(); + Assert.Equal(expected, converter.Convert(source)); + Assert.Equal(expected, converter.Convert(expected)); + } + + [Theory] + [InlineData("mas não me derrubar")] + [InlineData("mas nunca me ajudar")] + [InlineData("mas que me ajudem")] + [InlineData("E se deixarmos isso só entre nós?")] + [InlineData("mas se protegerem a entrada")] + [InlineData("mas se vestir")] + [InlineData("mas se compor")] + [InlineData("E se precisar de um ombro para chorar...")] + [InlineData("e se comprar uma casa")] + [InlineData("mas se conseguir entrar")] + [InlineData("ou se encontrar trabalho")] + [InlineData("mas nos lugares")] + [InlineData("mas nos bares")] + [InlineData("mas me inventarverbo")] + public void NonInfinitivesAndAttractorsArePreserved(string source) => + Assert.Equal(source, new PortugueseConverter().Convert(source)); + + [Fact] + public void PronounOptionControlsTheCorrection() => + Assert.Equal("mas me derrubar", + new PortugueseConverter(new ConversionOptions(Pronouns: false)).Convert("mas me derrubar")); + + [Fact] + public void FiniteVerbKeepsExistingEnclisisBehaviour() => + Assert.Equal("mas parece-me", new PortugueseConverter().Convert("mas me parece")); +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/NaturalComplementTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/NaturalComplementTests.cs new file mode 100644 index 00000000..00f2103e --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/NaturalComplementTests.cs @@ -0,0 +1,180 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; +using SubtitleEdit.Plugins.Shared; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class NaturalComplementTests +{ + [Theory] + [InlineData("Parece um dia devagar\nno escritório.", "It looks like it's a slow day in the office.", + "Parece um dia lento\nno escritório.")] + [InlineData("Foi uma manhã devagar.", "It was a slow morning.", "Foi uma manhã lenta.")] + [InlineData("Foram dias devagar.", "They were slow days.", "Foram dias lentos.")] + [InlineData("Foram semanas devagar.", "They were slow weeks.", "Foram semanas lentas.")] + [InlineData("UM DIA DEVAGAR.", "A slow day.", "UM DIA LENTO.")] + [InlineData("O que está havendo?", null, "O que se está a passar?")] + [InlineData("O que estava havendo?", null, "O que se estava a passar?")] + [InlineData("O que está acontecendo?", "What's going on?", "O que se está a passar?")] + [InlineData("O que está a haver?", null, "O que se está a passar?")] + [InlineData("O que está havendo?", null, "O que se está a passar?")] + [InlineData("O QUE ESTÁ HAVENDO?", null, "O QUE SE ESTÁ A PASSAR?")] + [InlineData("-O que está havendo?", null, "-O que se está a passar?")] + [InlineData("Espere até ver a sala de jogos.", "Wait till you see the game room.", + "Espera até veres a sala de jogos.")] + [InlineData("-Espere até ver a sala de jogos.\n-O quê?", "Wait till you see the game room. The what?", + "-Espera até veres a sala de jogos.\n-O quê?")] + [InlineData("- Espere até ver a sala.", "Wait until you see the room.", "- Espera até veres a sala.")] + [InlineData("-Espere até ver a sala.", "Wait until you see the room.", + "-Espera até veres a sala.")] + [InlineData("-Sim.\n-Espere até ver a sala.", "Yes. Wait until you see the room.", + "-Sim.\n-Espera até veres a sala.")] + [InlineData("Espere até ouvir isso.", "Wait until you hear that.", "Espera até ouvires isso.")] + [InlineData("Espere até conseguir entrar.", "Wait until you manage to enter.", "Espera até conseguires entrar.")] + [InlineData("Espera até receber a carta.", "Wait till you receive the letter.", "Espera até receberes a carta.")] + [InlineData("Espera até pôr tudo no lugar.", "Wait until you put everything in place.", + "Espera até pores tudo no lugar.")] + [InlineData("Espera até\nver a sala.", "Wait until you see the room.", "Espera até\nveres a sala.")] + [InlineData("Alugamos por você.", "We rented it for you.", "Alugamos para ti.")] + [InlineData("Compramos uma casa por você.", "We bought a house for you.", "Compramos uma casa para ti.")] + [InlineData("Reservamos por você.", "We reserved it for you.", "Reservamos para ti.")] + [InlineData("Preparámos tudo por ti.", "We prepared everything for you.", "Preparámos tudo para ti.")] + [InlineData("Alugou por mim.", "He rented it for me.", "Alugou para mim.")] + [InlineData("Este departamento sempre pode\nusar uma detetive condecorada.", + "This department can always use a decorated detective.", + "Este departamento pode sempre\nusar uma detetive condecorada.")] + [InlineData("A equipa sempre pode contar contigo.", "The team can always count on you.", + "A equipa pode sempre contar contigo.")] + [InlineData("Eles sempre podem ajudar.", "They can always help.", "Eles podem sempre ajudar.")] + [InlineData("A equipa sempre poderia melhorar.", "The team could always improve.", + "A equipa poderia sempre melhorar.")] + [InlineData("Eu sempre posso ajudar.", "I can always help.", "Eu posso sempre ajudar.")] + [InlineData("O negócio é", "Thing is...", "A questão é")] + [InlineData("O negócio é que não tenho tempo.", "The thing is that I don't have time.", + "A questão é que não tenho tempo.")] + [InlineData("A coisa é, ele não queria vir.", "The thing is, he didn't want to come.", + "A questão é que ele não queria vir.")] + [InlineData("O negócio era que não tinha tempo.", "The thing was that I didn't have time.", + "A questão era que não tinha tempo.")] + [InlineData("Bem, o lance é que não tenho tempo.", "Well, the thing is that I don't have time.", + "Bem, a questão é que não tenho tempo.")] + [InlineData("que não tem mais fogo\nem você?", "you got no more fight left in you?", + "que não tens mais fogo\nem ti?")] + [InlineData("que ainda pode ajudar.", "You can still help.", "que ainda podes ajudar.")] + [InlineData("que nunca sabe.", "You never know.", "que nunca sabes.")] + [InlineData("Certo, você é adulto agora,\ne é o Comissário", + "All right, you're your age now, and you're the PC", + "Certo, és adulto agora,\ne és o Comissário")] + [InlineData("Certo, você é médico agora e é o Diretor.", + "All right, you're a doctor now and you're the Director.", + "Certo, és médico agora e és o Diretor.")] + public void NaturalConstructionsAreGeneralizedAndStable(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 que está havendo no laboratório?", "What's there in the lab?", + "O que está a haver no laboratório?")] + [InlineData("Está havendo uma reunião.", "A meeting is taking place.", "Está a haver uma reunião.")] + [InlineData("Sabe o que está havendo?", "You know what's happening?", "Sabes o que está a haver?")] + [InlineData("O que está acontecendo?", "What are you doing?", "O que está a acontecer?")] + [InlineData("O que houve?", null, "O que se passou?")] + [InlineData("Espera até ver a sala.", null, "Espera até ver a sala.")] + [InlineData("Espera até ele ver a sala.", "Wait until he sees the room.", "Espera até ele ver a sala.")] + [InlineData("Espera até veres a sala.", "Wait until you see the room.", "Espera até veres a sala.")] + [InlineData("Alugamos por ti.", "We rented it on your behalf.", "Alugamos por ti.")] + [InlineData("Alugamos por você.", null, "Alugamos por ti.")] + [InlineData("Alugamos por você.", "We rented it instead of you.", "Alugamos por ti.")] + [InlineData("Lutamos por ti.", "We fight for you.", "Lutamos por ti.")] + [InlineData("Este departamento sempre pode usar uma sala.", null, + "Este departamento sempre pode usar uma sala.")] + [InlineData("O negócio é lucrativo.", "The business is profitable.", "O negócio é lucrativo.")] + [InlineData("A coisa é azul.", "The thing is blue.", "A coisa é azul.")] + [InlineData("O negócio é que não tenho tempo.", null, "O negócio é que não tenho tempo.")] + [InlineData("que não tem mais fogo em você?", "He has no more fight left in him?", + "que não tem mais fogo em ti?")] + [InlineData("Certo, você é adulto agora e ele é o Comissário.", + "All right, you're an adult now and he is the Commissioner.", + "Certo, és adulto agora e ele é o Comissário.")] + [InlineData("O tipo de cansaço que não passa a dormir.", "The kind you don't sleep off.", + "O tipo de cansaço que não passa a dormir.")] + public void OtherMeaningsAndUnknownSubjectsStayProtected(string source, string? english, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText(english))); + + [Theory] + [InlineData("O dia passou devagar.", "It was a slow day.")] + [InlineData("Parece um dia devagar.", null)] + [InlineData("Parece um dia devagar.", "It was a busy day.")] + [InlineData("Foi uma semana devagar.", "It was a slow day.")] + public void SlowPeriodCorrectionPreservesAdverbsAndRequiresMatchingReference(string source, string? english) => + Assert.Equal(source, new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText(english))); + + [Fact] + public void SlowPeriodCorrectionRespectsOptionsAndIgnoredText() + { + const string source = "Parece um dia devagar."; + var hint = ReferenceHint.FromText("It looks like a slow day."); + foreach (var options in new[] { new ConversionOptions(Vocabulary: false), + new ConversionOptions(Colloquial: false), new ConversionOptions(DisabledRules: "reference-idioms") }) + Assert.Equal(source, new PortugueseConverter(options).Convert(source, reference: hint)); + var words = new PortugueseLocalWordList(); + words.Ignored.Add("dia devagar"); + Assert.Equal(source, new PortugueseConverter(localWordList: words).Convert(source, reference: hint)); + } + + [Fact] + public void SplitIntroductionsAndDashedRepliesWorkThroughTheSubtitlePipeline() + { + var blocks = SubRipParser.Parse( + "1\n00:00:01,000 --> 00:00:03,000\nO negócio é\n\n" + + "2\n00:00:03,010 --> 00:00:05,000\nque meu novo chefe quer que eu vá.\n\n" + + "3\n00:01:01,000 --> 00:01:03,000\nO que está havendo?\n\n" + + "4\n00:01:03,010 --> 00:01:05,000\n-Espera até ver a sala de jogos.\n-O quê?\n\n" + + "5\n00:02:01,000 --> 00:02:03,000\nCerto, você é adulto agora,\ne é o Diretor.\n\n"); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(), + english: new string?[] { "Thing is...", "My new boss wants me gone.", "What's going on?", + "Wait till you see the game room. The what?", "All right, you're an adult now, and you're the Director." }); + Assert.Equal("A questão é", result[0].Text); + Assert.Equal("que o meu novo chefe quer que eu vá.", result[1].Text); + Assert.Equal("O que se está a passar?", result[2].Text); + Assert.Equal("-Espera até veres a sala de jogos.\n-O quê?", result[3].Text); + Assert.Equal("Certo, és adulto agora,\ne és o Diretor.", result[4].Text); + } + + [Fact] + public void OptionsFormalityAndLocalListsAreRespected() + { + var reference = ReferenceHint.FromText("Wait until you see the room.")!; + Assert.Equal("Espere até ver a sala.", new PortugueseConverter().Convert("Espere até ver a sala.", + reference: reference with { FormalAddress = true })); + Assert.Equal("Espere até ver a sala.", new PortugueseConverter(new ConversionOptions(YouToTu: false)) + .Convert("Espere até ver a sala.", reference: reference)); + Assert.Equal("Espera até ver a sala.", new PortugueseConverter(new ConversionOptions(DisabledRules: "wait-until")) + .Convert("Espere até ver a sala.", reference: reference)); + Assert.Equal("Alugamos por ti.", new PortugueseConverter(new ConversionOptions(Vocabulary: false)) + .Convert("Alugamos por você.", reference: ReferenceHint.FromText("We rented it for you."))); + Assert.Equal("Alugamos para si.", new PortugueseConverter().Convert("Alugamos por você.", + reference: ReferenceHint.FromText("We rented it for you.")! with { FormalAddress = true })); + var list = new PortugueseLocalWordList(); + list.Ignored.Add("Alugamos por você"); + list.Ignored.Add("O que está havendo"); + list.Ignored.Add("O negócio é"); + var converter = new PortugueseConverter(localWordList: list); + Assert.Equal("Alugamos por você.", converter.Convert("Alugamos por você.", + reference: ReferenceHint.FromText("We rented it for you."))); + Assert.Equal("O que está havendo?", converter.Convert("O que está havendo?")); + Assert.Equal("O negócio é", converter.Convert("O negócio é", reference: ReferenceHint.FromText("Thing is..."))); + foreach (var options in new[] { new ConversionOptions(Vocabulary: false), + new ConversionOptions(Colloquial: false), new ConversionOptions(DisabledRules: "explanatory-introductions") }) + Assert.Equal("O negócio é", new PortugueseConverter(options).Convert("O negócio é", + reference: ReferenceHint.FromText("Thing is..."))); + Assert.Equal("Certo, é médico agora e é o Diretor.", new PortugueseConverter().Convert( + "Certo, você é médico agora e é o Diretor.", + reference: ReferenceHint.FromText("All right, you're a doctor now and you're the Director.")! with { FormalAddress = true })); + Assert.Equal("Senhor, é o Diretor.", new PortugueseConverter().Convert("Senhor, você é o Diretor.", + reference: ReferenceHint.FromText("Sir, you're the Director."))); + } +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/NaturalDialogueTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/NaturalDialogueTests.cs new file mode 100644 index 00000000..5af4092e --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/NaturalDialogueTests.cs @@ -0,0 +1,98 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; +using SubtitleEdit.Plugins.Shared; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class NaturalDialogueTests +{ + [Theory] + [InlineData("Tudo o que lembro\né alguém", "All I remember is someone.", "Tudo o que me lembro\né alguém")] + [InlineData("Tudo o que lembro é alguém.", "All I remember is someone.", "Tudo o que me lembro é alguém.")] + [InlineData("A única coisa que lembramos é o ruído.", "The only thing we remember is the noise.", + "A única coisa que nos lembramos é o ruído.")] + [InlineData("mandando-se daqui\no mais rápido possível.", "by getting the hell out of here as quick as you can.", + "saindo daqui\no mais rápido possível.")] + [InlineData("Ele mandou-se daqui.", "He got out of here.", "Ele saiu daqui.")] + [InlineData("Ela estava a mandar-se dali.", "She was going away.", "Ela estava a sair dali.")] + [InlineData("Sobre o que você\ne João discutiram?", "What did you and John fight about?", + "Tu\ne o João discutiram sobre o quê?")] + [InlineData("Sobre o que você e a Ana falaram?", "What did you and Anne talk about?", + "Tu e a Ana falaram sobre o quê?")] + [InlineData("Vá embora!", "Walk away!", "Vá-se!")] + [InlineData("VÁ EMBORA!", "Walk away!", "VÁ-SE!")] + [InlineData("Vai embora!", "Go away!", "Vai-te!")] + [InlineData("Vão embora!", "Go away!", "Vão-se!")] + [InlineData("João! João.\nVá embora!", "John! John.", "João! João.\nVá-se!")] + [InlineData("Está certo que não quer\nque eu desembale aquilo?", + "Are you sure you don't want me to unpack that?", + "Tem a certeza de que não quer\nque desembale aquilo?")] + public void NaturalDialogueKeepsMeaningAndRegister(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 mandou a carta.", "He sent the letter.", "Ele mandou a carta.")] + [InlineData("Ele mandou-se daqui.", "He sent himself from here.", "Ele mandou-se daqui.")] + [InlineData("Sobre o que ele e João discutiram?", "What did he and John argue about?", + "Sobre o que ele e João discutiram?")] + [InlineData("Está certo que ele vem.", "It's certain he is coming.", "Está certo que ele vem.")] + [InlineData("Ele vai embora.", "He leaves.", "Ele vai embora.")] + [InlineData("Vamos embora!", "Let's leave!", "Vamos embora!")] + [InlineData("O que lembro ao João é importante.", "What I remind John of is important.", + "O que lembro ao João é importante.")] + public void OtherSensesArePreserved(string source, string english, string expected) => + Assert.Equal(expected, new PortugueseConverter().Convert(source, reference: ReferenceHint.FromText(english))); + + [Fact] + public void EllipsisVocativeCarriesFormalAddressAcrossDialogueLines() + { + var blocks = SubRipParser.Parse( + "1\n00:00:01,000 --> 00:00:03,000\n-Detetive...\n-Estou só dizendo.\n\n" + + "2\n00:00:03,010 --> 00:00:05,000\n-Isso é necessário?\n-Bem, não queria\n\n" + + "3\n00:00:05,010 --> 00:00:07,000\nconstrangê-lo na frente\nda sua paciente.\n\n"); + var result = SubtitleConversion.Convert(blocks, new ConversionOptions(), english: new string?[] { + "Detective... I'm just saying.", "Is this necessary? Well, I didn't want", + "to embarrass you in front of your patient." }); + Assert.True(result[2].Hint!.FormalAddress); + Assert.Contains("da sua paciente.", result[2].Text); + Assert.DoesNotContain("tua", result[2].Text); + } + + [Fact] + public void InformalCertaintyAndOptionsAreRespected() + { + var hint = ReferenceHint.FromText("Are you sure you don't want me to unpack that?")!; + Assert.Equal("Tens a certeza de que não queres que desembale aquilo?", + new PortugueseConverter().Convert("Estás certo que não queres que eu desembale aquilo?", reference: hint)); + Assert.Equal("Vá embora!", new PortugueseConverter(new ConversionOptions(Colloquial: false)) + .Convert("Vá embora!")); + Assert.Equal("Vá embora!", new PortugueseConverter(new ConversionOptions(DisabledRules: "natural-dialogue")) + .Convert("Vá embora!")); + Assert.Equal("mandando-se daqui.", new PortugueseConverter(new ConversionOptions(Vocabulary: false)) + .Convert("mandando-se daqui.", reference: ReferenceHint.FromText("getting out of here."))); + var words = new PortugueseLocalWordList(); + words.Ignored.Add("Vá embora"); + Assert.Equal("Vá embora!", new PortugueseConverter(localWordList: words).Convert("Vá embora!")); + } + + [Fact] + public void FormalTopicQuestionPreservesCoordinatedSubject() + { + var hint = ReferenceHint.FromText("What did you and John fight about?")! with { FormalAddress = true }; + Assert.Equal("Você e o João discutiram sobre o quê?", + new PortugueseConverter().Convert("Sobre o que você e João discutiram?", reference: hint)); + Assert.Equal("Você e o Jack discutiram sobre o quê?", + new PortugueseConverter().Convert("Sobre o que você e Jack discutiram?", + reference: ReferenceHint.FromText("What did you and Jack fight about?")! with { FormalAddress = true })); + Assert.Equal("Você e a Ana discutiram sobre o quê?", + new PortugueseConverter().Convert("Sobre o que você e Ana discutiram?", + reference: ReferenceHint.FromText("What did you and Anne fight about?")! with { FormalAddress = true })); + Assert.Equal("Tem a certeza de que não quer que desembale aquilo?", + new PortugueseConverter().Convert("Está certo que não quer que eu desembale aquilo?", + reference: ReferenceHint.FromText("Are you sure you don't want me to unpack that?")! with { FormalAddress = true })); + } +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/OrthographyTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/OrthographyTests.cs index 4b7f74cd..dff7bddc 100644 --- a/se5/BrazilianToEuropeanPortuguese.Tests/OrthographyTests.cs +++ b/se5/BrazilianToEuropeanPortuguese.Tests/OrthographyTests.cs @@ -13,6 +13,43 @@ namespace BrazilianToEuropeanPortuguese.Tests; public sealed class OrthographyTests { + [Theory] + [InlineData("Baseada em que?", "Baseada em quê?")] + [InlineData("Estão a falar de que?", "Estão a falar de quê?")] + [InlineData("Para que?", "Para quê?")] + [InlineData("Com que?", "Com quê?")] + [InlineData("O que?", "O quê?")] + [InlineData("Que?", "Quê?")] + [InlineData("BASEADA EM QUE?", "BASEADA EM QUÊ?")] + [InlineData("Baseada em\nque?", "Baseada em\nquê?")] + [InlineData("Baseada em que?", "Baseada em quê?")] + [InlineData("Baseada em que?", "Baseada em quê?")] + [InlineData("-Baseada em que?\n-Nada.", "-Baseada em quê?\n-Nada.")] + [InlineData("Baseada em que ideia?", "Baseada em que ideia?")] + [InlineData("Que queres?", "Que queres?")] + [InlineData("Penso que sim.", "Penso que sim.")] + [InlineData("Baseada em quê?", "Baseada em quê?")] + public void FinalInterrogativeQueIsAccentedInBothOrthographies(string source, string expected) + { + foreach (var postAgreement in new[] { true, false }) + { + var converter = new PortugueseConverter(new ConversionOptions(PostAgreement: postAgreement)); + Assert.Equal(expected, converter.Convert(source)); + Assert.Equal(expected, converter.Convert(expected)); + } + } + + [Fact] + public void InterrogativeAccentRespectsSpellingOptionsAndIgnoredPhrases() + { + const string source = "Baseada em que?"; + Assert.Equal(source, new PortugueseConverter(new ConversionOptions(Spelling: false)).Convert(source)); + Assert.Equal(source, new PortugueseConverter(new ConversionOptions(DisabledRules: "orthography")).Convert(source)); + var list = new PortugueseLocalWordList(); + list.Ignored.Add("Baseada em que"); + Assert.Equal(source, new PortugueseConverter(localWordList: list).Convert(source)); + } + [Theory] [InlineData("A ação do diretor.", "A acção do director.")] [InlineData("Os atores e as atrizes.", "Os actores e as actrizes.")] diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/PersonalNameComplementTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/PersonalNameComplementTests.cs new file mode 100644 index 00000000..322a5af0 --- /dev/null +++ b/se5/BrazilianToEuropeanPortuguese.Tests/PersonalNameComplementTests.cs @@ -0,0 +1,78 @@ +using SubtitleEdit.Plugins.BrazilianToEuropeanPortuguese; + +namespace BrazilianToEuropeanPortuguese.Tests; + +public sealed class PersonalNameComplementTests +{ + [Theory] + [InlineData("até mesmo para\npensar em Linda.", "até mesmo para\npensar na Linda.")] + [InlineData("Penso em Maria.", "Penso na Maria.")] + [InlineData("Pensas em João.", "Pensas no João.")] + [InlineData("Ele pensa em Ana.", "Ele pensa na Ana.")] + [InlineData("Pensamos em Pedro.", "Pensamos no Pedro.")] + [InlineData("Eles pensam em Sofia.", "Eles pensam na Sofia.")] + [InlineData("Pensei em Miguel.", "Pensei no Miguel.")] + [InlineData("Ela pensou em Teresa.", "Ela pensou na Teresa.")] + [InlineData("Pensávamos em Carlos.", "Pensávamos no Carlos.")] + [InlineData("Eles pensarão em Beatriz.", "Eles pensarão na Beatriz.")] + [InlineData("Pensaria em Daniel.", "Pensaria no Daniel.")] + [InlineData("Se pensarmos em Mariana.", "Se pensarmos na Mariana.")] + [InlineData("Não penso em Sara.", "Não penso na Sara.")] + [InlineData("Penso sempre em Linda.", "Penso sempre na Linda.")] + [InlineData("Pensar em Ana e em João.", "Pensar na Ana e no João.")] + [InlineData("pensar em Ana ou em Pedro.", "pensar na Ana ou no Pedro.")] + [InlineData("pensar em Maria João.", "pensar na Maria João.")] + [InlineData("pensar em Ana Silva.", "pensar na Ana Silva.")] + [InlineData("Tenho pensado em Marta.", "Tenho pensado na Marta.")] + [InlineData("Estou a pensar em José.", "Estou a pensar no José.")] + [InlineData("pensar\nem\nLinda.", "pensar\nna\nLinda.")] + [InlineData("pensar em Linda.", "pensar na Linda.")] + public void ThinkComplementsUseArticlesWithReviewedPersonalNames(string source, string expected) + { + var converter = new PortugueseConverter(); + Assert.Equal(expected, converter.Convert(source)); + Assert.Equal(expected, converter.Convert(expected)); + } + + [Theory] + [InlineData("pensar em alguém.")] + [InlineData("pensar em pessoa alguma.")] + [InlineData("pensar em pessoas.")] + [InlineData("pensar em sair.")] + [InlineData("pensar em Lisboa.")] + [InlineData("pensar em Portugal.")] + [InlineData("pensar em Deus.")] + [InlineData("pensar em linda companhia.")] + [InlineData("pensar em rosas.")] + [InlineData("pensar em soluções.")] + [InlineData("pensar na Linda.")] + [InlineData("pensar em Xanthe.")] + [InlineData("falar em Linda.")] + public void AmbiguousAndUnrelatedComplementsStayUnchanged(string source) => + Assert.Equal(source, new PortugueseConverter().Convert(source)); + + [Fact] + public void PronounOptionControlsNameArticles() => + Assert.Equal("pensar em Linda.", + new PortugueseConverter(new ConversionOptions(Pronouns: false)).Convert("pensar em Linda.")); + + [Fact] + public void IndividualRuleCanBeDisabled() => + Assert.Equal("pensar em Linda.", + new PortugueseConverter(new ConversionOptions(DisabledRules: "personal-name-complements")) + .Convert("pensar em Linda.")); + + [Fact] + public void LocalListsKeepPriority() + { + var ignored = new PortugueseLocalWordList(); + ignored.Ignored.Add("pensar em Linda"); + Assert.Equal("pensar em Linda.", new PortugueseConverter(localWordList: ignored) + .Convert("pensar em Linda.")); + + var custom = new PortugueseLocalWordList(); + custom.Words.Add(("pensar em Linda", "recordar a Linda")); + Assert.Equal("recordar a Linda.", new PortugueseConverter(localWordList: custom) + .Convert("pensar em Linda.")); + } +} diff --git a/se5/BrazilianToEuropeanPortuguese.Tests/ReferenceTests.cs b/se5/BrazilianToEuropeanPortuguese.Tests/ReferenceTests.cs index eb238ec9..184b16ac 100644 --- a/se5/BrazilianToEuropeanPortuguese.Tests/ReferenceTests.cs +++ b/se5/BrazilianToEuropeanPortuguese.Tests/ReferenceTests.cs @@ -371,13 +371,22 @@ public void ThirdPersonReferenceKeepsSeuAndThirdPersonVerbs(string text, string [Theory] [InlineData("Precisamos falar com\nseu marido. Ele está em casa?", "We need to talk to your husband. Is he home?")] [InlineData("Agora, seu irmão Tiago,", "Now, your brother Tiago,")] - [InlineData("Mamãe tá brava com ele\npor quê ficou ao seu lado.", "Mom's mad at him because he took your side.")] [InlineData("Você está bem?", "Are you okay?")] [InlineData("Não fala assim.", "Don't talk like that.")] [InlineData("Se afaste!", "Step aside!")] public void SecondPersonOrNeutralReferenceChangesNothing(string text, string english) => Assert.Equal(Converter.Convert(text), Converter.Convert(text, null, ReferenceHint.FromText(english))); + [Fact] + public void FamilySubjectArticleDoesNotOverrideThirdPersonPossessive() + { + const string source = "Mamãe tá brava com ele\npor quê ficou ao seu lado."; + const string expected = "Mãe está zangada com ele\npor quê ficou ao seu lado."; + Assert.Equal(expected, Converter.Convert(source)); + Assert.Equal("A " + expected, Converter.Convert(source, null, + ReferenceHint.FromText("Mom's mad at him because he took your side."))); + } + [Fact] public void ParsesVulkanDevicesAndSkipsSoftwareRenderers() { diff --git a/se5/BrazilianToEuropeanPortuguese/ConversionConstructions.cs b/se5/BrazilianToEuropeanPortuguese/ConversionConstructions.cs index 764b15c8..094af699 100644 --- a/se5/BrazilianToEuropeanPortuguese/ConversionConstructions.cs +++ b/se5/BrazilianToEuropeanPortuguese/ConversionConstructions.cs @@ -50,9 +50,32 @@ private static HashSet BuildHabitualVerbs() private static readonly Regex RecoverableProgressiveSubject = new( Before + @"(?quando|enquanto|porque|se)(?\s+)eu\s+(?estou)(?=\s+a\s+\p{L}+(?:ar|er|ir)\b)" + @"(?!\s+a\s+\p{L}+(?:ar|er|ir)\s+(?:e\s+(?:tu|ele|ela|nós|vocês|eles|elas)|mas)\b)", Ci); + private static readonly Regex ModalAlways = new( + Before + @"(?sempre)(?\s+)(?posso|podes|pode|podemos|podem|podia|podias|podíamos|podiam|" + + @"poderia|poderias|poderíamos|poderiam|devo|deves|deve|devemos|devem|devia|devias|devíamos|deviam)" + + @"(?=\s+\p{L}+(?:ar|er|ir|ôr)" + After + @")", Ci); - public static string NaturalWordOrder(string text) + public static string NaturalWordOrder(string text, ReferenceHint? reference = null) { + text = EchoTopicQuestion.Replace(text, m => + { + if (!Regex.IsMatch(reference?.EnglishText ?? "", @"\bwhat\s+did\s+you\s+and\b[^.!?]*\b(?:fight|argue|talk|discuss)\b", Ci)) + return m.Value; + var subject = m.Groups["subject"].Value; + if (!Regex.IsMatch(subject, @"\b(?:você|tu)\s+e\s+", Ci)) return m.Value; + subject = Regex.Replace(subject, @"(?<=\be\s)(?(?-i:\p{Lu}\p{Ll}+))\b", name => + { + var value = name.Groups["name"].Value; + return MasculinePersonalNames.Contains(value) ? "o " + value : + FemininePersonalNames.Contains(value) ? "a " + value : value; + }, Ci); + return m.Groups["head"].Value + CaseHelper.Capitalize(subject) + " " + + m.Groups["verb"].Value + " sobre o quê"; + }); + if (Regex.IsMatch(reference?.EnglishText ?? "", @"\b(?:can|could|may|might|should|must)\s+always\b", Ci)) + text = ModalAlways.Replace(text, m => CaseHelper.MatchCase(m.Groups["always"].Value, + m.Groups["modal"].Value) + m.Groups["gap"].Value + + (CaseHelper.IsAllUpper(m.Groups["always"].Value) ? "SEMPRE" : "sempre")); text = HabitualAlways.Replace(text, m => HabitualVerbs.Contains(m.Groups["v"].Value) ? m.Groups["head"].Value + CaseHelper.MatchCase(m.Groups["adv"].Value, m.Groups["v"].Value) + m.Groups["space"].Value + (CaseHelper.IsAllUpper(m.Groups["adv"].Value) ? "SEMPRE" : "sempre") @@ -61,6 +84,51 @@ public static string NaturalWordOrder(string text) m.Groups["link"].Value + m.Groups["space"].Value + m.Groups["verb"].Value); } + private static readonly Regex HappeningQuestion = new( + @"(?(?:^|(?<=[.!?;\n]))[\s\p{Co}-]*)(?o\s+que)(?\s+)(?está|estava)" + + @"\s+(?:havendo|acontecendo|a\s+(?:haver|acontecer))(?=\s*\?)", Ci); + + public static string HappeningQuestions(string text, ReferenceHint? reference) => + reference is not null && !Regex.IsMatch(reference.EnglishText ?? "", + @"\bwhat(?:['’]s|\s+(?:is|was))\s+(?:going\s+on|happening)\b", Ci) + ? text + : HappeningQuestion.Replace(text, m => m.Groups["head"].Value + + m.Groups["question"].Value + m.Groups["gap"].Value + + (CaseHelper.IsAllUpper(m.Value) ? "SE " : "se ") + m.Groups["aux"].Value + + (CaseHelper.IsAllUpper(m.Value) ? " A PASSAR" : " a passar")); + + private static readonly Regex WaitUntilPersonal = new( + @"(?espere|espera)(?\s+)(?até)(?\s+)" + + @"(?\p{L}+(?:ar|er|ir)|pôr)" + After + @"(?!-|\s+(?:eu|ele|ela|eles|elas|nós|vocês)\b)", Ci); + private static readonly Regex EnglishWaitUntilYou = new( + @"\bwait\s+(?:until|till)\s+you\b", Ci); + + public static string WaitUntil(string text, ReferenceHint? reference) => + EnglishWaitUntilYou.IsMatch(reference?.EnglishText ?? "") + ? WaitUntilPersonal.Replace(text, m => + { + var infinitive = m.Groups["inf"].Value; + if (!PortugueseVerbs.IsInfinitive(infinitive) || + !PortugueseVerbs.IsKnownInfinitive(infinitive) && !NaturaDictionary.Load(true).Check(infinitive)) return m.Value; + return CaseHelper.MatchCase(m.Groups["wait"].Value, "espera") + m.Groups["gap"].Value + + m.Groups["until"].Value + m.Groups["space"].Value + PortugueseVerbs.PersonalInfinitive(infinitive, "es"); + }) : text; + + private static readonly Regex AcquisitionBeneficiary = new( + Before + @"(?" + Alternatives(new[] { "alugar", "comprar", "reservar", "preparar", "arrendar" }.SelectMany(Forms)) + + @")(?(?:\s+(?!por\b)[\p{L}\p{M}]+){0,4})(?\s+)(?por)(?\s+)" + + @"(?você|ti|mim|nós|ele|ela|eles|elas)" + After, Ci); + private static readonly Regex EnglishAcquisitionBeneficiary = new( + @"\b(?:rent(?:ed|s|ing)?|bought|buy|buys|buying|reserv(?:e|ed|es|ing)|prepar(?:e|ed|es|ing))\b" + + @"[^.!?;]{0,80}\bfor\s+(?:you|me|us|him|her|them)\b", Ci); + + public static string AcquisitionBeneficiaries(string text, ReferenceHint? reference) => + EnglishAcquisitionBeneficiary.IsMatch(reference?.EnglishText ?? "") && + !Regex.IsMatch(reference?.EnglishText ?? "", @"\b(?:instead\s+of|on\s+(?:your|his|her|their|my|our)\s+behalf|by\s+you)\b", Ci) + ? AcquisitionBeneficiary.Replace(text, m => m.Groups["verb"].Value + m.Groups["object"].Value + + m.Groups["gap"].Value + CaseHelper.MatchCase(m.Groups["prep"].Value, "para") + + m.Groups["space"].Value + m.Groups["person"].Value) : text; + private const string MeasurementNumber = @"(?:\d+(?:[,.]\d+)?|(?:um|uma|dois|duas|três|quatro|cinco|seis|sete|oito|nove|" + @"dez|onze|doze|treze|catorze|quinze|dezasseis|dezassete|dezoito|dezanove|vinte|trinta|quarenta|cinquenta|" + @"sessenta|setenta|oitenta|noventa|cem|cento)(?:\s+e\s+(?:um|uma|dois|duas|três|quatro|cinco|seis|sete|oito|nove|" + @@ -76,6 +144,274 @@ public static string NaturalWordOrder(string text) public static string PhysicalDescriptions(string text) => PersonMeasurement.Replace(text, m => m.Value[..^m.Groups["pre"].Length] + CaseHelper.MatchCase(m.Groups["pre"].Value, "com")); + private static readonly HashSet FemininePersonalNames = new(StringComparer.Ordinal) + { + "Ana", "Maria", "Joana", "Mariana", "Marta", "Sofia", "Inês", "Beatriz", "Carolina", + "Catarina", "Leonor", "Teresa", "Isabel", "Helena", "Patrícia", "Diana", "Sara", + "Sandra", "Paula", "Luísa", "Susana", "Cristina", "Alice", "Laura", "Linda", + "Clara", "Rosa", "Eva", "Emma", "Emily", "Sarah", "Mary", "Elizabeth", "Jennifer", + "Jessica", "Rachel", "Susan", "Margaret", "Karen", "Nancy", "Abigail", + }; + private static readonly HashSet MasculinePersonalNames = new(StringComparer.Ordinal) + { + "João", "José", "Manuel", "António", "Pedro", "Miguel", "Tiago", "Diogo", "Francisco", + "Rui", "Luís", "Carlos", "Paulo", "Jorge", "Ricardo", "André", "Tomás", "Gonçalo", + "Nuno", "Daniel", "David", "Gabriel", "Rafael", "Bruno", "Alexandre", "Eduardo", + "John", "Jack", "James", "Robert", "Michael", "William", "Richard", "Thomas", "Charles", + "Henry", "George", "Kevin", "Frank", "Peter", "Paul", "Mark", "Steven", "Anthony", + }; + private static readonly Regex ThinkPersonalName = new( + Before + @"(?" + Alternatives(Forms("pensar")) + @")(?:-(?:me|te|se|nos|vos|lhe|lhes))?" + + @"(?\s+(?:(?:sempre|também|ainda|só|apenas|muito|constantemente)\s+){0,2})" + + @"(?em\s+[\p{L}\p{M}]+" + After + + @"(?:\s+(?:e|ou)\s+em\s+[\p{L}\p{M}]+" + After + @")*)", Ci); + private static readonly Regex PersonalNamePreposition = new( + Before + @"(?em)(?\s+)(?[\p{L}\p{M}]+)" + After, Ci); + + public static string PersonalNameComplements(string text) => ThinkPersonalName.Replace(text, m => + m.Value[..(m.Groups["complement"].Index - m.Index)] + + PersonalNamePreposition.Replace(m.Groups["complement"].Value, ContractPersonalName)); + + private static string ContractPersonalName(Match m) + { + var name = m.Groups["name"].Value; + var article = FemininePersonalNames.Contains(name) ? "na" : + MasculinePersonalNames.Contains(name) ? "no" : null; + if (article == null) return m.Value; + return CaseHelper.MatchCase(m.Groups["prep"].Value, article) + m.Groups["space"].Value + name; + } + + private static readonly Dictionary QuestionActions = new(StringComparer.OrdinalIgnoreCase) + { + ["ligue"] = "ligo", ["telefone"] = "telefono", ["pergunte"] = "pergunto", + ["diga"] = "digo", ["espere"] = "espero", ["deixe"] = "deixo", + ["vá"] = "vou", ["venha"] = "venho", ["ajude"] = "ajudo", ["leve"] = "levo", ["faça"] = "faço", + ["liga"] = "ligo", ["telefona"] = "telefono", ["pergunta"] = "pergunto", + ["diz"] = "digo", ["espera"] = "espero", ["deixa"] = "deixo", + ["vai"] = "vou", ["vem"] = "venho", ["ajuda"] = "ajudo", ["leva"] = "levo", ["faz"] = "faço", + }; + private static readonly Regex ActionQuestion = new( + @"^(?[\s\uE000-\uF8FF]*(?:[-–—]\s*)?)(?" + Alternatives(QuestionActions.Keys) + @")" + + @"(?[^.!;]*\?[\s\uE000-\uF8FF]*)$", Ci); + private static readonly Regex TelephoneName = new( + Before + @"(?" + Alternatives(new[] { "ligar", "telefonar" }.SelectMany(Forms)) + @")" + + @"(?\s+)(?para)(?\s+)(?[\p{L}\p{M}]+)" + After, Ci); + + public static string ActionQuestions(string text, string? previous, ReferenceHint? reference, bool nameArticles) + { + var english = reference?.EnglishText ?? ""; + var explicitSelf = Regex.IsMatch(english, @"^(?:shall|should|do|can)\s+i\s+" + + @"(?:call|phone|ring|ask|tell|wait|leave|go|come|help|take|do)\b[^.!;]*\?$", Ci); + if (!explicitSelf && reference is not { SelfActionQuestion: true } || + PortugueseConverter.YouToTuRules.IsFormal(text) || + previous != null && PortugueseConverter.YouToTuRules.IsFormal(previous)) return text; + text = ActionQuestion.Replace(text, m => m.Groups["head"].Value + + CaseHelper.MatchCase(m.Groups["verb"].Value, QuestionActions[m.Groups["verb"].Value]) + m.Groups["rest"].Value); + if (!nameArticles) return text; + return TelephoneName.Replace(text, m => + { + var name = m.Groups["name"].Value; + // Jamie's reviewed masculine reading is scoped to telephone questions, not general name inference. + var article = FemininePersonalNames.Contains(name) ? "a" : + MasculinePersonalNames.Contains(name) || name == "Jamie" ? "o" : null; + if (article == null) return m.Value; + return m.Groups["verb"].Value + m.Groups["gap"].Value + m.Groups["prep"].Value + + m.Groups["space"].Value + article + " " + name; + }); + } + + private static readonly Regex ReportedIntention = new( + Before + @"(?disseste|sabias|percebeste|achaste|pensaste|descobriste)(?\s+que\s+)" + + @"(?irias)(?=\s+(?:(?:me|te|se|nos|vos|lhe|lhes)\s+)?\p{L}+(?:ar|er|ir|ôr)" + After + @")", Ci); + + public static string ReportedPastIntention(string text, ReferenceHint reference) => + Regex.IsMatch(reference.EnglishText ?? "", @"\byou\s+were\s+(?:gonna|going\s+to)\b", Ci) + ? ReportedIntention.Replace(text, m => m.Groups["head"].Value + m.Groups["bridge"].Value + + CaseHelper.MatchCase(m.Groups["aux"].Value, "ias")) : text; + + private static readonly Regex StopNoun = new( + Before + @"(?uma|umas|a|as|esta|estas|essa|essas)(?\s+)" + + @"(?(?:(?:pequenas?|breves?|rápidas?|curtas?|últimas?|próximas?|outras?)\s+)?)" + + @"(?paradas?)" + After + + @"(?!\s+(?:cardíacas?|cardiorrespiratórias?|militar(?:es)?|de\s+(?:sucesso|sucessos))\b)", Ci); + + private static readonly Dictionary RespondToIncidentForms = MapForms("atender", "responder"); + private static readonly Regex IncidentResponse = new( + Before + @"(?" + Alternatives(RespondToIncidentForms.Keys) + @")(?\s+)" + + @"(?no|na|nos|nas)(?=\s+(?:(?:um|uma|o|a|os|as)\s+)?\p{L})", Ci); + private static readonly Regex EnglishIncidentResponse = new( + @"\brespond(?:ed|ing|s)?\s+to\s+(?:the|a|an|that|this|our)\s+" + + @"(?:(?:night|local|nearby)\s+){0,2}(?:club|nightclub|scene|incident|emergency|accident|" + + @"fire|collision|crash|hospital|house|home|school|building|hotel|bar|restaurant|address)\b", Ci); + private static readonly Regex EnglishMedicalArrest = new( + @"\b(?:(?:went|go|goes|going|gone)\s+into\s+(?:(?:cardiac|cardiorespiratory)\s+)?arrest|" + + @"(?:had|has|have|having|suffered|suffers|suffering)\s+(?:a\s+)?(?:cardiac|cardiorespiratory)\s+arrest)\b", Ci); + private static readonly Regex MedicalArrestNoun = new( + Before + @"(?teve|tiveram|tinha|tinham|tem|têm|ter|terá|terão|sofreu|sofreram|sofre|sofrem|sofrer)" + + @"(?\s+)(?
uma|a)(?\s+)(?parada|paragem)" + After + + @"(?!\s+(?:cardíaca|cardiorrespiratória|respiratória|militar|de\s+\p{L})\b)", Ci); + + public static string EmergencyConstructions(string text, ReferenceHint? reference) + { + var english = reference?.EnglishText ?? ""; + if (EnglishIncidentResponse.IsMatch(english) && + !Regex.IsMatch(english, @"\b(?:phone|telephone|doorbell|answered|reply|replied)\b", Ci)) + text = IncidentResponse.Replace(text, m => CaseHelper.MatchCase(m.Groups["verb"].Value, + RespondToIncidentForms[m.Groups["verb"].Value]) + m.Groups["gap"].Value + m.Groups["prep"].Value); + if (EnglishMedicalArrest.IsMatch(english)) + text = MedicalArrestNoun.Replace(text, m => m.Groups["verb"].Value + m.Groups["gap"].Value + + m.Groups["article"].Value + m.Groups["space"].Value + + CaseHelper.MatchCase(m.Groups["noun"].Value, + Regex.IsMatch(english, @"\bcardiorespiratory\s+arrest\b", Ci) + ? "paragem cardiorrespiratória" : "paragem cardíaca")); + return text; + } + + private static readonly Regex EchoTopicQuestion = new( + @"(?(?:^|[.!?;]\s*|\n)[\s\uE000-\uF8FF-]*)sobre\s+o\s+que\s+" + + @"(?(?:você|tu)\s+e\s+(?:(?:o|a)\s+)?(?-i:\p{Lu}\p{Ll}+)(?:\s+(?-i:\p{Lu}\p{Ll}+)){0,2})\s+" + + @"(?discutiram|brigaram|falaram|conversaram)(?=\s*\?)", Ci); + private static readonly Dictionary SlangDepartureForms = MapForms("mandar", "sair"); + private static readonly Regex SlangDeparture = new( + Before + @"(?:(?me|te|se|nos|vos)\s+)?(?" + Alternatives(SlangDepartureForms.Keys) + + @")(?:-(?me|te|se|nos|vos))?(?\s+)(?daqui|daí|dali)" + After, Ci); + private static readonly Regex DepartureCommand = new( + @"(?(?:^|[.!?;]\s*|\n)[\s\uE000-\uF8FF-]*(?:por\s+favor,\s*)?)" + + @"(?vá|vai|vão)(?\s+)embora(?=\s*[.!…]|\s*$)", Ci); + private static readonly Regex CertaintyQuestion = new( + @"(?(?:^|[.!?;]\s*|\n)[\s\uE000-\uF8FF-]*)(?está|estás|estão|estamos)" + + @"(?\s+)cert[oa]s?\s+que\s+(?[^.!?]*)(?=\?)", Ci); + + public static string NaturalDialogue(string text, ReferenceHint? reference, bool vocabulary) + { + if (vocabulary && Regex.IsMatch(reference?.EnglishText ?? "", @"\b(?:get(?:ting)?|got|go(?:ing)?|went|leave|leaving)" + + @"\s+(?:(?:the\s+hell\s+)?out\s+of\s+here|away|here)\b", Ci)) + text = SlangDeparture.Replace(text, m => + m.Groups["cl"].Success || m.Groups["attached"].Success + ? CaseHelper.MatchCase(m.Groups["verb"].Value, SlangDepartureForms[m.Groups["verb"].Value]) + + m.Groups["gap"].Value + m.Groups["place"].Value : m.Value); + text = DepartureCommand.Replace(text, m => m.Groups["head"].Value + PortugueseVerbs.AttachEnclitic(m.Groups["verb"].Value, + CaseHelper.IsAllUpper(m.Groups["verb"].Value) + ? (m.Groups["verb"].Value.Equals("vai", StringComparison.OrdinalIgnoreCase) ? "TE" : "SE") + : (m.Groups["verb"].Value.Equals("vai", StringComparison.OrdinalIgnoreCase) ? "te" : "se"))); + if (Regex.IsMatch(reference?.EnglishText ?? "", @"\bare\s+you\s+sure\b", Ci)) + text = CertaintyQuestion.Replace(text, m => + { + var verb = m.Groups["verb"].Value.ToLowerInvariant() switch + { "estás" => "tens", "estão" => "têm", "estamos" => "temos", _ => "tem" }; + var body = m.Groups["body"].Value; + if (Regex.IsMatch(reference?.EnglishText ?? "", @"\bwant(?:ed|s)?\s+me\s+to\b", Ci)) + body = Regex.Replace(body, @"\bque\s+eu\s+(?=\p{L})", "que ", Ci); + return m.Groups["head"].Value + CaseHelper.MatchCase(m.Groups["verb"].Value, verb) + + m.Groups["gap"].Value + "a certeza de que " + body; + }); + return text; + } + + private const string FamilyNoun = @"mãe|pai|mamã|papá|avó|avô|tia|tio|irmã|irmão"; + private static readonly Regex FamilySubject = new( + @"(?(?:^|[.!?;]\s*|\n)[\s\uE000-\uF8FF-]*)(?" + FamilyNoun + @")" + After + + @"(?=\s+(?:e\s+(?:(?:o|a)\s+)?(?:" + FamilyNoun + @")\b|" + + @"(?\p{L}+)\b))", Ci); + private static readonly Regex CoordinatedFamily = new( + Before + @"(?(?:a|o)\s+(?:" + FamilyNoun + @")" + + @"(?:\s+(?-i:\p{Lu}\p{Ll}+)){0,2})(?\s+e\s+)(?" + FamilyNoun + @")" + + After + @"(?=\s+(?-i:\p{Lu}[\p{L}\p{M}]+)\b|\s+(?\p{L}+)\b|[\s\uE000-\uF8FF]*$)", Ci); + private static readonly Regex EnglishFamilySubject = new( + @"(?:^|[.!?]\s*)(?:mom|mum|mother|dad|father|grandma|grandpa|grandmother|grandfather|" + + @"aunt|uncle|sister|brother)\b(?!\s*[,!?:])", Ci); + + public static string FamilySubjects(string text, ReferenceHint? reference) + { + if (!EnglishFamilySubject.IsMatch(reference?.EnglishText ?? "")) return text; + text = FamilySubject.Replace(text, m => + { + if (m.Groups["verb"].Success && (!PortugueseVerbs.IsKnownVerbForm(m.Groups["verb"].Value) || + PortugueseVerbs.IsInfinitive(m.Groups["verb"].Value))) return m.Value; + return m.Groups["head"].Value + FamilyArticle(m.Groups["noun"].Value, true); + }); + return CoordinatedFamily.Replace(text, m => + { + if (m.Groups["verb"].Success && (!PortugueseVerbs.IsKnownVerbForm(m.Groups["verb"].Value) || + PortugueseVerbs.IsInfinitive(m.Groups["verb"].Value))) return m.Value; + return m.Groups["first"].Value + m.Groups["link"].Value + FamilyArticle(m.Groups["noun"].Value, false); + }); + } + + private static string FamilyArticle(string noun, bool initial) + { + var feminine = Regex.IsMatch(noun, @"^(?:mãe|mamã|avó|tia|irmã)$", Ci); + var article = feminine ? "a" : "o"; + if (CaseHelper.IsAllUpper(noun)) return article.ToUpperInvariant() + " " + noun; + return (initial && char.IsUpper(noun[0]) ? article.ToUpperInvariant() : article) + " " + noun; + } + + private static readonly Dictionary ReferenceObjectActions = new(StringComparer.OrdinalIgnoreCase) + { + ["found"] = new[] { "encontrou", "encontraram", "encontrei", "encontrámos", "encontramos" }, + ["find"] = new[] { "encontrou", "encontraram", "encontrei", "encontrámos", "encontramos" }, + ["saw"] = new[] { "viu", "viram", "vi", "vimos" }, + ["helped"] = new[] { "ajudou", "ajudaram", "ajudei", "ajudámos", "ajudamos" }, + ["called"] = new[] { "chamou", "chamaram", "chamei", "chamámos", "chamamos" }, + ["took"] = new[] { "levou", "levaram", "levei", "levámos", "levamos" }, + ["got"] = new[] { "levou", "levaram", "levei", "levámos", "levamos" }, + ["brought"] = new[] { "trouxe", "trouxeram", "trouxemos", "levou", "levaram", "levei", "levámos", "levamos" }, + ["rescued"] = new[] { "resgatou", "resgataram", "resgatei", "resgatámos", "resgatamos" }, + ["saved"] = new[] { "salvou", "salvaram", "salvei", "salvámos", "salvamos" }, + }; + private static readonly Regex EnglishObjectAction = new( + @"\b(?" + Alternatives(ReferenceObjectActions.Keys) + @")\s+you\b" + + @"(?!\s+(?:(?:and|or)\s+(?:him|her|them|me|us|you)|all|both|guys|two)\b)", Ci); + private static readonly Regex PastObjectClitic = new( + Before + @"(?o|a)(?\s+)(?" + + Alternatives(ReferenceObjectActions.Values.SelectMany(forms => forms).Distinct(StringComparer.OrdinalIgnoreCase)) + + @")" + After + @"(?!-)", Ci); + private static readonly Dictionary BringPastForms = new(StringComparer.OrdinalIgnoreCase) + { + ["levou"] = "trouxe", ["levaram"] = "trouxeram", ["levei"] = "trouxe", + ["levámos"] = "trouxemos", ["levamos"] = "trouxemos", + }; + + public static string ReferenceObjectClitics(string text, ReferenceHint? reference, bool vocabulary) + { + var english = reference?.EnglishText ?? ""; + return PastObjectClitic.Replace(text, m => + { + var verb = m.Groups["verb"].Value; + var actions = EnglishObjectAction.Matches(english).Cast().Where(action => + ReferenceObjectActions[action.Groups["verb"].Value].Contains(verb, StringComparer.OrdinalIgnoreCase)).ToArray(); + if (actions.Length != 1) return m.Value; + var action = actions[0]; + if (action.Groups["verb"].Value.ToLowerInvariant() is "found" or "find" && + Regex.IsMatch(english[(action.Index + action.Length)..], @"^\s+(?:a|an|the)\s+\p{L}", Ci)) + return m.Value; + if (action.Groups["verb"].Value.Equals("got", StringComparison.OrdinalIgnoreCase) && + !Regex.IsMatch(english[(action.Index + action.Length)..], @"^\s+to\s+(?:the|a|an|this|that)\s+\p{L}", Ci)) + return m.Value; + if (Regex.IsMatch(english, @"\b(?:found|find|saw|helped|called|took|got|brought|rescued|saved)\s+" + + @"(?:him|her|them|it)\b", Ci)) return m.Value; + if (vocabulary && action.Groups["verb"].Value.Equals("brought", StringComparison.OrdinalIgnoreCase) && + BringPastForms.TryGetValue(verb, out var brought)) + verb = CaseHelper.MatchCase(verb, brought); + var prefix = Regex.Match(text[..m.Index], @"[^.!?;:,]*$").Value; + if (Regex.IsMatch(prefix, Before + @"(?:" + PortugueseConverter.YouToTuRules.ProclisisTriggers + + @"|se|quando|onde|como|porque|embora|caso|enquanto)\b", Ci)) + return CaseHelper.MatchCase(m.Groups["clitic"].Value, "te") + m.Groups["gap"].Value + verb; + if (char.IsUpper(m.Groups["clitic"].Value[0])) + verb = CaseHelper.MatchCase(m.Groups["clitic"].Value, verb); + return PortugueseVerbs.AttachEnclitic(verb, CaseHelper.IsAllUpper(verb) ? "TE" : "te"); + }); + } + + public static string TravelStops(string text, ReferenceHint? reference) => + Regex.IsMatch(reference?.EnglishText ?? "", @"\b(?:(?:a|one|two|three|some|the|this|that|another|our)\s+" + + @"(?:(?:little|small|short|brief|quick|last|next|more|bus|train|scheduled)\s+){0,3}|" + + @"(?:little|small|short|brief|quick|last|next|bus|train|scheduled)\s+)stops?\b", Ci) && + !Regex.IsMatch(reference?.EnglishText ?? "", @"\b(?:parade|cardiac|arrest|chart|standing|stopped)\b", Ci) + ? StopNoun.Replace(text, m => m.Groups["det"].Value + m.Groups["gap"].Value + + m.Groups["adjective"].Value + CaseHelper.MatchCase(m.Groups["noun"].Value, + m.Groups["noun"].Value.EndsWith('s') ? "paragens" : "paragem")) : text; + private static readonly Regex InformationTopic = new( @"^(?:(?:tenho|temos|há|são|e|quais\s+são)\s+)?(?:(?:o|a|os|as|um|uma|uns|umas|ess[ea]s?|est[ea]s?)\s+)?" + @"(?:(?:boas?|más?|últimas?|novas?)\s+)?(?:notícias|novidades|informações|detalhes|verdade|explicação)" + @@ -277,6 +613,66 @@ internal static bool KeepsExtractedQuestionSubject(string beforeVerb, ReferenceH private static bool Second(string word, out string second) => PortugueseVerbs.TryThirdToSecond(word, out second) || PortugueseVerbs.TryGuessThirdToSecond(word, out second); + private static readonly Regex ReferencedConditionalComplement = new( + Before + @"(?
se\s+" + Adverbs + @")(?\p{L}+)(?\s+que\s+" + Adverbs +
+        @")(?\p{L}+)" + After + @"(?!-)", Ci);
+    private static readonly Regex SharedPredicate = new(
+        @"(?=" + Before + @"(?:(?você|tu)\s+)?(?\p{L}+)" + After +
+        @"(?[^,;.!?]{0,100}?)(?,\s*(?:(?:e|mas|ou)\s+)?|\s+(?:e|mas|ou)\s+)" +
+        @"(?" + Adverbs + @")(?\p{L}+)" + After + @"(?!-))", Ci);
+    private static readonly Regex SubjectChange = new(
+        Before + @"(?:eu|ele|ela|eles|elas|nós|vocês|você|tu|que|quem|se|quando|porque|embora)\b" +
+        @"|(?-i:\p{Lu}[\p{Ll}\p{M}]+)", Ci);
+    private static readonly Regex EnglishSharedYouPredicate = new(
+        @"^\s*(?:(?:all\s+right|okay|ok|well|right)[,;]?\s+)?(?:(?:and|but|or)\s+)?" +
+        @"(?:(?:if|when|whenever|unless|as\s+soon\s+as)\s+)?" +
+        @"you(?:['’](?:re|ve|ll|d))?\b[^.!?;]{1,120}?(?:,\s*|\s+(?:and|but|or)\s+)" +
+        @"you(?:['’](?:re|ve|ll|d))?\b", Ci);
+
+    public static string InformalPredicates(string text, ReferenceHint? reference)
+    {
+        var conditionalMatches = ReferencedConditionalComplement.Matches(text);
+        if (conditionalMatches.Count == 1 && reference is { RepeatedConditionalYouSubject: true })
+            text = ReferencedConditionalComplement.Replace(text, m =>
+                m.Groups["v"].Value.EndsWith("sse", StringComparison.OrdinalIgnoreCase) &&
+                Second(m.Groups["v"].Value, out var verb) && Second(m.Groups["aux"].Value, out var auxiliary)
+                    ? m.Groups["pre"].Value + verb + m.Groups["bridge"].Value + auxiliary : m.Value);
+
+        var changedTails = new HashSet();
+        // Overlapping heads support chained predicates; right-to-left edits preserve captured offsets.
+        foreach (Match m in SharedPredicate.Matches(text).Cast().Reverse())
+        {
+            var head = m.Groups["head"].Value;
+            if ((!m.Groups["subject"].Success || !Second(head, out _)) &&
+                !PortugueseVerbs.IsSecondPersonForm(head)) continue;
+            if (SubjectChange.IsMatch(m.Groups["body"].Value) ||
+                reference?.EnglishText != null && !EnglishSharedYouPredicate.IsMatch(reference.EnglishText) ||
+                reference?.EnglishText == null && !Regex.IsMatch(head,
+                    @"^(?:é|és|era|eras|foi|foste|seria|serias|está|estás|estava|estavas|esteve|estiveste)$", Ci) ||
+                PortugueseVerbs.IsSecondPersonForm(m.Groups["tail"].Value) ||
+                !PortugueseVerbs.TryThirdToSecond(m.Groups["tail"].Value, out var second)) continue;
+            var tail = m.Groups["tail"];
+            if (changedTails.Add(tail.Index))
+                text = text.Remove(tail.Index, tail.Length).Insert(tail.Index, second);
+        }
+        return text;
+    }
+
+    private static readonly Regex ExplanatoryThing = new(
+        @"(?(?:^|(?<=[.!?;\n]))[\s\uE000-\uF8FF-]*(?:(?:bem|mas|olha|pois)[,;]?\s+)?)" +
+        @"(?o\s+(?:negócio|lance)|a\s+coisa)(?\s+)(?é|era)" +
+        @"(?:(?\s+que)\b|(?\s*,\s*)|(?=\s*[\uE000-\uF8FF]*(?:$|\.\.\.|…|\r?\n)))", Ci);
+    private static readonly Regex EnglishExplanatoryThing = new(
+        @"(?:^|[.!?]\s*)(?:(?:well|but|look|so)[,;]?\s+)?(?:the\s+)?thing\s+(?:is|was)\b" +
+        @"(?=\s*(?:that\b|[,.:…]|$))", Ci);
+
+    public static string ExplanatoryIntroductions(string text, ReferenceHint? reference) =>
+        EnglishExplanatoryThing.IsMatch(reference?.EnglishText ?? "")
+            ? ExplanatoryThing.Replace(text, m => m.Groups["head"].Value +
+                CaseHelper.MatchCase(m.Groups["phrase"].Value, "a questão") + m.Groups["space"].Value +
+                m.Groups["verb"].Value + (m.Groups["que"].Success ? m.Groups["que"].Value :
+                    m.Groups["comma"].Success ? " que " : "")) : text;
+
     public static string Clauses(string text, ReferenceHint? reference)
     {
         text = ControlledClause.Replace(text, m =>
@@ -574,8 +970,28 @@ public static string Conversational(string text, ReferenceHint? reference)
     private static readonly Regex BrokenSlang = new(
         @"^(?(?:eu\s+)?estou)(?\s+)(?quebrad[oa])(?[.!…]?)$", Ci);
 
+    private static readonly Dictionary SlowPeriodReferences = new(StringComparer.OrdinalIgnoreCase)
+    {
+        ["dia"] = "day", ["dias"] = "days", ["manhã"] = "morning", ["manhãs"] = "mornings",
+        ["tarde"] = "afternoon", ["tardes"] = "afternoons", ["noite"] = "night", ["noites"] = "nights",
+        ["semana"] = "week", ["semanas"] = "weeks", ["mês"] = "month", ["meses"] = "months",
+    };
+    private static readonly Regex SlowPeriod = new(
+        Before + @"(?" + Alternatives(SlowPeriodReferences.Keys) + @")(?\s+)(?devagar)" +
+        After + @"(?!\s+(?:demais|de\s+mais)\b)", Ci);
+
     public static string Idioms(string text, ReferenceHint? reference)
     {
+        text = SlowPeriod.Replace(text, m =>
+        {
+            var noun = m.Groups["noun"].Value;
+            if (!Regex.IsMatch(reference?.EnglishText ?? "", @"\bslow\s+" +
+                SlowPeriodReferences[noun] + @"\b", Ci)) return m.Value;
+            var feminine = Regex.IsMatch(noun, @"^(?:manhãs?|tardes?|noites?|semanas?)$", Ci);
+            var adjective = feminine ? "lenta" : "lento";
+            if (noun.EndsWith('s')) adjective += "s";
+            return noun + m.Groups["gap"].Value + CaseHelper.MatchCase(m.Groups["adverb"].Value, adjective);
+        });
         text = ClarifyDoubt.Replace(text, m => CaseHelper.MatchCase(m.Groups["v"].Value, ClarifyDoubtForms[m.Groups["v"].Value]) +
             m.Groups["cl"].Value + m.Groups["space"].Value + m.Groups["det"].Value + m.Groups["noun"].Value);
         text = MaybeSlang.Replace(text, m => CaseHelper.MatchCase(m.Value,
diff --git a/se5/BrazilianToEuropeanPortuguese/PortugueseConverter.cs b/se5/BrazilianToEuropeanPortuguese/PortugueseConverter.cs
index 2aa6c9ad..1a24dfee 100644
--- a/se5/BrazilianToEuropeanPortuguese/PortugueseConverter.cs
+++ b/se5/BrazilianToEuropeanPortuguese/PortugueseConverter.cs
@@ -661,8 +661,27 @@ void Track(string id, string description)
         Track("construction-vocabulary", "Complement-qualified vocabulary senses");
         if (_options.YouToTu) s = ConversionConstructions.PersonalInfinitives(s, informalConstructions);
         Track("personal-infinitives", "Explicit-subject personal infinitives");
+        if (_options.Pronouns) s = ConversionConstructions.FamilySubjects(s, reference);
+        Track("family-subjects", "Reference-confirmed family subjects with definite articles");
+        if (_options.Pronouns && informalConstructions)
+            s = ConversionConstructions.ReferenceObjectClitics(s, reference, _options.Vocabulary);
+        Track("reference-object-clitics", "Reference-confirmed informal past direct objects");
         if (informalConstructions) s = ConversionConstructions.Clauses(s, reference);
         Track("subject-continuity", "Controlled and reference-confirmed clause subjects");
+        if (_options.Colloquial && _options.Progressive && _options.Pronouns)
+            s = ConversionConstructions.HappeningQuestions(s, reference);
+        Track("happening-questions", "Impersonal questions about ongoing events");
+        if (_options.YouToTu && reference is not { FormalAddress: true } && !YouToTuRules.IsFormal(s))
+            s = ConversionConstructions.WaitUntil(s, reference);
+        Track("wait-until", "Reference-confirmed personal infinitives after esperar até");
+        if (_options.Vocabulary) s = ConversionConstructions.AcquisitionBeneficiaries(s, reference);
+        Track("acquisition-beneficiaries", "Para for reference-confirmed beneficiaries rather than substitution");
+        if (_options.Vocabulary && _options.Colloquial) s = ConversionConstructions.ExplanatoryIntroductions(s, reference);
+        Track("explanatory-introductions", "Reference-confirmed explanatory thing is constructions");
+        if (_options.Vocabulary) s = ConversionConstructions.EmergencyConstructions(s, reference);
+        Track("emergency-constructions", "Reference-confirmed incident response and medical arrest");
+        if (_options.Vocabulary) s = ConversionConstructions.TravelStops(s, reference);
+        Track("travel-stops", "Reference-confirmed stopping places and brief journey stops");
         if (_options.Colloquial && _options.Vocabulary) s = ConversionConstructions.Idioms(s, reference);
         Track("reference-idioms", "Brazilian expressions and English-confirmed literal idioms");
 
@@ -671,7 +690,7 @@ void Track(string id, string description)
 
         if (_options.YouToTu)
         {
-            s = YouToTuRules.Apply(s, previousText, reference);
+            s = YouToTuRules.Apply(s, previousText, reference, _options.Pronouns && _options.Colloquial);
         }
         Track("address", "Address, conjugation and possessives");
 
@@ -739,6 +758,11 @@ void Track(string id, string description)
         {
             s = NearDemonstrative.Replace(s, m => CaseHelper.MatchCase(m.Value, NearDemonstratives[m.Value]));
         }
+        else if (ReferentialThat.IsMatch(reference?.EnglishText ?? "") &&
+                 !Regex.IsMatch(reference?.EnglishText ?? "", @"\b(?:this|these|those)\b", Ci))
+        {
+            s = DistalDemonstrative.Replace(s, m => CaseHelper.MatchCase(m.Value, DistalDemonstratives[m.Value]));
+        }
         Track("reference-clitics", "Future-subjunctive clitics and reference demonstratives");
 
         if (_options.Progressive && _wordRegex != null)
@@ -771,15 +795,24 @@ void Track(string id, string description)
         }
         Track("reflexive-clitics", "Reflexives and clitic allomorphs");
 
+        if (_options.YouToTu && _options.Colloquial && reference is not { FormalAddress: true } &&
+            reference is not { IgnorePreviousAddress: true })
+            s = ConversionConstructions.ActionQuestions(s, previousText, reference, _options.Pronouns);
+        Track("action-questions", "Reference-confirmed self-directed action questions");
+
         if (_options.Colloquial && _options.Vocabulary)
             s = ConversionConstructions.SpokenInformation(s, previousText, reference);
         Track("spoken-information", "English-confirmed figurative give me requests about information");
 
-        if (_options.Colloquial) s = ConversionConstructions.NaturalWordOrder(s);
+        if (_options.Colloquial) s = ConversionConstructions.NaturalWordOrder(s, reference);
         Track("natural-word-order", "Habitual sempre after lexical verbs and recoverable progressive subjects");
+        if (_options.Colloquial && _options.Pronouns) s = ConversionConstructions.NaturalDialogue(s, reference, _options.Vocabulary);
+        Track("natural-dialogue", "Departure commands and reference-confirmed departure slang and certainty questions");
 
         if (_options.Colloquial) s = ConversionConstructions.PhysicalDescriptions(s);
         Track("physical-descriptions", "Com in person height and weight descriptions");
+        if (_options.Pronouns) s = ConversionConstructions.PersonalNameComplements(s);
+        Track("personal-name-complements", "Definite articles in pensar em complements with reviewed personal names");
 
         if (_options.Vocabulary && _options.Pronouns) s = ConversionConstructions.Complaints(s, informalConstructions);
         Track("construction-vocabulary", "Complement-qualified vocabulary senses");
@@ -879,6 +912,21 @@ private static string ReplaceFutureSubjunctiveClitic(Match m)
     private static readonly Regex NearDemonstrative = new(
         NotWordBefore + @"(? k.Length)) + ")" + NotWordAfter, Ci);
 
+    private static readonly Dictionary DistalDemonstratives = new(StringComparer.OrdinalIgnoreCase)
+    {
+        ["isto"] = "isso", ["disto"] = "disso", ["nisto"] = "nisso",
+    };
+    private static readonly Regex DistalDemonstrative = new(
+        NotWordBefore + @"(?:isto|disto|nisto)" + NotWordAfter, Ci);
+    private static readonly Regex ReferentialThat = new(
+        @"\b(?:(?:would|will|could|can|does|did|should|might)\s+that\s+" +
+        @"(?:work|help|change|matter|mean|make|be|have|do|happen|affect|cost|take|sound|seem)\b|" +
+        @"what(?:['’]s|\s+(?:is|does|did))\s+that(?=\s+(?:got|have|has|mean|do)\b|\s*[,;.!?]|$)|" +
+        @"that(?:['’]s|\s+(?:is|was|has|had|will|would|could|can|does|did|should|might|works?|worked|sounds?|seems?))\b|" +
+        @"(?:with|about|of|for|on|at|to|from)\s+that(?=\s*[,;.!?]|$)|" +
+        @"(?:do|does|did|doing|done|try|tried|mean|means|meant|said|say|saying|know|knew|believe)\s+that(?=\s*[,;.!?]|$))",
+        Ci);
+
     // "pegar o ônibus/trem/táxi" → "apanhar o autocarro/comboio/táxi" ("pegar" alone means "to hold/grab" in Portugal).
     private static readonly Regex CatchTransport = new(
         NotWordBefore + @"(?(?pegu|peg)(?o|as|a|amos|am|ei|aste|ou|ámos|aram|ava|avas|ávamos|avam|ar|e|es|emos|em|asse|ares|armos|arem))" +
@@ -2513,7 +2561,7 @@ internal static class YouToTuRules
     {
         private const string You = "você";
         private const string Adverbs = "não|nunca|jamais|também|já|ainda|sempre|só|realmente|simplesmente|provavelmente|certamente|nem|apenas|agora|quase";
-        private const string ProclisisTriggers = "não|nunca|jamais|também|já|ainda|sempre|só|nem|apenas|quase|talvez|que|quem|ninguém|alguém|tudo|todos";
+        internal const string ProclisisTriggers = "não|nunca|jamais|também|já|ainda|sempre|só|nem|apenas|quase|talvez|que|quem|ninguém|alguém|tudo|todos";
 
         private static readonly Regex Contigo = new(NotWordBefore + @"com\s+você" + NotWordAfter, Ci);
         private static readonly Regex Preposition = new(NotWordBefore + @"(?

para|pra|de|a|por|sem|em|sobre|até|contra|perante|entre\s+mim\s+e)\s+você" + NotWordAfter, Ci); @@ -2653,14 +2701,20 @@ private static string ReplaceLookAtYou(Match m) @"prefeita|madame|mademoiselle|mister|monsieur|sir|lady|lorde|milorde|vossa|(?:vice-)?cônsul"; private static readonly Regex FormalAddress = new(NotWordBefore + "(?:" + FormalTitles + ")" + NotWordAfter, Ci); + private static readonly Regex CopularTitlePredicate = new( + NotWordBefore + @"(?:é|és|são|somos|era|eras|eram|foi|foste|seria|serias)\s+" + + @"(?:o|a|um|uma)\s+(?:" + FormalTitles + ")" + NotWordAfter, Ci); private static readonly Regex FormalSubjectYou = new( @"(?" + NotWordBefore + @"(?:que|se|quando|porque|onde|como|quem|enquanto|embora|caso|talvez|quanto|qual|até\s+que|antes\s+que|depois\s+que)\s+)?" + NotWordBefore + @"(?você)(?(?:\s+(?:" + Adverbs + @")" + NotWordAfter + @"){0,2})\s+(?:(?se|me|nos|o|a|os|as|lhe)\s+)?(?\p{L}+)" + NotWordAfter + @"(?!-)", Ci); - public static bool IsFormal(string s) => FormalAddress.IsMatch(s); + public static bool IsFormal(string s) => FormalAddress.IsMatch( + ExplicitAddressee.IsMatch(s) || Regex.IsMatch(s, NotWordBefore + @"(?:és|eras|foste|serias)" + NotWordAfter, Ci) + ? CopularTitlePredicate.Replace(s, "") : s); - public static string Apply(string s, string? previousText = null, ReferenceHint? reference = null) + public static string Apply(string s, string? previousText = null, ReferenceHint? reference = null, + bool conversationalObjectConditional = false) { var explicitAddressee = ExplicitAddressee.IsMatch(s); // "Está bem?" next to "Is he okay?": the dropped subject is not "você". @@ -2670,6 +2724,7 @@ public static string Apply(string s, string? previousText = null, ReferenceHint? return ApplyFormal(s); } + s = ConversionConstructions.InformalPredicates(s, reference); s = LookAtYou.Replace(s, ReplaceLookAtYou); s = YouInfinitive.Replace(s, m => { @@ -2721,7 +2776,7 @@ public static string Apply(string s, string? previousText = null, ReferenceHint? s = CommandYou.Replace(s, m => m.Groups["pre"].Value + m.Groups["v"].Value + " " + CaseHelper.MatchCase(m.Groups["you"].Value, "tu")); s = StartCommandYou.Replace(s, m => m.Groups["pre"].Value + m.Groups["v"].Value + " " + CaseHelper.MatchCase(m.Groups["you"].Value, "tu")); s = ObjectYou.Replace(s, ReplaceObject); - s = SubjectYou.Replace(s, ReplaceSubject); + s = SubjectYou.Replace(s, m => ReplaceSubject(m, conversationalObjectConditional)); s = WaitingForYou.Replace(s, m => m.Groups["aux"].Value + m.Groups["place"].Value + m.Groups["sp"].Value + (CaseHelper.IsAllUpper(m.Value) ? "À TUA ESPERA" : "à tua espera")); @@ -2749,6 +2804,10 @@ public static string Apply(string s, string? previousText = null, ReferenceHint? s = ReferenceSubordinateVerb.Replace(s, m => ReplaceReferenceSubordinateVerb(m, reference)); s = ReferenceCausalVerb.Replace(s, ReplaceReferenceSecondPersonVerb); } + if (reference.ConditionalYouSubject) + s = ReferenceConditionalVerb.Replace(s, ReplaceReferenceSecondPersonVerb); + if (reference.RepeatedYouSubject) + s = ConversionConstructions.ReportedPastIntention(s, reference); } s = FamilyCongratulations.Replace(s, m => m.Groups["pre"].Value + (CaseHelper.IsAllUpper(m.Value) ? "ÀS" : "às") + m.Groups["sp"].Value + @@ -2758,10 +2817,20 @@ public static string Apply(string s, string? previousText = null, ReferenceHint? } private static readonly Regex ReferenceSubjectVerb = new( - @"(?

^[\s""“«'\uE000-\uF8FF]*(?:(?:então|bem|ora|bom|pois|mas|e|agora)\s+)?)(?\p{L}+)(?=\s)", Ci);
+            @"(?
^[\s""“«'\uE000-\uF8FF]*(?:(?:não|sim)\s*[.!]\s*)?" +
+            @"(?:(?:então|bem|ora|bom|pois|mas|e|agora)[,;]?\s+)?" +
+            @"(?:(?:talvez|provavelmente|certamente|possivelmente|não|nunca|já|ainda|só|apenas|também)\s+){0,3})" +
+            @"(?\p{L}+)" + NotWordAfter + @"(?=\s|[.!?…]|$|[\uE000-\uF8FF])(?!-)", Ci);
+
+        private static readonly Regex ReferenceConditionalVerb = new(
+            @"(?
^[\s""“«'\uE000-\uF8FF]*(?:(?:e|mas|ou|então|bem)[,;]?\s+)?" +
+            @"(?:se|quando|sempre\s+que|a\s+menos\s+que|assim\s+que)\s+" +
+            @"(?:(?:não|nunca|já|ainda|sempre|só|alguma\s+vez|também)\s+){0,3})" +
+            @"(?\p{L}+)" + NotWordAfter + @"(?!-)", Ci);
 
         private static readonly Regex ReferenceSubordinateVerb = new(
-            @"(?
(?\p{L}+)(?![\p{L}\p{M}-])", Ci);
+            @"(?
(?\p{L}+)(?![\p{L}\p{M}-])", Ci);
 
         private static readonly Regex ImpersonalWhatVerb = new(
             @"^(?:acontece|aconteceu|acontecia|acontecerá|aconteceria|aconteça|acontecesse)$", Ci);
@@ -2771,6 +2840,15 @@ public static string Apply(string s, string? previousText = null, ReferenceHint?
 
         private static string ReplaceReferenceSubordinateVerb(Match m, ReferenceHint reference)
         {
+            var prefix = m.Result("$`");
+            if (Regex.IsMatch(prefix, @"\b(?:quer|queres|queria|querias|querem|quiser)\s*$", Ci) &&
+                Regex.IsMatch(reference.EnglishText ?? "", @"\bwant(?:ed|s)?\s+me\s+to\b", Ci))
+                return m.Value;
+            if (Regex.IsMatch(m.Groups["pre"].Value, @"\b(?:não|nunca|já|ainda|sempre|também|só|apenas)\b", Ci) &&
+                !Regex.IsMatch(prefix, @"^[\s""“«'\uE000-\uF8FF-]*$|\b" +
+                    @"(?:sei|sabes|sabe|sabemos|disse|dizes|diz|digo|dizer|acho|achas|acha|penso|pensas|pensa|" +
+                    @"creio|acredito|certeza|claro|óbvio)\s*$", Ci))
+                return m.Value;
             // In "quem achas que vê", "quem" is the embedded subject, not the addressee.
             if (ConversionConstructions.KeepsExtractedQuestionSubject(m.Result("$`") + m.Groups["pre"].Value, reference))
                 return m.Value;
@@ -2780,7 +2858,7 @@ private static string ReplaceReferenceSubordinateVerb(Match m, ReferenceHint ref
                 var after = m.Result("$'");
                 var impersonal = ImpersonalWhatVerb.IsMatch(verb) ||
                     verb.Equals("está", StringComparison.OrdinalIgnoreCase) &&
-                    Regex.IsMatch(after, @"^\s+(?:a\s+acontecer|acontecendo)\b", Ci);
+                    Regex.IsMatch(after, @"^\s+(?:a\s+(?:acontecer|haver)|acontecendo|havendo)\b", Ci);
                 var copular = CopularWhatVerb.IsMatch(verb) &&
                     (!reference.SecondPersonWhatSubject || Regex.IsMatch(after, @"^\s+(?:isso|isto|aquilo)\b", Ci));
                 if (impersonal || copular) return m.Value;
@@ -3087,7 +3165,7 @@ private static string ReplaceObject(Match m)
         /// "você tem" → "tens": European Portuguese normally drops the subject pronoun, the verb ending
         /// already says who it is ("Você não se lembra?" → "Não te lembras?").
         /// 
-        private static string ReplaceSubject(Match m)
+        private static string ReplaceSubject(Match m, bool conversationalObjectConditional)
         {
             var verb = m.Groups["v"].Value;
             var explicitTu = m.Groups["you"].Value.Equals("tu", StringComparison.OrdinalIgnoreCase);
@@ -3128,7 +3206,18 @@ private static string ReplaceSubject(Match m)
                     ? CaseHelper.MatchCase(clitic, "te")
                     : clitic;
                 var proclisis = m.Groups["sub"].Success || Regex.IsMatch(adv, @"(?:^|\s)(?:" + ProclisisTriggers + @")(?:$|\s)", Ci) || PortugueseVerbs.IsFutureOrConditional(verb);
-                result = proclisis
+                var conditionalObject = conversationalObjectConditional && !m.Groups["sub"].Success &&
+                    adv.Length == 0 && cl.ToLowerInvariant() is "o" or "a" or "os" or "as" &&
+                    PortugueseVerbs.TrySplitFuture(second, out _, out var ending, true) &&
+                    ending.Equals("ias", StringComparison.OrdinalIgnoreCase);
+                result = conditionalObject
+                    ? "irias " + PortugueseVerbs.AttachEnclitic(
+                        second[..^3].ToLowerInvariant() switch
+                        {
+                            "dir" => "dizer", "far" => "fazer", "trar" => "trazer",
+                            var infinitive => infinitive,
+                        }, cl)
+                    : proclisis
                     ? (adv.Length > 0 ? adv + " " : string.Empty) + cl + " " + second
                     : (adv.Length > 0 ? adv + " " : string.Empty) + PortugueseVerbs.AttachEnclitic(second, cl);
             }
@@ -3521,7 +3610,22 @@ private static string ReplaceReflexiveCommand(Match m)
 
         // "obriga-os a te verem" / "dopar-te e depois te afogar" → "a verem-te" / "depois afogar-te".
         private static readonly Regex LinkedInfinitiveClitic = new(
-            NotWordBefore + @"(?a|e\s+depois|depois|e|ou)\s+(?me|te|nos|lhe|lhes)\s+(?\p{Ll}+(?:ar|er|ir|arem|erem|irem|ares|eres|ires|armos|ermos|irmos))" + NotWordAfter + @"(?!-)", Ci);
+            NotWordBefore + @"(?a|e\s+depois|depois|e|ou|mas)\s+(?me|te|se|nos|vos|lhe|lhes)\s+" +
+            @"(?(?:[\p{L}\p{M}]+(?:ar|er|ir|or|ôr)|pôr)(?:em|es|mos)?)" + NotWordAfter + @"(?!-)", Ci);
+
+        private static string ReplaceLinkedInfinitive(Match m)
+        {
+            var verb = m.Groups["v"].Value;
+            var infinitive = Regex.Replace(verb.ToLowerInvariant(), @"(?:em|es|mos)$", "");
+            // At a coordinated clause start, "se" can be a conditional conjunction, not a clitic.
+            if (m.Groups["cl"].Value.Equals("se", StringComparison.OrdinalIgnoreCase) &&
+                m.Groups["l"].Value.ToLowerInvariant() is "e" or "ou" or "mas")
+                return m.Value;
+            if (!PortugueseVerbs.IsInfinitive(infinitive) ||
+                (!PortugueseVerbs.IsKnownInfinitive(infinitive) && !NaturaDictionary.Load(true).Check(infinitive)))
+                return m.Value;
+            return m.Groups["l"].Value + " " + PortugueseVerbs.AttachEnclitic(verb, m.Groups["cl"].Value);
+        }
 
         // "Eu vi ele" / "Mata ela!" → "Eu vi-o" / "Mata-a!"  ("não vi ele" → "não o vi")
         private static readonly Regex ObjectPronoun = new(
@@ -3530,7 +3634,8 @@ private static string ReplaceReflexiveCommand(Match m)
 
         private static readonly HashSet Modals = new(StringComparer.OrdinalIgnoreCase)
         {
-            "vou", "vais", "vai", "vamos", "vão", "ia", "ias", "íamos", "iam", "fui", "foi", "quero", "queres", "quer",
+            "vou", "vais", "vai", "vamos", "vão", "ia", "ias", "íamos", "iam", "iria", "irias", "iríamos", "iriam",
+            "fui", "foi", "quero", "queres", "quer",
             "queremos", "querem", "queria", "querias", "queríamos", "queriam", "posso", "podes", "pode", "podemos",
             "podem", "podia", "podias", "podíamos", "podiam", "poderia", "poderias", "poderíamos", "poderiam",
             "preciso", "precisas", "precisa", "precisamos", "precisam", "precisava", "devo", "deves", "deve", "devemos",
@@ -3938,13 +4043,7 @@ public static string Apply(string s)
             s = CommaClitic.Replace(s, m => ReplaceLeading(m, capitalize: false));
             s = SubjectClitic.Replace(s, ReplaceSubject);
             s = ModalClitic.Replace(s, ReplaceModal);
-            s = LinkedInfinitiveClitic.Replace(s, m =>
-            {
-                var v = m.Groups["v"].Value;
-                return PortugueseVerbs.IsInfinitive(v) || Regex.IsMatch(v, @"(?:ar|er|ir)(?:em|es|mos)$", RegexOptions.IgnoreCase)
-                    ? m.Groups["l"].Value + " " + PortugueseVerbs.AttachEnclitic(v, m.Groups["cl"].Value)
-                    : m.Value;
-            });
+            s = LinkedInfinitiveClitic.Replace(s, ReplaceLinkedInfinitive);
             s = NounSubjectClitic.Replace(s, ReplaceNounSubject);
             s = AdverbClitic.Replace(s, m =>
             {
@@ -4174,7 +4273,7 @@ private static string ReplaceObject(Match m)
         private static readonly Regex InfinitiveProclisis = new(
             @"(?
(?eu|tu|ele|ela|nós|vocês|eles|elas)(?\s+))?" +
-            @"(?vou|vais|vai|vamos|vão|ia|ias|íamos|iam|irei|irás|irá|iremos|irão|estou|estás|está|estamos|estão|estava|estavas|estávamos|estavam|" +
+            @"(?vou|vais|vai|vamos|vão|ia|ias|íamos|iam|iria|irias|iríamos|iriam|irei|irás|irá|iremos|irão|estou|estás|está|estamos|estão|estava|estavas|estávamos|estavam|" +
             @"posso|podes|pode|podemos|podem|podia|podias|podíamos|podiam|quero|queres|quer|queremos|querem|queria|querias|queríamos|queriam|" +
             @"devo|deves|deve|devemos|devem|devia|devias|devíamos|deviam)(?\s+)(?\p{L}+)-(?me|te|se|nos|vos|lhe|lhes)(?![\p{L}\p{M}-])", Ci);
 
@@ -4591,7 +4690,7 @@ void Add(Person p, params string[] forms)
         private static readonly Regex Verb = new(
             NotWordBefore + @"(?se\s+)?(?" + string.Join("|", BuildForms().Keys.OrderByDescending(k => k.Length)) + @")" + NotWordAfter + @"(?!-)" +
             @"(?=(?\s+(?:de|do|da|dos|das|disso|disto|daquilo|daquele|daquela|daqueles|daquelas|dele|dela|deles|delas|desse|dessa|" +
-            @"desses|dessas|deste|desta|destes|destas|dum|duma|dumas|duns|ti|mim|nós)" + NotWordAfter + @")|(?\s*(?:[.!?…,]|$)))",
+            @"desses|dessas|deste|desta|destes|destas|dum|duma|dumas|duns|ti|mim|nós)" + NotWordAfter + @")|(?\s*(?:[.!?…,]|$))|(?\s+(?:é|era|são|eram)\b))",
             RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline);
 
         private static readonly Regex ClauseTail = new(@"(?:[^.!?…;\n]|\n(?![-–—]))*$", RegexOptions.CultureInvariant);
@@ -4638,11 +4737,15 @@ private static string Replace(string s, Match m, bool formal)
             if (comma < 0 && Titles.Contains(previous ?? string.Empty)) formal = true;
             // "não se lembra" already has it; only a Brazilian "Se lembra..." at the start is moved.
             if (m.Groups["se"].Success && !bareStart) return m.Value;
+            if (m.Groups["relative"].Success && (esquecer || !Regex.IsMatch(clause,
+                @"\b(?:tudo\s+o\s+que|(?:a\s+)?(?:única\s+)?coisa\s+que)\s*$", Ci))) return m.Value;
 
             if (m.Groups["end"].Success)
             {
                 // At the end of the clause the object may be before it ("o que esqueci", "tudo o que lembro").
-                if (words.Any(w => w.Equals("que", StringComparison.OrdinalIgnoreCase) || w.Equals("quê", StringComparison.OrdinalIgnoreCase) ||
+                var relativeRecall = !esquecer && Regex.IsMatch(clause,
+                    @"\b(?:tudo\s+o\s+que|(?:a\s+)?(?:única\s+)?coisa\s+que)\s*$", Ci);
+                if (!relativeRecall && words.Any(w => w.Equals("que", StringComparison.OrdinalIgnoreCase) || w.Equals("quê", StringComparison.OrdinalIgnoreCase) ||
                                    w.Equals("qual", StringComparison.OrdinalIgnoreCase))) return m.Value;
                 // "Esquece!" / "Esquece isso" (forget it) is fine in Portugal; only past and negative forms take the pronoun at the end.
                 if (esquecer && !Preterite.Contains(lower) && !(previous?.Equals("não", StringComparison.OrdinalIgnoreCase) ?? false)) return m.Value;
diff --git a/se5/BrazilianToEuropeanPortuguese/PortugueseOrthography.cs b/se5/BrazilianToEuropeanPortuguese/PortugueseOrthography.cs
index e27d8045..20cf0985 100644
--- a/se5/BrazilianToEuropeanPortuguese/PortugueseOrthography.cs
+++ b/se5/BrazilianToEuropeanPortuguese/PortugueseOrthography.cs
@@ -33,9 +33,13 @@ private static readonly (string Before, string After)[] Stems =
         ("crêem", "creem"), ("dêem", "deem"),
     };
     private static readonly Regex Word = new(@"[\p{L}\p{M}]+");
+    private static readonly Regex FinalInterrogativeQue = new(
+        @"(? CaseHelper.MatchCase(match.Value, "quê"));
         var dictionary = NaturaDictionary.Load(postAgreement);
         return Word.Replace(text, match =>
         {
diff --git a/se5/BrazilianToEuropeanPortuguese/README.md b/se5/BrazilianToEuropeanPortuguese/README.md
index 21e105cd..1c227706 100644
--- a/se5/BrazilianToEuropeanPortuguese/README.md
+++ b/se5/BrazilianToEuropeanPortuguese/README.md
@@ -18,9 +18,14 @@ Earlier updates include reviewed workbook vocabulary and verb constructions,
 English-confirmed sportswear and shared-residence expressions, physical descriptions,
 finding filters, and original PT-BR text visible on warning-only rows.
 
-The standalone source build is **1.4.1**. Official Subtitle Edit package versions
+The standalone source build is **1.4.2**. Official Subtitle Edit package versions
 are assigned by the upstream release workflow and may differ.
 
+Version **1.4.2** improves contextual agreement and formal address, reference-confirmed
+objects and vocabulary senses, natural dialogue constructions and interrogative
+accentuation. It adds regression coverage for these interactions while preserving
+subtitle cue timings and user-controlled rule options.
+
 Converts Brazilian Portuguese (pt-BR) subtitles to natural European Portuguese (pt-PT):
 spelling, vocabulary and grammar. Like *American to British*, it shows a checkable
 preview of every changed line, so you can review and untick individual changes before
@@ -314,6 +319,12 @@ The **Orthography** selector chooses **Post-1990 agreement (AO90)** (default) or
 Existing saved settings continue to use post-agreement spelling. Switching off
 **Spelling** also switches off agreement conversion.
 
+With **Spelling** enabled, interrogative *que* immediately before a question
+mark receives its tonic accent: *Baseada em que?* → *Baseada em quê?*,
+*De que?* → *De quê?*. This applies in both orthographies, preserves casing
+and subtitle markup, and leaves *Que queres?* and *em que ideia?* unchanged.
+No English reference or MiniLM score is needed for this accentuation rule.
+
 Reviewed spelling stems are checked against the selected European Portuguese
 dictionary before a change is proposed. For example:
 
@@ -896,6 +907,160 @@ The conversion is rule- and dictionary-based, so **always review the preview**.
 
 ## Dialogue context, grammar review and layout
 
+Short action questions can ask what the **speaker** should do, rather than
+give an instruction. With **You → tu** and **Colloquial** enabled, an explicit
+English *Should I call …?*, or a bare *Call …?* immediately following an
+English *if you …* cue ending in an ellipsis, supports *Ligue para João?* →
+*Ligo para o João?*. The latter requires adjacent cues within 1.5 seconds.
+Other supported actions include asking, telling, waiting, going and helping.
+With **Pronouns** enabled, telephone questions add articles for reviewed
+personal names; unknown names keep their original article choice. Genuine
+commands/questions and formal address are not changed by this rule
+(`action-questions`). The reviewed masculine reading of the unisex name
+*Jamie* is limited to these telephone questions.
+
+Replies beginning *Não.* or *Sim.* no longer hide an English-confirmed **you**
+subject. *Não. Disse que iria me ajudar* can become *Não. Disseste que me ias
+ajudar* when English says *you said you were going to help me*; *would help*
+keeps the conditional *me irias ajudar*. Modal clitics after *que* use the
+existing proclisis rule. A named title in an identifying apposition such as
+*É a minha amiga, Sra. Silva* does not establish formal address for nearby
+cues; genuine vocatives and direct address remain formal.
+
+With **Vocabulary** enabled, reference-confirmed journey stops use
+*paragem/paragens*: *É uma pequena parada* → *É uma pequena paragem*.
+The `travel-stops` stage runs before colloquial *parada* → *coisa* handling,
+so the stopping sense is not lost. Unknown senses, stationary adjectives and
+military parades are not blanket replacements; existing medical vocabulary
+continues to use its own rules.
+
+Reference-confirmed constructions also cover standalone happening questions
+(*O que está havendo?* → *O que se está a passar?*), informal *espera até*
+with personal infinitives (*Espera até ver* → *Espera até veres*), and
+acquisition beneficiaries (*Alugamos por ti* → *Alugamos para ti*).
+The beneficiary rule distinguishes *for you* from *instead of/on behalf of you*.
+With **WordOrder** enabled, English modal-plus-*always* supports
+*sempre pode ajudar* → *pode sempre ajudar*, including other modal persons.
+With **Vocabulary** and **Colloquial** enabled, explanatory English *the thing is*
+supports *O negócio é que…* → *A questão é que…*; literal business/object senses
+and unconfirmed introductions stay unchanged. A split introduction such as
+*O negócio é* becomes *A questão é*, leaving the following cue's *que* intact.
+
+Reference-confirmed informal subordinate verbs accept intervening negatives
+and adverbs: *que não tem* → *que não tens*, *que ainda pode* → *que ainda podes*.
+Repeated English *you're* subjects also confirm coordinated agreement after
+reply prefixes: *Certo, você é adulto e é o diretor* →
+*Certo, és adulto e és o diretor*. Copular titles such as *é o diretor* describe
+a role rather than establish formal address; genuine title vocatives remain formal.
+
+With **Pronouns** enabled, English-confirmed family subjects receive definite
+articles: *Mãe e tio Pedro* → *A Mãe e o tio Pedro*, *Pai está acordado* →
+*O Pai está acordado*. Existing capitalization is preserved; this rule does
+not impose capital letters on ordinary family nouns. Vocatives such as *Mãe!*
+or *Mãe, ajuda!* remain article-free.
+With **Pronouns** and informal **YouToTu** enabled, reviewed English past
+actions with object *you* distinguish it from a third-person Portuguese
+clitic: *o encontraram e o levaram* → *encontraram-te e levaram-te* for
+*found you and got you to the hospital*. Verb tense and subject number remain
+unchanged. The rule covers finding, seeing, helping, calling, transporting,
+rescuing and saving, with negative/subordinate triggers retaining proclisis.
+Formal address, ambiguous mixed objects and missing references are protected.
+English *brought you* can also select *trazer* with **Vocabulary** enabled;
+*got/took you to* retains *levar*. Whether the destination is the speaker's
+current location requires scene knowledge: edit to *trouxeram-te* when appropriate,
+rather than automatically changing every *levar*.
+
+Conversational recall uses the reflexive construction in *Tudo o que lembro*
+→ *Tudo o que me lembro*, including other recognized persons and split
+subtitle lines. Direct reminder uses such as *o que lembro ao João* remain
+unchanged. With **Colloquial**, **Pronouns** and **Vocabulary** enabled,
+English-confirmed departure slang *mandando-se daqui* becomes *saindo daqui*;
+finite forms such as *mandou-se daqui* become *saiu daqui*. Literal sending
+is preserved. Standalone departure commands with **Colloquial** and
+**Pronouns** use *Vá-se / Vai-te / Vão-se*, preserving formal/informal
+agreement; narrative *ele vai embora* and inclusive *vamos embora* stay unchanged.
+English-confirmed coordinated topic questions can put the subject first:
+*Sobre o que você e João discutiram?* →
+*Você e o João discutiram sobre o quê?* in formal address. Informal address
+retains *tu*. Name articles use the reviewed name genders, not capitalization alone.
+English *Are you sure…?* supports *Tem/Tens a certeza de que…*.
+In *want me to…*, a recoverable embedded *eu* can be omitted without changing
+its first-person verb: *Tem a certeza de que não quer que desembale aquilo?*.
+Ellipsis vocatives such as *-Detetive…* establish formal register even when
+another speaker's line follows in the same cue; existing timing, speaker,
+informal-boundary and title-apposition guards remain.
+
+With **Vocabulary** enabled, English *responded to the scene/club/incident*
+selects emergency-response *responder* rather than *atender*, across supported
+verb forms: *quando atenderam no clube* → *quando responderam no clube*.
+Telephone answering and replies to letters are not included.
+English *went into arrest* or explicit *cardiac arrest* selects the medical
+sense: *teve uma parada* → *teve uma paragem cardíaca*. Explicit
+*cardiorespiratory arrest* uses *paragem cardiorrespiratória*. Journey stops,
+police arrests and unconfirmed bare *parada* do not trigger this expansion.
+The aligned medical phrase is sufficient; earlier deaths alone are not used
+to infer the diagnosis. MiniLM compares individual cue meanings and flags
+possible mismatches; its similarity score cannot guarantee recognition of
+an implicit diagnosis and it does not generate a correction.
+
+With **Vocabulary** and **Colloquial** enabled, reference-confirmed
+*slow day/morning/afternoon/night/week/month* repairs an adverb incorrectly
+used as an adjective: *dia devagar* → *dia lento*, *manhã devagar* →
+*manhã lenta*, including plurals. This repairs a source translation error,
+not a general PT-BR/PT-PT difference. Valid adverbial uses such as
+*o dia passou devagar* and cases without a matching reference are preserved.
+
+Informal address also covers shared predicates, not just the first verb:
+*Você é péssimo pescando, odeia golfe* → *És péssimo a pescar, odeias golfe*.
+Known verb forms share the subject across commas and *e/mas/ou* when the
+English reference confirms repeated **you** subjects. Without a reference,
+the rule is limited to copular heads; named subjects, pronouns and subordinate
+clauses block propagation. It does not infer a subject for unrelated cues.
+An English-confirmed *if you … you …* clause can repair omitted subjects in
+*se dissesse que iria sair* → *se dissesses que irias sair*, without changing a
+different person's verb elsewhere in the sentence. This works within an
+individual cue, including a sentence continuation, without enabling sentence
+context. Both repairs respect formal address and the `address` rule switch.
+
+Reference-confirmed dropped subjects also work after introductory adverbs:
+*Bem, talvez precise do trabalho* → *Bem, talvez precises do trabalho*.
+Conditional and temporal heads use the same verb tools, including irregular
+forms: *E se precisar de ajuda* → *E se precisares de ajuda*, *Quando tiver
+tempo* → *Quando tiveres tempo*. These changes require an aligned English
+**you** subject and informal address; unknown or explicitly different subjects
+stay unchanged. *O trabalho não precisa de mim* keeps **precisa**, because the
+subject is the job, not the person being addressed.
+
+With **You → tu**, **Pronouns** and **Colloquial** enabled, an explicit informal
+subject plus an untriggered direct-object conditional uses a spoken infinitive
+construction: *Você a deixaria* → *Irias deixá-la*, *Você o faria* →
+*Irias fazê-lo*. Regular and supported irregular transitive verbs use the
+shared clitic allomorph helper. Negative/subordinate proclisis and other clitic
+types keep their existing treatment; this is not a blanket replacement of
+conditionals or a change of tense.
+
+With **Pronouns** enabled, complements of *pensar* use the normal spoken PT-PT
+article before reviewed, capitalized personal names: *pensar em Maria* →
+*pensar na Maria*, *penso em João* → *penso no João*. The rule covers conjugated
+forms, infinitives, participles and coordinated complements, preserving line
+breaks. It does not guess gender from spelling: unknown names, places and
+generic complements such as *pensar em alguém*, *pensar em pessoas* and
+*pensar em sair* remain unchanged. It does not change other verbs' complements.
+The rule ID is `personal-name-complements`.
+
+Linked infinitives after *mas*, as well as *e*, *ou*, *depois* and *a*, use
+the shared pronoun-placement rule: *mas me derrubar* → *mas derrubar-me*,
+*mas te proteger* → *mas proteger-te*, *a se vestir* → *a vestir-se*.
+This is not a verb-specific replacement: regular, irregular and personal
+infinitives use the existing verb tools and dictionary validation, including
+*pôr/compor*. Negation and intervening attracting words are not removed
+(*mas não me derrubar* stays). The **Pronoun placement** option controls it.
+Ambiguous *e/ou/mas se + infinitive* is not treated as a reflexive clitic:
+conditional **se** stays before the verb, even without a reference or with
+address conversion disabled. For example, *E se precisar* never becomes
+*E precisar-se*. Clear reflexives after *a/depois* retain the shared placement
+rule; an ambiguous coordinated reflexive may need manual review.
+
 **Dialogue context and subtitle layout** exposes the same settings in preview and
 Batch convert:
 
@@ -1126,6 +1291,12 @@ names differ after the episode code (`Show.S01E01.720p.pt-br.srt` and
   *esse/essa/nesse/dessa...* become *isto/disto/nisto*, *este/esta/neste/desta...* (*I got this
   one.* → *Eu cuido disto*; *this morning* → *esta manhã*). *Por isso* and the verb *desse*
   (from *dar*) are kept. Without a reference, *isso/esse* are never changed.
+- Referential English *that* also supports *isto/disto/nisto* →
+  *isso/disso/nisso*: *Sim, isto funcionaria?* → *Sim, isso funcionaria?*
+  for *Yeah, would that work?*. This direction uses reviewed pronoun
+  constructions, not conjunction *that* or noun determiners, and is suppressed
+  when the English cue also contains *this/these/those*. Without a reference,
+  no distal reading is inferred.
 - **Lines to review:** when the English says *you/your* but the line still has *você* or
   *seu/sua*, it is listed with an orange note (*Keep it if formal; change to tu/teu if informal*).
   Nothing is changed automatically; edit the line directly in the plugin if needed,
diff --git a/se5/BrazilianToEuropeanPortuguese/README.pt-PT.md b/se5/BrazilianToEuropeanPortuguese/README.pt-PT.md
index f1723c4c..f068c6fa 100644
--- a/se5/BrazilianToEuropeanPortuguese/README.pt-PT.md
+++ b/se5/BrazilianToEuropeanPortuguese/README.pt-PT.md
@@ -22,10 +22,16 @@ partir de folhas de cálculo, vestuário desportivo e expressões de residência
 confirmados pelo inglês, descrições físicas, filtros de resultados e o texto
 original em PT-BR visível nas linhas que contêm apenas avisos.
 
-A compilação do repositório autónomo é a **1.4.1**. As versões oficiais dos
+A compilação do repositório autónomo é a **1.4.2**. As versões oficiais dos
 pacotes do Subtitle Edit são atribuídas pelo fluxo de lançamento do projeto
 principal e podem ser diferentes.
 
+A versão **1.4.2** melhora a concordância contextual e o tratamento formal,
+os complementos e sentidos de vocabulário confirmados pela referência, as
+construções naturais de diálogo e a acentuação interrogativa. Acrescenta testes
+de regressão destas interações, preservando os tempos das legendas e as opções
+de regras controladas pelo utilizador.
+
 Converte legendas de português do Brasil (pt-BR) para português europeu (pt-PT),
 adaptando a ortografia, o vocabulário e a gramática. Tal como o plugin *American to
 British*, apresenta uma pré-visualização de cada legenda alterada, com uma caixa
@@ -344,6 +350,12 @@ incluindo para **Batch convert**. As definições já guardadas continuam a usar
 ortografia pós-acordo. Desativar **Spelling** também desativa a conversão entre
 ortografias.
 
+Com **Spelling** ativado, o *que* interrogativo imediatamente antes do ponto
+de interrogação recebe acento tónico: *Baseada em que?* → *Baseada em quê?*,
+*De que?* → *De quê?*. A regra aplica-se nas duas ortografias, preserva
+maiúsculas e marcação das legendas e mantém *Que queres?* e *em que ideia?*.
+Esta regra de acentuação não exige referência inglesa nem pontuação do MiniLM.
+
 Os radicais ortográficos revistos são verificados no dicionário de português
 europeu selecionado antes de se propor uma alteração. Por exemplo:
 
@@ -1092,6 +1104,177 @@ pré-visualização**.
 
 ## Contexto do diálogo, revisão gramatical e disposição das linhas
 
+Uma pergunta curta sobre uma ação pode perguntar o que **a própria pessoa**
+deve fazer, em vez de dar uma instrução. Com **You → tu** e **Colloquial**
+ativados, uma pergunta inglesa explícita como *Should I call …?*, ou
+*Call …?* logo após uma fala inglesa com *if you …* terminada em reticências,
+permite *Ligue para João?* → *Ligo para o João?*. Neste segundo caso, as
+falas têm de ser adjacentes e estar separadas por, no máximo, 1,5 segundos.
+As ações reconhecidas também incluem perguntar, dizer, esperar, ir e ajudar.
+Com **Pronouns** ativado, as perguntas sobre telefonar acrescentam artigos
+antes de nomes de pessoas revistos; os nomes desconhecidos mantêm a escolha
+de artigo original. Esta regra (`action-questions`) não altera ordens ou
+perguntas genuínas nem o tratamento formal. A leitura masculina revista do
+nome unissexo *Jamie* limita-se a estas perguntas sobre telefonar.
+
+As respostas iniciadas por *Não.* ou *Sim.* deixam de ocultar um sujeito
+**you** confirmado pela referência inglesa. *Não. Disse que iria me ajudar*
+pode passar a *Não. Disseste que me ias ajudar* quando o inglês diz
+*you said you were going to help me*; *would help* mantém o condicional
+*me irias ajudar*. Os pronomes com verbos modais após *que* usam a regra
+existente de próclise. Um nome com título num aposto identificativo, como
+*É a minha amiga, Sra. Silva*, não impõe tratamento formal às falas próximas;
+os vocativos genuínos e o tratamento direto continuam a ser formais.
+
+Com **Vocabulary** ativado, as paragens durante um percurso confirmadas pela
+referência usam *paragem/paragens*: *É uma pequena parada* →
+*É uma pequena paragem*. A etapa `travel-stops` ocorre antes do tratamento
+coloquial *parada* → *coisa*, para não perder o sentido de paragem.
+Os sentidos desconhecidos, os adjetivos que indicam imobilidade e as paradas
+militares não são substituídos indiscriminadamente; o vocabulário médico
+existente continua a usar as suas próprias regras.
+
+As construções confirmadas pela referência também abrangem perguntas
+independentes sobre acontecimentos (*O que está havendo?* →
+*O que se está a passar?*), *espera até* informal com infinitivo pessoal
+(*Espera até ver* → *Espera até veres*) e destinatários de aquisições
+(*Alugamos por ti* → *Alugamos para ti*). Esta última regra distingue
+*for you* de *instead of/on behalf of you*.
+Com **WordOrder** ativado, um verbo modal seguido de *always* na referência
+permite *sempre pode ajudar* → *pode sempre ajudar*, também noutras pessoas.
+Com **Vocabulary** e **Colloquial** ativados, a introdução explicativa inglesa
+*the thing is* permite *O negócio é que…* → *A questão é que…*. Os sentidos
+literais de negócio ou objeto e as introduções sem confirmação são preservados.
+Se a introdução terminar em *O negócio é*, passa a *A questão é*, mantendo
+o *que* da legenda seguinte.
+
+Os verbos subordinados informais confirmados pela referência aceitam negativas
+e advérbios intermédios: *que não tem* → *que não tens*, *que ainda pode* →
+*que ainda podes*. Sujeitos *you're* repetidos também confirmam a concordância
+coordenada após introduções de resposta: *Certo, você é adulto e é o diretor* →
+*Certo, és adulto e és o diretor*. Títulos predicativos como *é o diretor*
+descrevem uma função, sem impor tratamento formal; os verdadeiros vocativos
+com títulos continuam a ser formais.
+
+Com **Pronouns** ativado, os sujeitos familiares confirmados pelo inglês
+recebem artigo definido: *Mãe e tio Pedro* → *A Mãe e o tio Pedro*,
+*Pai está acordado* → *O Pai está acordado*. Preservam-se as maiúsculas
+existentes; a regra não impõe maiúsculas nos nomes de parentesco comuns.
+Os vocativos *Mãe!* e *Mãe, ajuda!* continuam sem artigo.
+Com **Pronouns** e **YouToTu** informal ativados, ações inglesas no passado
+com complemento *you* distinguem-no de um clítico português de terceira pessoa:
+*o encontraram e o levaram* → *encontraram-te e levaram-te* quando a referência
+diz *found you and got you to the hospital*. Mantêm-se o tempo verbal e o
+número do sujeito. A regra abrange encontrar, ver, ajudar, chamar, transportar,
+resgatar e salvar; negativas e subordinadas mantêm a próclise.
+O tratamento formal, complementos mistos ambíguos e casos sem referência
+são preservados. *Brought you* também pode selecionar *trazer*, com
+**Vocabulary** ativado; *got/took you to* mantém *levar*.
+Saber se o destino coincide com o local atual do falante exige conhecimento
+da cena: nesses casos, edite para *trouxeram-te*, em vez de substituir
+automaticamente todos os usos de *levar*.
+
+A recordação em registo conversacional usa a construção reflexa em
+*Tudo o que lembro* → *Tudo o que me lembro*, também noutras pessoas
+reconhecidas e em legendas repartidas por linhas. Mantêm-se usos de lembrar
+algo a alguém, como *o que lembro ao João*. Com **Colloquial**, **Pronouns**
+e **Vocabulary** ativados, a referência inglesa que confirma uma saída
+permite *mandando-se daqui* → *saindo daqui* e *mandou-se daqui* →
+*saiu daqui*. Mantêm-se os sentidos literais de enviar.
+Ordens de saída independentes, com **Colloquial** e **Pronouns** ativados,
+usam *Vá-se / Vai-te / Vão-se*, preservando o tratamento formal ou informal;
+mantêm-se *ele vai embora* narrativo e *vamos embora* inclusivo.
+Perguntas sobre um assunto com sujeito coordenado, confirmadas pelo inglês,
+podem colocar o sujeito primeiro: *Sobre o que você e João discutiram?* →
+*Você e o João discutiram sobre o quê?* no tratamento formal.
+No informal mantém-se *tu*. Os artigos dos nomes usam géneros revistos,
+não apenas a presença de maiúscula.
+*Are you sure…?* permite *Tem/Tens a certeza de que…*.
+Em *want me to…*, pode omitir-se o *eu* subordinado recuperável, sem mudar
+a pessoa verbal: *Tem a certeza de que não quer que desembale aquilo?*.
+Vocativos com reticências, como *-Detetive…*, estabelecem tratamento formal
+mesmo com uma segunda fala na mesma legenda; mantêm-se os limites temporais,
+as mudanças de falante, as fronteiras informais e as proteções das aposições.
+
+Com **Vocabulary** ativado, *responded to the scene/club/incident* na referência
+seleciona *responder* no sentido de responder a uma ocorrência, em vez de
+*atender*, nas formas verbais reconhecidas: *quando atenderam no clube* →
+*quando responderam no clube*. Atender o telefone ou responder a cartas
+não faz parte desta regra.
+*Went into arrest* ou *cardiac arrest* na referência seleciona o sentido
+médico: *teve uma parada* → *teve uma paragem cardíaca*.
+*Cardiorespiratory arrest* explícito usa *paragem cardiorrespiratória*.
+As paragens de percurso, as detenções policiais e *parada* sem confirmação
+não desencadeiam esta expansão. A expressão médica alinhada é suficiente;
+a menção anterior a mortes não serve, por si só, para deduzir o diagnóstico.
+O MiniLM compara o sentido de cada legenda e assinala possíveis divergências;
+a pontuação de semelhança não garante o reconhecimento de um diagnóstico
+implícito e não gera uma correção.
+
+Com **Vocabulary** e **Colloquial** ativados, *slow day/morning/afternoon/night/
+week/month* confirmado pela referência corrige o advérbio usado indevidamente
+como adjetivo: *dia devagar* → *dia lento*, *manhã devagar* → *manhã lenta*,
+também no plural. Trata-se de corrigir um erro da tradução de origem, não
+de uma diferença geral entre PT-BR e PT-PT. Mantêm-se usos adverbiais válidos
+como *o dia passou devagar* e casos sem referência correspondente.
+
+O tratamento informal também abrange predicados com sujeito partilhado, não
+apenas o primeiro verbo: *Você é péssimo pescando, odeia golfe* →
+*És péssimo a pescar, odeias golfe*. As formas verbais reconhecidas partilham
+o sujeito após vírgulas e *e/mas/ou* quando a referência inglesa confirma
+sujeitos **you** repetidos. Sem referência, a regra limita-se a verbos de
+ligação no início; nomes próprios, pronomes e orações subordinadas impedem
+a propagação. Não deduz o sujeito de falas independentes.
+Uma oração inglesa com *if you … you …* pode corrigir sujeitos omitidos em
+*se dissesse que iria sair* → *se dissesses que irias sair*, sem alterar o
+verbo de outra pessoa na mesma frase. Funciona numa fala individual, incluindo
+a continuação de uma frase, sem ativar o contexto entre falas. Ambas as
+correções respeitam o tratamento formal e o interruptor da regra `address`.
+
+Os sujeitos omitidos confirmados pela referência também são reconhecidos após
+advérbios introdutórios: *Bem, talvez precise do trabalho* →
+*Bem, talvez precises do trabalho*. As orações condicionais e temporais usam
+as mesmas ferramentas verbais, incluindo formas irregulares:
+*E se precisar de ajuda* → *E se precisares de ajuda*, *Quando tiver tempo* →
+*Quando tiveres tempo*. Estas alterações exigem um sujeito **you** na referência
+inglesa alinhada e tratamento informal; sujeitos desconhecidos ou explicitamente
+diferentes ficam inalterados. *O trabalho não precisa de mim* mantém
+**precisa**, porque o sujeito é o trabalho, não a pessoa a quem se fala.
+
+Com **You → tu**, **Pronouns** e **Colloquial** ativados, um sujeito informal
+explícito com um pronome de complemento direto antes de um condicional, sem
+elementos que exijam próclise, usa uma construção falada com infinitivo:
+*Você a deixaria* → *Irias deixá-la*, *Você o faria* → *Irias fazê-lo*.
+Os verbos transitivos regulares e irregulares reconhecidos usam a ferramenta
+partilhada de junção dos pronomes. A próclise negativa ou subordinada e os
+outros tipos de pronome mantêm o tratamento existente; não é uma substituição
+indiscriminada dos condicionais nem uma mudança de tempo verbal.
+
+Com **Pronouns** ativado, os complementos de *pensar* usam o artigo habitual
+no PT-PT falado antes de nomes próprios de pessoas revistos e escritos com
+inicial maiúscula: *pensar em Maria* → *pensar na Maria*, *penso em João* →
+*penso no João*. A regra abrange formas conjugadas, infinitivos, particípios
+e complementos coordenados, preservando as quebras de linha. Não deduz o
+género pela grafia: nomes desconhecidos, lugares e complementos genéricos
+como *pensar em alguém*, *pensar em pessoas* e *pensar em sair* ficam
+inalterados. Não altera os complementos de outros verbos.
+O identificador da regra é `personal-name-complements`.
+
+Os infinitivos ligados por *mas*, tal como por *e*, *ou*, *depois* e *a*,
+usam a regra partilhada de colocação dos pronomes:
+*mas me derrubar* → *mas derrubar-me*, *mas te proteger* → *mas proteger-te*,
+*a se vestir* → *a vestir-se*. Não é uma substituição específica de um verbo:
+os infinitivos regulares, irregulares e pessoais usam as ferramentas verbais
+existentes e a validação pelo dicionário, incluindo *pôr/compor*.
+Não se removem a negação nem palavras intercaladas que atraem o pronome
+(*mas não me derrubar* mantém-se). A opção **Pronoun placement** controla a regra.
+As construções ambíguas *e/ou/mas se + infinitivo* não são tratadas como pronomes
+reflexos: a conjunção condicional **se** mantém-se antes do verbo, mesmo sem
+referência ou com a conversão do tratamento desativada. Por exemplo,
+*E se precisar* nunca passa a *E precisar-se*. Os reflexos claros após
+*a/depois* mantêm a regra partilhada de colocação; um reflexo coordenado
+ambíguo pode precisar de revisão manual.
+
 **Dialogue context and subtitle layout** disponibiliza as mesmas definições
 na pré-visualização e em **Batch convert**:
 
@@ -1382,6 +1565,12 @@ local models (optional)** e use **Browse…** para o escolher:
   e *este/esta/neste/desta...* (*I got this one.* → *Eu cuido disto*;
   *this morning* → *esta manhã*). Mantêm-se *Por isso* e o verbo *desse*
   (de *dar*). Sem referência, *isso/esse* nunca são alterados.
+- O pronome referencial inglês *that* também permite *isto/disto/nisto* →
+  *isso/disso/nisso*: *Sim, isto funcionaria?* → *Sim, isso funcionaria?*
+  quando a referência diz *Yeah, would that work?*. Esta direção usa
+  construções pronominais reconhecidas, não a conjunção *that* nem
+  determinantes de nomes, e fica desativada se a mesma legenda inglesa
+  também contiver *this/these/those*. Sem referência, não se deduz esta leitura.
 - **Linhas a rever:** quando o inglês diz *you/your*, mas a legenda ainda
   contém *você* ou *seu/sua*, esta é listada com uma nota laranja:
   manter no tratamento formal; mudar para *tu/teu* no informal.
diff --git a/se5/BrazilianToEuropeanPortuguese/ReferenceSubtitle.cs b/se5/BrazilianToEuropeanPortuguese/ReferenceSubtitle.cs
index 40f7affe..28c6a5a7 100644
--- a/se5/BrazilianToEuropeanPortuguese/ReferenceSubtitle.cs
+++ b/se5/BrazilianToEuropeanPortuguese/ReferenceSubtitle.cs
@@ -17,6 +17,9 @@ public sealed record ReferenceHint(bool SecondPerson, bool SecondPossessive, boo
     public bool InclusiveContinuation { get; init; }
     public bool CatchUpLater { get; init; }
     internal bool RepeatedYouSubject { get; init; }
+    internal bool RepeatedConditionalYouSubject { get; init; }
+    internal bool ConditionalYouSubject { get; init; }
+    internal bool SelfActionQuestion { get; init; }
     internal bool SecondPersonWhatSubject { get; init; }
     internal string? EnglishText { get; init; }
 
@@ -24,6 +27,14 @@ public sealed record ReferenceHint(bool SecondPerson, bool SecondPossessive, boo
         IReadOnlyList? speakers = null)
     {
         var hint = FromText(english?[index]);
+        if (hint != null && index > 0 && english != null &&
+            InitialConditionalYouSubject.IsMatch(ReferenceAligner.Clean(english[index - 1] ?? "")) &&
+            source[index].StartMs >= source[index - 1].StartMs &&
+            source[index].StartMs - source[index - 1].EndMs <= 1500 &&
+            Regex.IsMatch(source[index - 1].Text, @"(?:\.\.\.|…)\s*$") &&
+            Regex.IsMatch(hint.EnglishText ?? "", @"^(?:call|phone|ring|ask|tell|wait|leave|go|come|help|take|do)\b[^.!;]*\?$",
+                RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
+            hint = hint with { SelfActionQuestion = true };
         if (index > 0 && source[index].StartMs >= source[index - 1].StartMs &&
             source[index].StartMs - source[index - 1].EndMs <= 5000 &&
             !DialogueRegister.DifferentSpeaker(source[index - 1].Text, source[index].Text) &&
@@ -118,11 +129,23 @@ public sealed record ReferenceHint(bool SecondPerson, bool SecondPossessive, boo
         .OrderByDescending(word => word.Length)
         .Select(Regex.Escape));
     private static readonly Regex InitialYouSubject = new(
-        @"^\s*(?:(?:well|so|then|but|and|now|hey|look|listen|okay|ok|all\s+right)\b[,!?;:]?\s*)?(?:you\b|you['’](?:re|ll|ve|d)\b|y['’]all\b)",
+        @"^\s*(?:(?:no|yes)\s*[.,!]\s*)?" +
+        @"(?:(?:well|so|then|but|and|now|hey|look|listen|okay|ok|all\s+right)\b[,!?;:]?\s*)?" +
+        @"(?:(?:maybe|perhaps|probably|surely|certainly)\s+){0,2}(?:you\b|you['’](?:re|ll|ve|d)\b|y['’]all\b)",
+        RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
+    private static readonly Regex InitialConditionalYouSubject = new(
+        @"^\s*(?:(?:well|so|then|but|and|or)\b[,!?;:]?\s*)?" +
+        @"(?:(?:(?:what|even|only)\s+)?if|when|whenever|unless|as\s+soon\s+as)\s+" +
+        @"you(?=\s+(?!(?:and|or|all|both|guys)\b)\p{L})",
         RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
     private static readonly Regex YouSubject = new(
         @"(? pattern.Matches(text).Cast().Any(m =>
+    {
+        if (!NamedTitle.IsMatch(m.Value) || DirectAddressTail.IsMatch(text[(m.Index + m.Length)..])) return true;
+        var comma = m.Value.IndexOf(',');
+        var before = text[..(m.Index + (comma >= 0 ? comma + 1 : 0))];
+        return !PersonApposition.IsMatch(before);
+    });
 
     internal static bool IsFormal(IReadOnlyList source, int index, IReadOnlyList? english = null,
         IReadOnlyList? speakers = null)
@@ -239,8 +284,8 @@ internal static bool IsFormal(IReadOnlyList source, int index, IReadOn
         }
         return false;
 
-        bool Address(int i) => PortugueseAddress.IsMatch(Plain(source[i].Text)) ||
-                               english != null && EnglishAddress.IsMatch(Plain(english[i] ?? string.Empty));
+        bool Address(int i) => HasAddress(Plain(source[i].Text), PortugueseAddress) ||
+                               english != null && HasAddress(Plain(english[i] ?? string.Empty), EnglishAddress);
     }
 
     private static string Plain(string text) => Regex.Replace(text, @"<[^<>]*>|\{[^{}]*\}", " ");
@@ -258,7 +303,7 @@ private static string SpeakerOf(string text) =>
         Regex.Match(Plain(text), @"^\s*(?[\p{Lu}][\p{Lu}\d _'-]{1,35}):").Groups["name"].Value;
 
     internal static bool ConflictingAddress(string text) =>
-        PortugueseAddress.IsMatch(Plain(text)) && Informal.IsMatch(Plain(text));
+        HasAddress(Plain(text), PortugueseAddress) && Informal.IsMatch(Plain(text));
 }
 
 public sealed record TimeMap(double Scale, double OffsetMs)