Repository: bradymholt/cRonstrue
Pull request #326 (2024-10-28 09:31:46)

[i18n] fix masculine and feminine forms of the weekdays in PT
Hello! This pull request addresses a grammatical issue in the Portuguese translation of weekdays and weekends.

In Portuguese, weekdays are grammatically feminine, while weekends are masculine. Currently, the output does not reflect this distinction, leading to incorrect gender usage in some translations.

Example
Given the following cron expression:

`45 10 * * 6#2`
(At 10:45 AM, on the second Saturday of the month)

Expected Output:
`"Às 10:45, no segundo sábado do mês"`

Actual Output:
`"Às 10:45, na segunda sábado do mês"`

This PR corrects the gender agreement for weekend days, ensuring accurate and grammatically correct translations.

Thank you for your consideration!

einavkor: @bradymholt 

Patch:
diff --git a/src/expressionDescriptor.ts b/src/expressionDescriptor.ts
index 45c9283..b9b5995 100644
--- a/src/expressionDescriptor.ts
+++ b/src/expressionDescriptor.ts
@@ -381,7 +381,8 @@ export class ExpressionDescriptor {
           let format: string | null = null;
           if (s.indexOf("#") > -1) {
             let dayOfWeekOfMonthNumber: string = s.substring(s.indexOf("#") + 1);
-            format = this.i18n.commaOnThe(dayOfWeekOfMonthNumber).trim() + this.i18n.spaceX0OfTheMonth();
+            let dayOfWeek = s.substring(0, s.indexOf("#"));
+            format = this.i18n.commaOnThe(dayOfWeekOfMonthNumber, dayOfWeek).trim() + this.i18n.spaceX0OfTheMonth();
           } else if (s.indexOf("L") > -1) {
             format = this.i18n.commaOnTheLastX0OfTheMonth(s.replace("L", ""));
           } else {
diff --git a/src/i18n/locale.ts b/src/i18n/locale.ts
index 49035c6..86018ba 100644
--- a/src/i18n/locale.ts
+++ b/src/i18n/locale.ts
@@ -37,7 +37,7 @@ export interface Locale {
   third(s?: string): string;
   fourth(s?: string): string;
   fifth(s?: string): string;
-  commaOnThe(s?: string): string;
+  commaOnThe(s?: string, day?: string): string;
   spaceX0OfTheMonth(): string;
   lastDay(): string;
   commaOnTheLastX0OfTheMonth(s?: string): string;
diff --git a/src/i18n/locales/pt_BR.ts b/src/i18n/locales/pt_BR.ts
index 5a1874e..bf69f19 100644
--- a/src/i18n/locales/pt_BR.ts
+++ b/src/i18n/locales/pt_BR.ts
@@ -65,8 +65,8 @@ export class pt_BR implements Locale {
   commaAndOnX0() {
     return ", e de %s";
   }
-  commaOnThe() {
-    return ", na ";
+  commaOnThe(s?: string, day?: string) {
+    return day === '6' || day === '0' ? ", no" : ", na ";
   }
   commaOnTheLastDayOfTheMonth() {
     return ", no último dia do mês";
@@ -110,23 +110,23 @@ export class pt_BR implements Locale {
   everyX0Seconds() {
     return "a cada %s segundos";
   }
-  fifth() {
-    return "quinta";
+  fifth(s?: string) {
+    return s === '6' || s === '0' ? "quinto" : "quinta";
   }
-  first() {
-    return "primeira";
+  first(s?: string) {
+    return s === '6' || s === '0' ? "primeiro" : "primeira";
   }
   firstWeekday() {
     return "primeiro dia da semana";
   }
-  fourth() {
-    return "quarta";
+  fourth(s?: string) {
+    return s === '6' || s === '0' ? "quarto" : "quarta";
   }
   minutesX0ThroughX1PastTheHour() {
     return "do minuto %s até %s de cada hora";
   }
-  second() {
-    return "segunda";
+  second(s?: string) {
+    return s === '6' || s === '0' ? "segundo" : "segunda";
   }
   secondsX0ThroughX1PastTheMinute() {
     return "No segundo %s até %s de cada minuto";
@@ -140,8 +140,8 @@ export class pt_BR implements Locale {
   lastDay() {
     return "o último dia";
   }
-  third() {
-    return "terceira";
+  third(s?: string) {
+    return s === '6' || s === '0' ? "terceiro" : "terceira";
   }
   weekdayNearestDayX0() {
     return "dia da semana mais próximo do dia %s";
diff --git a/src/i18n/locales/pt_PT.ts b/src/i18n/locales/pt_PT.ts
index 8a05ea9..b0320cd 100644
--- a/src/i18n/locales/pt_PT.ts
+++ b/src/i18n/locales/pt_PT.ts
@@ -65,8 +65,8 @@ export class pt_PT implements Locale {
   commaAndOnX0() {
     return ", e de %s";
   }
-  commaOnThe() {
-    return ", na ";
+  commaOnThe(s?: string, day?: string) {
+    return day === '6' || day === '0' ? ", no" : ", na ";
   }
   commaOnTheLastDayOfTheMonth() {
     return ", no último dia do mês";
@@ -110,23 +110,23 @@ export class pt_PT implements Locale {
   everyX0Seconds() {
     return "a cada %s segundos";
   }
-  fifth() {
-    return "quinta";
+  fifth(s?: string) {
+    return s === '6' || s === '0' ? "quinto" : "quinta";
   }
-  first() {
-    return "primeira";
+  first(s?: string) {
+    return s === '6' || s === '0' ? "primeiro" : "primeira";
   }
   firstWeekday() {
     return "primeiro dia da semana";
   }
-  fourth() {
-    return "quarta";
+  fourth(s?: string) {
+    return s === '6' || s === '0' ? "quarto" : "quarta";
   }
   minutesX0ThroughX1PastTheHour() {
     return "do minuto %s até %s de cada hora";
   }
-  second() {
-    return "segunda";
+  second(s?: string) {
+    return s === '6' || s === '0' ? "segundo" : "segunda";
   }
   secondsX0ThroughX1PastTheMinute() {
     return "No segundo %s até %s de cada minuto";
@@ -140,8 +140,8 @@ export class pt_PT implements Locale {
   lastDay() {
     return "o último dia";
   }
-  third() {
-    return "terceira";
+  third(s?: string) {
+    return s === '6' || s === '0' ? "terceiro" : "terceira";
   }
   weekdayNearestDayX0() {
     return "dia da semana mais próximo do dia %s";


Test patch:
diff --git a/test/i18n.ts b/test/i18n.ts
index b680e7b..47bce1e 100644
--- a/test/i18n.ts
+++ b/test/i18n.ts
@@ -181,6 +181,27 @@ describe("i18n", function () {
         "A cada 5 minutos, entre 15:00 e 15:59, somente de segunda-feira a sexta-feira e domingo"
       );
     });
+
+    it("45 10 * * 6#2", function () {
+      assert.equal(
+        cronstrue.toString(this.test?.title as string, { locale: "pt_BR" }),
+        "Às 10:45, no segundo sábado do mês"
+      );
+    });
+
+    it("45 10 * * 0#3", function () {
+      assert.equal(
+        cronstrue.toString(this.test?.title as string, { locale: "pt_BR" }),
+        "Às 10:45, no terceiro domingo do mês"
+      );
+    });
+
+    it("45 10 * * 1#3", function () {
+      assert.equal(
+        cronstrue.toString(this.test?.title as string, { locale: "pt_BR" }),
+        "Às 10:45, na terceira segunda-feira do mês"
+      );
+    });
   });
 
 
@@ -195,6 +216,28 @@ describe("i18n", function () {
         "A cada 5 minutos, entre 15:00 e 15:59, de segunda-feira a sexta-feira"
       );
     });
+
+
+    it("45 10 * * 6#2", function () {
+      assert.equal(
+        cronstrue.toString(this.test?.title as string, { locale: "pt_BR" }),
+        "Às 10:45, no segundo sábado do mês"
+      );
+    });
+
+    it("45 10 * * 0#3", function () {
+      assert.equal(
+        cronstrue.toString(this.test?.title as string, { locale: "pt_BR" }),
+        "Às 10:45, no terceiro domingo do mês"
+      );
+    });
+
+    it("45 10 * * 1#3", function () {
+      assert.equal(
+        cronstrue.toString(this.test?.title as string, { locale: "pt_BR" }),
+        "Às 10:45, na terceira segunda-feira do mês"
+      );
+    });
   });
 
   describe("ro", function () {