mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(mental-health): stop sending Portuguese readers in Portugal to Brazil-only crisis lines
The app ships one Portuguese bundle, and pt-PT, pt-AO and bare pt all fold into pt-BR, so a stored pt-BR does not mean Brazil. A positive PHQ-9 item 9 still answered it with SAMU 192 as the emergency number and CVV 188 as the only line, neither of which connects from Portugal, under a comment claiming pt-PT kept the international set. With no region signal, Portuguese now gets the DE_AT_CH treatment: 112 first, the worldwide directory, then CVV and SAMU labelled Brazil and SNS 24 (808 24 24 24, option 4) and SOS Voz Amiga labelled Portugal. An explicit pt-PT tag gets the Portugal set and br the Brazil set.
This commit is contained in:
1 parent
5448a43651
commit
38941c55d8
11 files changed
+196
-30
No files matched your search
+10
-1
@@ -11064,7 +11064,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (rund um die Uhr, kostenlos, vertraulich)"
|
||||
"name": "CVV, Centro de Valorização da Vida (Brasilien, rund um die Uhr, kostenlos, vertraulich)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU, medizinischer Notruf (Brasilien)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24, psychologische Beratung (Portugal, rund um die Uhr, Option 4 wählen)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (Portugal, täglich 15:30 bis 0:30 Uhr)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
+10
-1
@@ -11074,7 +11074,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (24/7, free, confidential)"
|
||||
"name": "CVV, Centro de Valorização da Vida (Brazil, 24/7, free, confidential)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU medical emergency service (Brazil)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24 psychological counselling (Portugal, 24/7, choose option 4)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (Portugal, daily 3.30 pm to 12.30 am)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
+10
-1
@@ -11064,7 +11064,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (24/7, gratuito y confidencial)"
|
||||
"name": "CVV, Centro de Valorização da Vida (Brasil, 24/7, gratuito y confidencial)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU, urgencias médicas (Brasil)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24, asesoramiento psicológico (Portugal, 24/7, opción 4)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (Portugal, todos los días de 15:30 a 0:30)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
+10
-1
@@ -11064,7 +11064,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (24h/24, 7j/7, gratuit et confidentiel)"
|
||||
"name": "CVV, Centro de Valorização da Vida (Brésil, 24h/24, 7j/7, gratuit et confidentiel)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU, urgences médicales (Brésil)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24, soutien psychologique (Portugal, 24h/24, 7j/7, option 4)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (Portugal, tous les jours de 15h30 à 0h30)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
+10
-1
@@ -11064,7 +11064,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (24/7, gratuito e riservato)"
|
||||
"name": "CVV, Centro de Valorização da Vida (Brasile, 24/7, gratuito e riservato)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU, emergenza sanitaria (Brasile)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24, supporto psicologico (Portogallo, 24/7, opzione 4)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (Portogallo, tutti i giorni dalle 15:30 alle 0:30)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
+10
-1
@@ -11064,7 +11064,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (24시간 연중무휴, 무료, 비밀 보장)"
|
||||
"name": "CVV, Centro de Valorização da Vida (브라질, 24시간 연중무휴, 무료, 비밀 보장)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU 응급의료 서비스 (브라질)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24 심리 상담 (포르투갈, 24시간 연중무휴, 4번 선택)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (포르투갈, 매일 15:30~00:30)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
+10
-1
@@ -11022,7 +11022,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (dag en nacht, gratis, vertrouwelijk)"
|
||||
"name": "CVV, Centro de Valorização da Vida (Brazilië, dag en nacht, gratis, vertrouwelijk)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU, medische spoedhulp (Brazilië)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24, psychologische hulp (Portugal, dag en nacht, keuze 4)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (Portugal, dagelijks van 15.30 tot 0.30 uur)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
+10
-1
@@ -11064,7 +11064,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (całodobowo, bezpłatnie i poufnie)"
|
||||
"name": "CVV, Centro de Valorização da Vida (Brazylia, całodobowo, bezpłatnie i poufnie)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU, pogotowie ratunkowe (Brazylia)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24, wsparcie psychologiczne (Portugalia, całodobowo, opcja 4)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (Portugalia, codziennie od 15:30 do 0:30)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
+10
-1
@@ -11022,7 +11022,16 @@
|
||||
"name": "Crisis Text Line"
|
||||
},
|
||||
"cvv": {
|
||||
"name": "CVV, Centro de Valorização da Vida (24 horas, gratuito, sigiloso)"
|
||||
"name": "CVV, Centro de Valorização da Vida (Brasil, 24 horas, gratuito, sigiloso)"
|
||||
},
|
||||
"samu": {
|
||||
"name": "SAMU, urgência médica (Brasil)"
|
||||
},
|
||||
"sns24": {
|
||||
"name": "SNS 24, aconselhamento psicológico (Portugal, 24 horas, opção 4)"
|
||||
},
|
||||
"sosVozAmiga": {
|
||||
"name": "SOS Voz Amiga (Portugal, todos os dias das 15h30 às 0h30)"
|
||||
}
|
||||
},
|
||||
"instrumentDescription": {
|
||||
|
||||
@@ -6,8 +6,31 @@ import { getServerTranslator } from "@/lib/i18n/server-translator";
|
||||
import { crisisResourcesForLocale } from "../crisis-resources";
|
||||
|
||||
describe("crisisResourcesForLocale", () => {
|
||||
it("gives a Brazilian Portuguese reader CVV and SAMU", () => {
|
||||
const set = crisisResourcesForLocale("pt-BR");
|
||||
it("gives a Portuguese reader with no region 112 first, then each country's lines by name", () => {
|
||||
// The web stores the folded app locale: someone in Portugal reads the
|
||||
// pt-BR bundle too, so "pt-BR" is no evidence of Brazil.
|
||||
for (const locale of ["pt-BR", "pt_BR", "pt", "pt-AO"]) {
|
||||
const set = crisisResourcesForLocale(locale);
|
||||
expect(set.emergencyNumber, locale).toBe("112");
|
||||
expect(set.resources.map((r) => r.id)).toEqual([
|
||||
"findahelpline",
|
||||
"cvv",
|
||||
"samu",
|
||||
"sns24",
|
||||
"sosVozAmiga",
|
||||
]);
|
||||
}
|
||||
const { t } = getServerTranslator("pt-BR");
|
||||
expect(t("mentalHealth.crisisResource.cvv.name")).toContain("Brasil");
|
||||
expect(t("mentalHealth.crisisResource.samu.name")).toContain("Brasil");
|
||||
expect(t("mentalHealth.crisisResource.sns24.name")).toContain("Portugal");
|
||||
expect(t("mentalHealth.crisisResource.sosVozAmiga.name")).toContain(
|
||||
"Portugal",
|
||||
);
|
||||
});
|
||||
|
||||
it("gives an explicit Brazilian region CVV and SAMU", () => {
|
||||
const set = crisisResourcesForLocale("br");
|
||||
expect(set.emergencyNumber).toBe("192");
|
||||
expect(set.resources[0]).toEqual({
|
||||
id: "cvv",
|
||||
@@ -15,12 +38,35 @@ describe("crisisResourcesForLocale", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the international set for Portuguese outside Brazil", () => {
|
||||
// 188 does not connect from Portugal.
|
||||
for (const locale of ["pt", "pt-PT"]) {
|
||||
it("gives an explicit Portuguese region 112, SNS 24 and SOS Voz Amiga, never Brazil's lines", () => {
|
||||
for (const locale of ["pt-PT", "pt_PT", "pt-pt"]) {
|
||||
const set = crisisResourcesForLocale(locale);
|
||||
expect(set.emergencyNumber).toBe("112");
|
||||
expect(set.resources.map((r) => r.id)).not.toContain("cvv");
|
||||
expect(set.resources[0]).toEqual({
|
||||
id: "sns24",
|
||||
contacts: ["808 24 24 24"],
|
||||
});
|
||||
const ids = set.resources.map((r) => r.id);
|
||||
expect(ids).toContain("sosVozAmiga");
|
||||
expect(ids).not.toContain("cvv");
|
||||
expect(ids).not.toContain("samu");
|
||||
}
|
||||
});
|
||||
|
||||
it("names every crisis resource in every language", () => {
|
||||
// History re-derives the set from the row's stored locale and renders it
|
||||
// in whatever language the reader uses now.
|
||||
const ids = new Set(
|
||||
["pt-BR", "pt-PT", "br", "de", "de-AT", "de-CH", "en-US", "fr"].flatMap(
|
||||
(l) => crisisResourcesForLocale(l).resources.map((r) => r.id),
|
||||
),
|
||||
);
|
||||
for (const locale of locales) {
|
||||
const { t } = getServerTranslator(locale);
|
||||
for (const id of ids) {
|
||||
const key = `mentalHealth.crisisResource.${id}.name`;
|
||||
expect(t(key), `${locale} ${key}`).not.toBe(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -99,9 +99,10 @@ const US: CrisisResourceSet = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Brazil (pt-BR) — CVV by phone and chat, SAMU as the emergency number.
|
||||
* Both numbers are free and answer around the clock (cvv.org.br; the
|
||||
* Ministry of Health's SAMU 192 page).
|
||||
* Brazil (an explicit region signal: a bare "br" or "pt_BR"-style tag a
|
||||
* native client sends from the device region) — CVV by phone and chat, SAMU
|
||||
* as the emergency number. Both numbers are free and answer around the clock
|
||||
* (cvv.org.br; the Ministry of Health's SAMU 192 page).
|
||||
*/
|
||||
const BR: CrisisResourceSet = {
|
||||
emergencyNumber: "192",
|
||||
@@ -111,27 +112,65 @@ const BR: CrisisResourceSet = {
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Portugal (explicit "pt-PT" / "pt_PT" / "pt-pt"). 112 is the emergency
|
||||
* number. SNS 24 (808 24 24 24) runs the national psychological counselling
|
||||
* line around the clock as menu option 4 and transfers a call at risk to
|
||||
* INEM; SOS Voz Amiga answers daily from 15:30 to 00:30 (sosvozamiga.org).
|
||||
*/
|
||||
const PT: CrisisResourceSet = {
|
||||
emergencyNumber: "112",
|
||||
resources: [
|
||||
{ id: "sns24", contacts: ["808 24 24 24"] },
|
||||
{ id: "sosVozAmiga", contacts: ["213 544 545", "sosvozamiga.org"] },
|
||||
{ id: "findahelpline", contacts: ["findahelpline.com"] },
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Portuguese with no region signal. The app ships one Portuguese bundle,
|
||||
* `pt-BR`, and `localeFromLanguageTag` folds `pt-PT`, `pt-AO` and bare `pt`
|
||||
* into it, so a stored "pt-BR" says only that the reader reads Portuguese:
|
||||
* it is as likely to be someone in Lisbon as in São Paulo. The account keeps
|
||||
* no country to tell them apart. Following DE_AT_CH, the set leads with 112
|
||||
* (the emergency number across Portugal and the EU, which mobile networks in
|
||||
* Brazil also route to the police) and the worldwide directory, then names
|
||||
* every country's own lines with the country in the label, so nothing reads
|
||||
* as if it reached everyone.
|
||||
*/
|
||||
const PT_BR_AMBIGUOUS: CrisisResourceSet = {
|
||||
emergencyNumber: "112",
|
||||
resources: [
|
||||
{ id: "findahelpline", contacts: ["findahelpline.com"] },
|
||||
...BR.resources.filter((r) => r.id !== "findahelpline"),
|
||||
{ id: "samu", contacts: ["192"] },
|
||||
...PT.resources.filter((r) => r.id !== "findahelpline"),
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a crisis-resource set from the user's locale. Region detection is
|
||||
* coarse (locale prefix) and defaults to the international set — the goal is to
|
||||
* always surface SOMETHING actionable, never to be exhaustive.
|
||||
*
|
||||
* A region-qualified tag ("de-AT", "de_CH", …) resolves to the exact country
|
||||
* set when the caller has one to give us. `User.locale` today only ever
|
||||
* stores the bare short code, so in practice every "de" hits the DE_AT_CH
|
||||
* branch below — that is the safe default for an unqualified "de": never
|
||||
* assume Germany when the region is unknown.
|
||||
* A region-qualified tag ("de-AT", "de_CH", "pt-PT", …) resolves to the exact
|
||||
* country set when the caller has one to give us. The web stores the app
|
||||
* locale, which is a bare "de" or the folded "pt-BR", so it lands on the
|
||||
* combined sets: never assume Germany or Brazil when the region is unknown.
|
||||
*/
|
||||
export function crisisResourcesForLocale(
|
||||
locale: string | null | undefined,
|
||||
): CrisisResourceSet {
|
||||
const lc = (locale ?? "").toLowerCase();
|
||||
if (lc === "de-at" || lc === "de_at" || lc === "at") return AT;
|
||||
if (lc === "de-ch" || lc === "de_ch" || lc === "ch") return CH;
|
||||
const lc = (locale ?? "").toLowerCase().replace("_", "-");
|
||||
if (lc === "de-at" || lc === "at") return AT;
|
||||
if (lc === "de-ch" || lc === "ch") return CH;
|
||||
if (lc.startsWith("de")) return DE_AT_CH;
|
||||
if (lc === "en-us" || lc.startsWith("en_us") || lc === "us") return US;
|
||||
// Brazilian Portuguese only: CVV's 188 does not connect from Portugal, so a
|
||||
// bare "pt" or "pt-PT" keeps the international set.
|
||||
if (lc === "pt-br" || lc === "pt_br" || lc === "br") return BR;
|
||||
if (lc.startsWith("en-us") || lc === "us") return US;
|
||||
if (lc === "br") return BR;
|
||||
if (lc === "pt-pt") return PT;
|
||||
// "pt-BR" from the web is the folded app locale, not a region signal; see
|
||||
// PT_BR_AMBIGUOUS. Any other Portuguese tag (pt, pt-AO, pt-MZ) lands here
|
||||
// too, and still gets 112 and the worldwide directory first.
|
||||
if (lc === "pt" || lc.startsWith("pt-")) return PT_BR_AMBIGUOUS;
|
||||
return INTERNATIONAL;
|
||||
}
|
||||
Reference in new issue
Block a user