diff --git a/src/contactLink.test.ts b/src/contactLink.test.ts
index 953a897..f3e1aa7 100644
--- a/src/contactLink.test.ts
+++ b/src/contactLink.test.ts
@@ -167,6 +167,27 @@ describe('contact link localization', () => {
}
})
+ it('renders Arabic right to left on both routes', async () => {
+ const title = 'افتح جهة الاتصال هذه في WitnessWork'
+ for (const path of ['/c', `/c/${PAYLOAD}`]) {
+ const response = await request(path, {
+ headers: { 'Accept-Language': 'ar-SA,ar;q=0.9,en;q=0.5' },
+ })
+ expect(response.status).toBe(200)
+ expect(response.headers.get('content-language')).toBe('ar')
+ const page = await response.text()
+ expect(page).toContain('')
+ expect(page).toContain(`
${title}`)
+ expect(page).toContain(`${title}
`)
+ expect(page).toContain('')
+ expect(page).toContain('>احصل على WitnessWork')
+ expect(page).toContain('>فتح التطبيق (مثبّت بالفعل)')
+ expect(ogUrl(page)).toBe(CANONICAL_URL)
+ expect(page.slice(0, page.indexOf(''))).not.toContain(PAYLOAD)
+ }
+ expect(await html('/c?lang=en-us')).toContain('')
+ })
+
it('translates instructions, buttons, descriptions and image labels', async () => {
const response = await request('/c', { headers: { 'Accept-Language': 'es-MX' } })
const page = await response.text()
@@ -199,6 +220,9 @@ describe('contact link localization', () => {
['zh-Hans-TW', 'zh-hans-cn'],
['zh-CN', 'zh-hans-cn'],
['zh', 'zh-hans-cn'],
+ ['ar', 'ar'],
+ ['ar-EG', 'ar'],
+ ['xx, AR-sa;q=0.6', 'ar'],
['*', 'en-us'],
['xx-XX', 'en-us'],
['constructor, __proto__', 'en-us'],
@@ -207,7 +231,7 @@ describe('contact link localization', () => {
headers: header ? { 'Accept-Language': header } : undefined,
})
expect(response.headers.get('content-language')).toBe(locale)
- expect(await response.text()).toContain(``)
+ expect(await response.text()).toContain(` {
diff --git a/src/contactLink.ts b/src/contactLink.ts
index 3163588..f8229a9 100644
--- a/src/contactLink.ts
+++ b/src/contactLink.ts
@@ -3,6 +3,7 @@ import { HTTP_STATUS } from './config'
import { apiError } from './errors'
import { nameTransactionAfterRoute } from './sentry'
import {
+ contactLinkDirection,
contactLinkTranslations,
resolveContactLinkLocale,
type ContactLinkLocale,
@@ -180,10 +181,12 @@ function renderFallbackPage(locale: ContactLinkLocale, openAppLink: string): str
const description = escapeHtml(messages.description)
const imageAlt = escapeHtml(messages.imageAlt)
// Open Graph uses language_TERRITORY rather than BCP 47 script subtags.
+ // Region-less Arabic becomes `ar_AR`, the code Open Graph uses for Arabic.
const [language] = locale.split('-')
const territory = locale.split('-').at(-1)!.toUpperCase()
+ const dir = contactLinkDirection(locale) === 'rtl' ? ' dir="rtl"' : ''
return `
-
+
diff --git a/src/contactLinkLocales.ts b/src/contactLinkLocales.ts
index 1336fbe..6d48978 100644
--- a/src/contactLinkLocales.ts
+++ b/src/contactLinkLocales.ts
@@ -153,11 +153,27 @@ export const contactLinkTranslations = {
openApp: 'Fungura porogaramu (isanzwe iri ku gikoresho)',
imageAlt: 'Agashushondanga ka porogaramu ya WitnessWork',
},
+ // One Arabic for every country, like the app (`ar` has no region).
+ 'ar': {
+ title: 'افتح جهة الاتصال هذه في WitnessWork',
+ description: 'تمت مشاركة جهة اتصال معك من WitnessWork — تطبيق إدارة وقت الخدمة وجهات الاتصال لشهود يهوه.',
+ instructions: 'ثبّت WitnessWork لاستيراد جهة الاتصال التي شوركت معك. إذا كان التطبيق مثبّتًا لديك، فمن المفترض أن يكون قد فُتح تلقائيًا.',
+ getApp: 'احصل على WitnessWork',
+ openApp: 'فتح التطبيق (مثبّت بالفعل)',
+ imageAlt: 'أيقونة تطبيق WitnessWork',
+ },
} satisfies Record
export type ContactLinkLocale = keyof typeof contactLinkTranslations
const DEFAULT_LOCALE: ContactLinkLocale = 'en-us'
+const RIGHT_TO_LEFT: ReadonlySet = new Set(['ar'])
+
+/** The page's text direction in `locale`. */
+export function contactLinkDirection(locale: ContactLinkLocale): 'ltr' | 'rtl' {
+ return RIGHT_TO_LEFT.has(locale) ? 'rtl' : 'ltr'
+}
+
function matchLocale(value: string): ContactLinkLocale | undefined {
const locale = value.trim().toLowerCase()
if (Object.hasOwn(contactLinkTranslations, locale)) {
@@ -174,7 +190,7 @@ function matchLocale(value: string): ContactLinkLocale | undefined {
: 'zh-hans-cn'
}
return (Object.keys(contactLinkTranslations) as ContactLinkLocale[])
- .find((key) => key.startsWith(`${language}-`))
+ .find((key) => key === language || key.startsWith(`${language}-`))
}
/** Explicit link language wins; otherwise use browser preferences, then English. */