Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/(app)/[appName]/[objectName]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useLocalSearchParams, useRouter } from "expo-router";
import { Plus } from "lucide-react-native";
import { useClient, useQuery, useView } from "@objectstack/client-react";
import { useTranslation } from "react-i18next";
import { tCount } from "~/lib/i18n";
import { useCallback, useState } from "react";
import { ListViewRenderer } from "~/components/renderers";
import type { ListViewMeta } from "~/components/renderers";
Expand Down Expand Up @@ -84,7 +85,7 @@ export default function ObjectListScreen() {
const recordCount = records.length;
const countLabel = isLoading
? undefined
: t("records.recordCount", { count: recordCount });
: tCount("records.recordCount", recordCount);

return (
<SafeAreaView className="flex-1 bg-background" edges={["left", "right"]}>
Expand Down
3 changes: 2 additions & 1 deletion app/(tabs)/apps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ListSkeleton } from "~/components/ui/ListSkeleton";
import { useApps } from "~/hooks/useApps";
import { getIcon } from "~/lib/getIcon";
import { getUserErrorMessage } from "~/lib/error-handling";
import { tCount } from "~/lib/i18n";

export default function AppsScreen() {
const { apps, isLoading, error, refetch } = useApps();
Expand Down Expand Up @@ -46,7 +47,7 @@ export default function AppsScreen() {
<Text className="mt-1 text-sm text-muted-foreground">
{showSkeleton
? t("apps.loading")
: t("apps.installed", { count: apps.length })}
: tCount("apps.installed", apps.length)}
</Text>
</View>

Expand Down
5 changes: 3 additions & 2 deletions app/(tabs)/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { webContentMaxWidth } from "~/lib/responsive";
import { Search as SearchIcon, X, ChevronRight, FileText, SearchX } from "lucide-react-native";
import { useRouter } from "expo-router";
import { useTranslation } from "react-i18next";
import { tCount } from "~/lib/i18n";
import { Input } from "~/components/ui/Input";
import { EmptyState } from "~/components/ui/EmptyState";
import { useGlobalSearch } from "~/hooks/useGlobalSearch";
Expand Down Expand Up @@ -49,7 +50,7 @@ export default function SearchScreen() {
title={t("search.emptyTitle")}
description={
objectCount > 0
? t("search.lookingAcross", { count: objectCount })
? tCount("search.lookingAcross", objectCount)
: t("search.emptyHint")
}
/>
Expand All @@ -73,7 +74,7 @@ export default function SearchScreen() {
keyboardShouldPersistTaps="handled"
>
<Text className="mb-3 text-xs font-medium text-muted-foreground">
{t("search.resultCount", { count: totalCount })}
{tCount("search.resultCount", totalCount)}
</Text>
{groups.map((group) => (
<View key={`${group.appName}:${group.objectName}`} className="mb-5">
Expand Down
17 changes: 17 additions & 0 deletions lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,21 @@ i18n.use(initReactI18next).init({
compatibilityJSON: "v4",
});

/**
* Count-aware translation that does NOT depend on `Intl.PluralRules`.
*
* Hermes (React Native's JS engine) ships without `Intl.PluralRules`, and
* i18next's plural resolver relies on it — so on device every plural key falls
* back to the fallback language (e.g. "1 app installed" instead of the
* localized string) while non-plural keys localize fine. This picks the English
* one/other form manually and uses the single "other" form for the other
* locales; the strings interpolate `{{n}}` (named so i18next's own plural
* handling never triggers).
*/
export function tCount(baseKey: string, count: number): string {
const isEnglish = (i18n.language || "en").startsWith("en");
const suffix = isEnglish && count === 1 ? "_one" : "_other";
return i18n.t(`${baseKey}${suffix}`, { n: count });
}

export default i18n;
8 changes: 4 additions & 4 deletions locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"recordCount_two": "سجلان",
"recordCount_few": "{{count}} سجلات",
"recordCount_many": "{{count}} سجلاً",
"recordCount_other": "{{count}} سجل",
"recordCount_other": "{{n}} سجل",
"deleteConfirm": "هل أنت متأكد من أنك تريد حذف هذا السجل؟",
"deleteConfirmNamed": "هل أنت متأكد من أنك تريد حذف \"{{label}}\"؟",
"deleteFailed": "فشل حذف السجل.",
Expand Down Expand Up @@ -139,7 +139,7 @@
"apps": {
"title": "التطبيقات",
"loading": "جارٍ تحميل تطبيقاتك…",
"installed_other": "{{count}} تطبيق مثبّت",
"installed_other": "{{n}} تطبيق مثبّت",
"loadErrorTitle": "تعذّر تحميل التطبيقات",
"emptyTitle": "لا توجد تطبيقات",
"emptyDesc": "ستظهر تطبيقات مؤسستك هنا بمجرد تثبيتها.",
Expand Down Expand Up @@ -227,15 +227,15 @@
"lookingAcross_two": "يتم البحث في كائنين",
"lookingAcross_few": "يتم البحث في {{count}} كائنات",
"lookingAcross_many": "يتم البحث في {{count}} كائناً",
"lookingAcross_other": "يتم البحث في {{count}} كائن",
"lookingAcross_other": "يتم البحث في {{n}} كائن",
"noResultsTitle": "لا توجد نتائج",
"noResultsBody": "لا يوجد ما يطابق \"{{query}}\". جرّب كلمة مفتاحية أخرى.",
"resultCount_zero": "{{count}} نتيجة",
"resultCount_one": "نتيجة واحدة",
"resultCount_two": "نتيجتان",
"resultCount_few": "{{count}} نتائج",
"resultCount_many": "{{count}} نتيجة",
"resultCount_other": "{{count}} نتيجة",
"resultCount_other": "{{n}} نتيجة",
"openLabel": "فتح {{title}}"
},
"offline": {
Expand Down
16 changes: 8 additions & 8 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
"editRecord": "Edit Record",
"deleteRecord": "Delete Record",
"createNamed": "Create {{name}}",
"recordCount_one": "{{count}} record",
"recordCount_other": "{{count}} records",
"recordCount_one": "{{n}} record",
"recordCount_other": "{{n}} records",
"deleteConfirm": "Are you sure you want to delete this record?",
"deleteConfirmNamed": "Are you sure you want to delete \"{{label}}\"?",
"deleteFailed": "Failed to delete the record.",
Expand Down Expand Up @@ -135,8 +135,8 @@
"apps": {
"title": "Apps",
"loading": "Loading your apps…",
"installed_one": "{{count}} app installed",
"installed_other": "{{count}} apps installed",
"installed_one": "{{n}} app installed",
"installed_other": "{{n}} apps installed",
"loadErrorTitle": "Unable to Load Apps",
"emptyTitle": "No Apps",
"emptyDesc": "Your enterprise applications will appear here once installed.",
Expand Down Expand Up @@ -219,12 +219,12 @@
"clearLabel": "Clear search",
"emptyTitle": "Search across all your records",
"emptyHint": "Type to start searching",
"lookingAcross_one": "Looking across {{count}} object",
"lookingAcross_other": "Looking across {{count}} objects",
"lookingAcross_one": "Looking across {{n}} object",
"lookingAcross_other": "Looking across {{n}} objects",
"noResultsTitle": "No Results",
"noResultsBody": "Nothing matched “{{query}}”. Try a different keyword.",
"resultCount_one": "{{count}} result",
"resultCount_other": "{{count}} results",
"resultCount_one": "{{n}} result",
"resultCount_other": "{{n}} results",
"openLabel": "Open {{title}}"
},
"offline": {
Expand Down
8 changes: 4 additions & 4 deletions locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"editRecord": "编辑记录",
"deleteRecord": "删除记录",
"createNamed": "创建{{name}}",
"recordCount_other": "{{count}} 条记录",
"recordCount_other": "{{n}} 条记录",
"deleteConfirm": "确定要删除此记录吗?",
"deleteConfirmNamed": "确定要删除“{{label}}”吗?",
"deleteFailed": "删除记录失败。",
Expand Down Expand Up @@ -134,7 +134,7 @@
"apps": {
"title": "应用",
"loading": "正在加载你的应用…",
"installed_other": "已安装 {{count}} 个应用",
"installed_other": "已安装 {{n}} 个应用",
"loadErrorTitle": "无法加载应用",
"emptyTitle": "暂无应用",
"emptyDesc": "安装后,你的企业应用将显示在这里。",
Expand Down Expand Up @@ -217,10 +217,10 @@
"clearLabel": "清除搜索",
"emptyTitle": "搜索您的所有记录",
"emptyHint": "输入以开始搜索",
"lookingAcross_other": "正在 {{count}} 个对象中搜索",
"lookingAcross_other": "正在 {{n}} 个对象中搜索",
"noResultsTitle": "无结果",
"noResultsBody": "没有与“{{query}}”匹配的结果。请尝试其他关键词。",
"resultCount_other": "{{count}} 条结果",
"resultCount_other": "{{n}} 条结果",
"openLabel": "打开 {{title}}"
},
"offline": {
Expand Down
Loading