Compare commits

..

3 Commits

Author SHA1 Message Date
b588ce2355 Added i18n, and time units, v1.1.0 2025-07-30 18:42:10 +02:00
5e91df4529 Added translations 2025-07-30 16:59:58 +02:00
27bcd339ea dev i18n 2025-07-30 16:03:14 +02:00
27 changed files with 838 additions and 112 deletions

View File

@ -55,13 +55,13 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
# Use legacy packaging to compress native libraries in the resulting APK.
expo.useLegacyPackaging=false
# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin
expo.edgeToEdgeEnabled=true
android.packagingOptions.pickFirsts=**/libc++_shared.so
expo.sqlite.enableFTS=false
expo.sqlite.useSQLCipher=false
MYAPP_UPLOAD_STORE_FILE=taskeep.keystore
MYAPP_UPLOAD_KEY_ALIAS=taskeep
MYAPP_UPLOAD_STORE_PASSWORD=123?_Tk_?123
MYAPP_UPLOAD_KEY_PASSWORD=123?_Tk_?123
# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin
expo.edgeToEdgeEnabled=true
MYAPP_UPLOAD_KEY_ALIAS=taskeep
MYAPP_UPLOAD_KEY_PASSWORD=123?_Tk_?123

View File

@ -3,10 +3,12 @@ import React from 'react';
import { Platform } from 'react-native';
import Ionicons from '@expo/vector-icons/Ionicons';
import { useTranslation } from 'react-i18next';
import { HapticTab } from '../../components/HapticTab';
import TabBarBackground from '../../components/ui/TabBarBackground';
export default function TabLayout() {
const { t } = useTranslation();
return (
<Tabs
@ -27,14 +29,14 @@ export default function TabLayout() {
<Tabs.Screen
name="index"
options={{
title: 'Tasks',
title: t('Tasks'),
tabBarIcon: ({ color }) => <Ionicons size={28} name="document-text-outline" color={color} />,
}}
/>
<Tabs.Screen
name="categories"
options={{
title: 'Categories',
title: t('Categories'),
tabBarIcon: ({ color }) => <Ionicons size={28} name="albums-outline" color={color} />,
}}
/>

View File

@ -4,6 +4,7 @@ import Ionicons from '@expo/vector-icons/Ionicons';
import { useFocusEffect } from 'expo-router';
import { navigate } from 'expo-router/build/global-state/routing';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CategoryItem } from '../../components/categoryItem';
import { ThemedText } from '../../components/ThemedText';
import { ThemedView } from '../../components/ThemedView';
@ -17,6 +18,7 @@ const categoryRepository = new CategoryRepository(new SQLiteDataService<Category
const taskRepository = new TaskRepository(new SQLiteDataService<Task>(TaskQuery));
export default function CategoryScreen() {
const { t } = useTranslation();
const [categories, setCategories] = useState<Category[]>([]);
@ -42,14 +44,18 @@ export default function CategoryScreen() {
<ThemedView style={styles.headerContainer}>
<ThemedText type="title" style={{
color: '#fff',
}}>Taskeep!</ThemedText>
}}>
{t('app_name')}
</ThemedText>
<Pressable onPress={() => {
navigate('../categoryForm', {})
}}>
<Ionicons name='add-circle-sharp' size={32} color="#fff" />
</Pressable>
</ThemedView>
<ThemedText style={{ padding: 24 }} type="subtitle">Your Categories</ThemedText>
<ThemedText style={{ padding: 24 }} type="subtitle">
{t('your_categories')}
</ThemedText>
<ScrollView style={styles.scrollView}>
{categories.map((cat, index) => (
<CategoryItem categories={categories} key={index} category={cat} onUpdate={() => {

View File

@ -38,25 +38,25 @@ export default function HomeScreen() {
useEffect(() => { // Initial data fetch
(async () => {
console.log("Fetching categories...");
console.log(t('fetching_categories'));
try {
const fetchedCategories = await categoryRepository.findAll();
if (fetchedCategories.length === 0) {
// If no categories, create a default one
const defaultCategory = new Category("Uncategorized", "🤷‍♂️");
const defaultCategory = new Category(t('default_category_name'), t('default_category_icon'));
const createdCat = await categoryRepository.create(defaultCategory);
setCategories([createdCat]);
} else {
setCategories(fetchedCategories);
}
} catch (error) {
console.error("Error fetching categories:", error);
console.error(t('error_fetching_categories'), error);
}
try {
console.log("Fetching tasks...");
console.log(t('fetching_tasks'));
RefreshTasks();
} catch (error) {
console.error("Error fetching categories or tasks:", error);
console.error(t('error_fetching_tasks'), error);
}
setReady(true);
})();
@ -76,24 +76,23 @@ export default function HomeScreen() {
const fetchedTasks = await taskRepository.findAll();
fetchedTasks.sort((a, b) => {
return (a.daysToRedo! * 24 * 3600 * 1000 - (Date.now() - a.lastDone!)) - ((b.daysToRedo! * 24 * 3600 * 1000) - (Date.now() - b.lastDone!));
})
console.log("Tasks fetched:", fetchedTasks);
});
console.log(t('tasks_fetched'), fetchedTasks);
setTasks(fetchedTasks);
} catch (error) {
console.error("Error refreshing tasks:", error);
console.error(t('error_refreshing_tasks'), error);
}
}
};
const RefreshCategories = async () => {
try {
const fetchedCategories = await categoryRepository.findAll();
setCategories(fetchedCategories);
} catch (error) {
console.error("Error refreshing categories:", error);
console.error(t('error_refreshing_categories'), error);
}
}
};
useEffect(() => {
if (ready) {
@ -104,9 +103,9 @@ export default function HomeScreen() {
}
}, [ready]);
//On layout focus to refresh tasks
// On layout focus to refresh tasks
useFocusEffect(React.useCallback(() => {
console.log("focus");
console.log(t('focus_event'));
(async () => {
await RefreshTasks();
await RefreshCategories();
@ -116,18 +115,18 @@ export default function HomeScreen() {
return (
<ThemedView style={styles.mainContainer}>
<ThemedView style={styles.headerContainer}>
<ThemedText type="title" style={
{
color: '#fff',
}
}>Taskeep!</ThemedText>
<ThemedText type="title" style={{ color: '#fff' }}>
{t('app_name')}
</ThemedText>
<Pressable onPress={() => {
navigate('./taskForm', {})
navigate('./taskForm', {});
}}>
<Ionicons name='add-circle-sharp' size={32} color="#fff" />
</Pressable>
</ThemedView>
<ThemedText style={{ padding: 24 }} type="subtitle">Your Tasks</ThemedText>
<ThemedText style={{ padding: 24 }} type="subtitle">
{t('your_tasks')}
</ThemedText>
<ScrollView style={styles.scrollView}>
{tasks.map((task, index) => (
<TaskItem categories={categories} key={Date.now() + "-" + task.id} task={task} onUpdate={() => {
@ -136,10 +135,10 @@ export default function HomeScreen() {
))}
{tasks.length === 0 && (<>
<ThemedText type="defaultSemiBold" style={{ textAlign: 'center', marginTop: 20 }}>
No tasks available.
{t('no_tasks_available')}
</ThemedText>
<ThemedText type="default" style={{ textAlign: 'center', marginTop: 20 }}>
Add a task to get started!
{t('add_task_to_get_started')}
</ThemedText>
</>)}
<View style={{ height: 50 }} />

View File

@ -1,17 +1,20 @@
import { Link, Stack } from 'expo-router';
import { useTranslation } from 'react-i18next';
import { StyleSheet } from 'react-native';
import { ThemedText } from '../components/ThemedText';
import { ThemedView } from '../components/ThemedView';
export default function NotFoundScreen() {
const { t } = useTranslation();
return (
<>
<Stack.Screen options={{ title: 'Oops!' }} />
<Stack.Screen options={{ title: t('oops') }} />
<ThemedView style={styles.container}>
<ThemedText type="title">This screen does not exist.</ThemedText>
<ThemedText type="title">{t('screen_does_not_exist')}</ThemedText>
<Link href="/" style={styles.link}>
<ThemedText type="link">Go to home screen!</ThemedText>
<ThemedText type="link">{t('go_to_home_screen')}</ThemedText>
</Link>
</ThemedView>
</>

View File

@ -3,12 +3,8 @@ import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { registerWidgetTaskHandler } from 'react-native-android-widget';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import 'react-native-reanimated';
import { widgetTaskHandler } from './widgets/widget-task-handler';
registerWidgetTaskHandler(widgetTaskHandler);
import React from 'react';
import { useColorScheme } from '../hooks/useColorScheme';

View File

@ -2,6 +2,7 @@ import { toast } from '@backpackapp-io/react-native-toast';
import Ionicons from '@expo/vector-icons/Ionicons';
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Pressable, ScrollView, StyleSheet, TextInput, View } from 'react-native';
import { EmojiPopup } from 'react-native-emoji-popup';
import { ThemedView } from '../..//components/ThemedView';
@ -13,6 +14,7 @@ import { SQLiteDataService } from '../../services/data/sqliteDataService';
const categoryRepository = new CategoryRepository(new SQLiteDataService<Category>(CategoryQuery));
export default function CategoryForm() {
const { t } = useTranslation();
const { category: categoryJson } = useLocalSearchParams();
const category: Category = categoryJson ? JSON.parse(categoryJson as string) : null;
@ -30,7 +32,7 @@ export default function CategoryForm() {
<ThemedView style={styles.headerContainer}>
<ThemedText type="title" style={{
color: '#fff',
}}> Taskeep! </ThemedText>
}}> {t('app_name')} </ThemedText>
< Pressable onPress={() => {
router.back();
}
@ -39,16 +41,16 @@ export default function CategoryForm() {
</Pressable>
</ThemedView>
< ScrollView style={styles.scrollView} >
<ThemedText type="subtitle">Add a new category</ThemedText>
<ThemedText type="subtitle">{t('add_new_category')}</ThemedText>
<ThemedView style={styles.stepContainer}>
<EmojiPopup onEmojiSelected={emoji => setNewCategory({ ...newCategory, icon: emoji })} >
<ThemedView style={styles.addIconView}>
{newCategory.icon != "" ? <ThemedText>{newCategory.icon}</ThemedText> : <Ionicons name={newCategory.icon || "happy-outline"} size={24} color="#ccc" />}
<ThemedText>{newCategory.icon != "" ? "Change" : "Select"} icon *</ThemedText>
<ThemedText>{newCategory.icon != "" ? t('change_icon') : t('select_icon')} *</ThemedText>
</ThemedView>
</EmojiPopup>
<TextInput
placeholder="Category title *"
placeholder={t('category_title_placeholder')}
style={styles.inputText}
placeholderTextColor="#333"
value={newCategory.title}
@ -64,22 +66,22 @@ export default function CategoryForm() {
...category,
...newCategory
}).then(() => {
toast.success("Category updated successfully!");
toast.success(t('category_updated_successfully'));
router.back();
});
} else {
// Create new task
categoryRepository.create(new Category(newCategory.title, newCategory.icon)).then(() => {
toast.success("Category added successfully!");
toast.success(t('category_added_successfully'));
router.back();
});
}
} else {
toast.error("Please select an icon and enter a title for the category.");
toast.error(t('select_icon_and_enter_title'));
}
}}>
<Ionicons name='checkmark-circle-outline' size={24} color="#fff" />
<ThemedText style={styles.addButtonText}>{category?.id ? "Update" : "Add"} Category</ThemedText>
<ThemedText style={styles.addButtonText}>{category?.id ? t('update_category') : t('add_category')}</ThemedText>
</Pressable>
< View style={{ height: 50 }} />
</ScrollView >

View File

@ -2,6 +2,7 @@ import { toast } from '@backpackapp-io/react-native-toast';
import Ionicons from '@expo/vector-icons/Ionicons';
import { Stack, useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Pressable, ScrollView, StyleSheet, TextInput, View } from 'react-native';
import Dropdown from 'react-native-input-select';
import { ThemedText } from '../../components/ThemedText';
@ -45,6 +46,7 @@ const timeUnitToDays = (num: number, unit: string): number => {
}
export default function TasksForm() {
const { t } = useTranslation();
const { task: taskJson } = useLocalSearchParams();
const task: Task = taskJson ? JSON.parse(taskJson as string) : null;
@ -82,7 +84,7 @@ export default function TasksForm() {
useFocusEffect(React.useCallback(() => {
categoryRepository.findAll().then((categories) => {
categories.push(new Category("Create new category", "", 0));
categories.push(new Category(t("create_new_category"), "", 0));
setCategories(categories);
if (newTask.category === 0) {
setNewTask({ ...newTask, category: -1 });
@ -99,7 +101,9 @@ export default function TasksForm() {
<ThemedView style={styles.headerContainer}>
<ThemedText type="title" style={{
color: '#fff',
}}> Taskeep! </ThemedText>
}}>
{t('app_name')}
</ThemedText>
< Pressable onPress={() => {
router.back();
}
@ -108,10 +112,10 @@ export default function TasksForm() {
</Pressable>
</ThemedView>
< ScrollView style={styles.scrollView} >
<ThemedText type="subtitle">Add a new task</ThemedText>
<ThemedText type="subtitle">{t('add_new_task')}</ThemedText>
<ThemedView style={styles.stepContainer}>
<TextInput
placeholder="Task title *"
placeholder={t('task_title_placeholder')}
style={styles.inputText}
placeholderTextColor="#333"
value={newTask.title}
@ -119,7 +123,7 @@ export default function TasksForm() {
/>
<View style={{ flexDirection: 'row', gap: 8, height: 60 }}>
<TextInput
placeholder={`${timeUnits.find(unit => unit.value === selectedTimeUnit)?.label} after which task is due *`}
placeholder={`${t(timeUnits.find(unit => unit.value === selectedTimeUnit)?.label!)} ${t('after_which_task_is_due')}`}
style={styles.inputText}
placeholderTextColor="#333"
keyboardType='numeric'
@ -127,7 +131,12 @@ export default function TasksForm() {
onChange={(e) => setNewTask({ ...newTask, daysToRedo: e.nativeEvent.text.length > 0 ? parseInt(e.nativeEvent.text) : NaN })}
/>
<Dropdown
options={timeUnits}
options={timeUnits.map((tu) => {
return {
label: t(tu.label),
value: tu.value
}
})}
onValueChange={(value) => setSelectedTimeUnit(value?.toString() ?? "")}
selectedValue={selectedTimeUnit}
maxSelectableItems={1}
@ -159,7 +168,7 @@ export default function TasksForm() {
/>
</View>
<Dropdown
placeholder="Select category *"
placeholder={t('select_category')}
options={
categories.map(category => ({
label: `${category.icon} ${category.title}`,
@ -204,22 +213,22 @@ export default function TasksForm() {
...task,
...newTask
}).then(() => {
toast.success("Task updated successfully!");
toast.success(t('task_updated_successfully'));
router.back();
});
} else {
// Create new task
taskRepository.create(new Task(newTask.title, newTask.daysToRedo, newTask.category)).then(() => {
toast.success("Task added successfully!");
toast.success(t('task_added_successfully'));
router.back();
});
}
} else {
toast.error("Please fill in all fields correctly.");
toast.error(t("please_fill_in_all_fields_correctly"));
}
}}>
<Ionicons name='checkmark-circle-outline' size={24} color="#fff" />
<ThemedText style={styles.addButtonText}>{task?.id ? "Update" : "Add"} Task</ThemedText>
<ThemedText style={styles.addButtonText}>{task?.id ? t('update_task') : t('add_task')}</ThemedText>
</Pressable>
< View style={{ height: 50 }} />
</ScrollView >

View File

@ -1,6 +1,7 @@
import React from 'react';
import { ColorProp, FlexWidget, IconWidget, ListWidget, SvgWidget, TextWidget, } from 'react-native-android-widget';
import { getClockProgressSVG } from '../../components/clockProgress';
import i18n from '../../i18n.js';
import { Category } from '../../models/category';
import { Task } from '../../models/task';
import { GetTaskColor } from '../../utils/colors';
@ -17,6 +18,8 @@ const adaptDaysToGoToUnit = (t: Task) => {
export function HelloWidget({ tasks, categories }: { tasks: Task[], categories?: Category[] }) {
const { t } = i18n;
const demoTasks: Task[] = [
new Task('Task 1', 1, 1, new Date().getTime() - 1000 * 60 * 60 * 24 * 0, 5), // 2 days ago
new Task('Task 2', 2, 1, new Date().getTime() - 1000 * 60 * 60 * 24), // 1 day ago
@ -50,7 +53,7 @@ export function HelloWidget({ tasks, categories }: { tasks: Task[], categories?:
width: 'match_parent',
}}>
<TextWidget
text="Hello, Tasks!"
text={t('hello_tasks')}
style={{
color: '#ffffff',
fontSize: 24,
@ -110,14 +113,14 @@ export function HelloWidget({ tasks, categories }: { tasks: Task[], categories?:
}}
/>
<TextWidget
text={`${category ? (category.icon + " " + category.title) : 'Uncategorized'}`}
text={`${category ? (category.icon + " " + category.title) : t('default_category_name')}`}
style={{
color: '#ffffff',
fontSize: 12,
}}
/>
<TextWidget
text={parseInt(timeLeft.toString()) > 0 ? timeLeft + " left" : "Overdue"}
text={parseInt(timeLeft.toString()) > 0 ? timeLeft + " " + t('left') : t('overdue')}
style={{
color: '#ffffff',
fontSize: 16,
@ -137,7 +140,7 @@ export function HelloWidget({ tasks, categories }: { tasks: Task[], categories?:
)
})}
{(tasks && tasks.length === 0) &&
<TextWidget text='No tasks created.' style={{
<TextWidget text={t('no_tasks_created')} style={{
color: '#ffffff'
}}>
</TextWidget>}

View File

@ -1,12 +1,13 @@
import { throttle } from '@/utils/throttle';
import * as Notifications from 'expo-notifications';
import React from 'react';
import type { WidgetTaskHandlerProps } from 'react-native-android-widget';
import i18n from '../../i18n';
import { Category, CategoryQuery } from '../../models/category';
import { Task, TaskQuery } from '../../models/task';
import { CategoryRepository } from '../../repositories/CategoryRepository';
import { TaskRepository } from '../../repositories/TaskRepository';
import { SQLiteDataService } from '../../services/data/sqliteDataService';
import { throttle } from '../../utils/throttle';
import { HelloWidget } from './hello';
@ -15,6 +16,7 @@ const categoryRepository = new CategoryRepository(new SQLiteDataService<Category
let tasks: Task[] = [];
let categories: Category[] = [];
let resizeInterval = -1;
Notifications.setNotificationHandler({
handleNotification: async () => ({
@ -66,8 +68,8 @@ const CheckTaskAndNotify = async (): Promise<{ newTasks: Task[], newCategories:
await Notifications.scheduleNotificationAsync({
identifier: `task-due-${task.id}`,
content: {
title: 'Task Reminder',
body: `${category?.icon} "${task.title}" is due!`,
title: i18n.t("task_reminder_title"),
body: `${category?.icon} "${task.title}" ${i18n.t("is_due")}`,
data: { taskId: task.id },
},
trigger: null
@ -78,35 +80,37 @@ const CheckTaskAndNotify = async (): Promise<{ newTasks: Task[], newCategories:
return { newTasks: tasks, newCategories: categories };
}
const throttledCheckTaskAndNotify = throttle(CheckTaskAndNotify, 1000); // Throttle to prevent too frequent calls
setInterval(async () => {
const { newTasks, newCategories } = await throttledCheckTaskAndNotify();
const { newTasks, newCategories } = await CheckTaskAndNotify();
console.log("Throttled task check and notify executed OUT");
console.log("New tasks:", newTasks);
console.log("New categories:", newCategories);
tasks = newTasks;
categories = newCategories;
}, 1000 * 60 * 30); // Check every 30 minutes
}, 1000 * 60); // Check every minute
let lastRender = 0;
export async function widgetTaskHandler(props: WidgetTaskHandlerProps) {
async function updateAndRenderWidget() {
const { newTasks, newCategories } = await throttledCheckTaskAndNotify();
tasks = newTasks;
categories = newCategories;
console.log("Throttled task check and notify executed IN");
console.log("New tasks:", newTasks);
console.log("New categories:", newCategories);
props.renderWidget(<HelloWidget tasks={tasks} categories={categories} />);
const throttledDraw = throttle(props.renderWidget, 1000);
async function updateAndRenderWidget(force = false) {
console.log("Updating widget with tasks and categories");
const { newTasks, newCategories } = await CheckTaskAndNotify();
throttledDraw(<HelloWidget tasks={newTasks} categories={newCategories} />);
if (lastRender > Date.now() - 1000 || force) {
lastRender = Date.now();
console.log("Rendering widget");
props.renderWidget(<HelloWidget tasks={newTasks} categories={newCategories} />);
}
}
switch (props.widgetAction) {
case 'WIDGET_ADDED':
case 'WIDGET_UPDATE':
case 'WIDGET_RESIZED':
// Not needed for now
updateAndRenderWidget();
break;
@ -117,7 +121,7 @@ export async function widgetTaskHandler(props: WidgetTaskHandlerProps) {
case 'WIDGET_CLICK':
switch (props.clickAction) {
case 'REFRESH':
await updateAndRenderWidget();
await updateAndRenderWidget(true);
break;
case 'UPDATE_TASK':
if (props.clickActionData && props.clickActionData.id) {
@ -126,7 +130,7 @@ export async function widgetTaskHandler(props: WidgetTaskHandlerProps) {
if (task) {
task.lastDone = Math.floor(Date.now() / 1000 / 60 / 60 / 24); // Set last done to today
await taskRepository.update(parseInt(taskId.toString()), { ...task, lastDone: new Date().getTime() });
await updateAndRenderWidget();
await updateAndRenderWidget(true);
}
}
break;

View File

@ -2,6 +2,7 @@ import { Alert, ColorSchemeName, Pressable, StyleSheet, useColorScheme, type Vie
import { Text } from '@react-navigation/elements';
import { useRouter } from 'expo-router';
import { useTranslation } from 'react-i18next';
import { useThemeColor } from '../hooks/useThemeColor';
import { Category, CategoryQuery } from '../models/category';
import { Task, TaskQuery } from '../models/task';
@ -24,6 +25,7 @@ const categoryRepository = new CategoryRepository(new SQLiteDataService<Category
const taskRepository = new TaskRepository(new SQLiteDataService<Task>(TaskQuery));
export function CategoryItem({ category, categories, onUpdate, lightColor, darkColor, ...otherProps }: CategoryItemProps) {
const { t } = useTranslation();
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
const router = useRouter();
const colorScheme = useColorScheme();
@ -32,11 +34,11 @@ export function CategoryItem({ category, categories, onUpdate, lightColor, darkC
//select edit or remove
console.log("Long pressed task:", category);
Alert.alert(
"Select action",
"Choose an action for the task",
t('select_action'),
t('choose_action_for_category'),
[
{
text: "Edit",
text: t('edit'),
onPress: () => {
// Navigate to edit task form
console.log("Editing category:", category);
@ -45,16 +47,16 @@ export function CategoryItem({ category, categories, onUpdate, lightColor, darkC
}
},
{
text: "Remove",
text: t('remove'),
onPress: () => {
// Remove the task
taskRepository.findAll().then((tasks) => {
const tasksInCategory = tasks.filter(task => task.category === category.id);
if (tasksInCategory.length > 0) {
Alert.alert(
"Cannot delete category",
"This category has tasks assigned to it. Please remove or reassign tasks before deleting the category.",
[{ text: "OK" }]
t('cannot_delete_category'),
t('category_has_tasks'),
[{ text: t('ok') }]
);
} else {
categoryRepository.delete(category.id!).then(() => {
@ -69,7 +71,7 @@ export function CategoryItem({ category, categories, onUpdate, lightColor, darkC
},
style: "destructive"
},
{ text: "Cancel", style: "cancel" }
{ text: t('cancel'), style: "cancel" }
]);
}

View File

@ -4,6 +4,7 @@ import { toast } from '@backpackapp-io/react-native-toast';
import { Text } from '@react-navigation/elements';
import { useRouter } from 'expo-router';
import { useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useThemeColor } from '../hooks/useThemeColor';
import { Category } from '../models/category';
import { Task, TaskQuery } from '../models/task';
@ -34,6 +35,7 @@ const adaptDaysToGoToUnit = (t: Task) => {
};
export function TaskItem({ task, categories, onUpdate, lightColor, darkColor, ...otherProps }: TaskItemProps) {
const { t } = useTranslation();
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
const router = useRouter();
const daysLeft = useRef(task.daysToRedo! * 24 * 3600 * 1000 - (Date.now() - task.lastDone!));
@ -46,11 +48,11 @@ export function TaskItem({ task, categories, onUpdate, lightColor, darkColor, ..
//select edit or remove
console.log("Long pressed task:", task);
Alert.alert(
"Select action",
"Choose an action for the task",
t('select_action'),
t('choose_action_for_task'),
[
{
text: "Edit",
text: t('edit'),
onPress: () => {
// Navigate to edit task form
console.log("Editing task:", task);
@ -59,22 +61,22 @@ export function TaskItem({ task, categories, onUpdate, lightColor, darkColor, ..
}
},
{
text: "Remove",
text: t('remove'),
onPress: () => {
// Remove the task
console.log("Removing task:", task);
if (!task?.id) return;
taskRepository.delete(task.id).then(() => {
toast.success("Task removed successfully");
toast.success(t('task_removed_successfully'));
onUpdate?.();
}).catch((error) => {
toast.success("Error removing task");
toast.error(t('error_removing_task'));
console.error("Error removing task:", error);
});
},
style: "destructive"
},
{ text: "Cancel", style: "cancel" }
{ text: t('cancel'), style: "cancel" }
]);
}
@ -89,16 +91,16 @@ export function TaskItem({ task, categories, onUpdate, lightColor, darkColor, ..
{task?.title}
</Text>
<Text style={styles(colorScheme).taskItemText}>
{category?.icon + " " + category?.title + " - " + (adaptDaysToGoToUnit(task) == 0 ? "Overdue" : adaptDaysToGoToUnit(task))}
{category?.icon + " " + category?.title + " - " + (adaptDaysToGoToUnit(task) == 0 ? t("Overdue") : adaptDaysToGoToUnit(task))}
</Text>
</ThemedView>
<Pressable style={styles(colorScheme).checkButton} onPress={() => {
taskRepository.update(task!.id!, { ...task, lastDone: Date.now() }).then(() => {
toast.success("Task updated successfully");
toast.success(t('task_updated_successfully'));
onUpdate?.();
}).catch((error) => {
toast.error("Error updating task");
toast.error(t('error_updating_task'));
console.error("Error updating task:", error);
});
}}>

30
i18n.js
View File

@ -1,23 +1,35 @@
import { getLocales } from 'expo-localization';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from 'react-native-localize';
import ar from './translations/ar.json';
import de from './translations/de.json';
import en from './translations/en.json';
import es from './translations/es.json';
import fr from './translations/fr.json';
import it from './translations/it.json';
import ja from './translations/ja.json';
import pt from './translations/pt.json';
import ru from './translations/ru.json';
import zh from './translations/zh.json';
const resources = {
en: { translation: en },
};
// Initialize i18n
const langList = { en, es, fr, de, it, pt, zh, ja, ru, ar };
const resources = {};
const locales = getLocales();
const fallback = { languageTag: 'en', isRTL: false };
const { languageTag } = RNLocalize.findBestLanguageTag(Object.keys(resources)) || fallback;
Object.keys(langList).forEach((lang) => {
resources[lang] = { translation: langList[lang] };
});
i18n
.use(initReactI18next)
.init({
resources,
lng: languageTag,
lng: locales[0]?.languageTag.split('-')[0], // Use the device's language
fallbackLng: 'en',
interpolation: { escapeValue: false },
interpolation: {
escapeValue: false, // React already escapes values
},
});
export default i18n;

View File

@ -1,8 +1,7 @@
import 'expo-router/entry';
import { registerWidgetTaskHandler } from 'react-native-android-widget';
import { widgetTaskHandler } from './app/widgets/widget-task-handler';
import "./i18n.js";
registerWidgetTaskHandler(widgetTaskHandler);
import 'expo-router/entry';
import './i18n.js';

22
package-lock.json generated
View File

@ -23,6 +23,7 @@
"expo-haptics": "~14.1.4",
"expo-image": "~2.3.2",
"expo-linking": "~7.1.7",
"expo-localization": "~16.1.6",
"expo-navigation-bar": "~4.2.7",
"expo-notifications": "~0.31.4",
"expo-router": "~5.1.3",
@ -37,7 +38,7 @@
"react-dom": "19.0.0",
"react-i18next": "^15.6.1",
"react-native": "0.79.5",
"react-native-android-widget": "^0.17.0",
"react-native-android-widget": "^0.17.1",
"react-native-emoji-popup": "^0.3.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-input-select": "^2.1.7",
@ -7533,6 +7534,19 @@
"react-native": "*"
}
},
"node_modules/expo-localization": {
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/expo-localization/-/expo-localization-16.1.6.tgz",
"integrity": "sha512-v4HwNzs8QvyKHwl40MvETNEKr77v1o9/eVC8WCBY++DIlBAvonHyJe2R9CfqpZbC4Tlpl7XV+07nLXc8O5PQsA==",
"license": "MIT",
"dependencies": {
"rtl-detect": "^1.0.2"
},
"peerDependencies": {
"expo": "*",
"react": "*"
}
},
"node_modules/expo-modules-autolinking": {
"version": "2.1.14",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.14.tgz",
@ -12686,6 +12700,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rtl-detect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz",
"integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==",
"license": "BSD-3-Clause"
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",

View File

@ -26,6 +26,7 @@
"expo-haptics": "~14.1.4",
"expo-image": "~2.3.2",
"expo-linking": "~7.1.7",
"expo-localization": "~16.1.6",
"expo-navigation-bar": "~4.2.7",
"expo-notifications": "~0.31.4",
"expo-router": "~5.1.3",
@ -40,7 +41,7 @@
"react-dom": "19.0.0",
"react-i18next": "^15.6.1",
"react-native": "0.79.5",
"react-native-android-widget": "^0.17.0",
"react-native-android-widget": "^0.17.1",
"react-native-emoji-popup": "^0.3.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-input-select": "^2.1.7",

67
translations/ar.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "مرحبًا",
"hello_world": "مرحبًا، العالم!",
"fetching_categories": "جارٍ تحميل الفئات...",
"default_category_name": "غير مصنف",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "خطأ أثناء تحميل الفئات:",
"fetching_tasks": "جارٍ تحميل المهام...",
"error_fetching_tasks": "خطأ أثناء تحميل المهام:",
"app_name": "Taskeep!",
"your_tasks": "مهامك",
"no_tasks_available": "لا توجد مهام متاحة.",
"add_task_to_get_started": "أضف مهمة للبدء!",
"tasks_fetched": "تم تحميل المهام:",
"error_refreshing_tasks": "خطأ أثناء تحديث المهام:",
"error_refreshing_categories": "خطأ أثناء تحديث الفئات:",
"focus_event": "تم تشغيل حدث التركيز.",
"oops": "عذرًا!",
"screen_does_not_exist": "هذه الشاشة غير موجودة.",
"go_to_home_screen": "العودة إلى الشاشة الرئيسية!",
"your_categories": "فئاتك",
"add_new_category": "إضافة فئة جديدة",
"category_title_placeholder": "عنوان الفئة *",
"add_new_task": "إضافة مهمة جديدة",
"task_title_placeholder": "عنوان المهمة *",
"cannot_delete_category": "لا يمكن حذف الفئة",
"category_has_tasks": "تحتوي هذه الفئة على مهام مخصصة. يرجى إزالة المهام أو إعادة تعيينها قبل حذف الفئة.",
"ok": "موافق",
"hello_tasks": "مرحبًا، المهام!",
"no_tasks_created": "لم يتم إنشاء مهام.",
"left": "متبقي",
"overdue": "متأخر",
"select_action": "اختر إجراءً",
"choose_action_for_task": "اختر إجراءً لهذه المهمة",
"choose_action_for_category": "اختر إجراءً لهذه الفئة",
"edit": "تحرير",
"remove": "إزالة",
"cancel": "إلغاء",
"select_icon": "اختر رمزًا",
"change_icon": "تغيير الرمز",
"select_iconand_enter_title": "اختر رمزًا وأدخل عنوانًا",
"add_category": "إضافة فئة",
"add_task": "إضافة مهمة",
"update_category": "تحديث الفئة",
"update_task": "تحديث المهمة",
"task_updated_successfully": "تم تحديث المهمة بنجاح!",
"category_updated_successfully": "تم تحديث الفئة بنجاح!",
"error_updating_task": "خطأ أثناء تحديث المهمة:",
"error_updating_category": "خطأ أثناء تحديث الفئة:",
"task_added_successfully": "تمت إضافة المهمة بنجاح!",
"category_added_successfully": "تمت إضافة الفئة بنجاح!",
"please_fill_in_all_fields_correctly": "يرجى ملء جميع الحقول بشكل صحيح.",
"task_deleted_successfully": "تم حذف المهمة بنجاح!",
"category_deleted_successfully": "تم حذف الفئة بنجاح!",
"create_new_category": "إنشاء فئة جديدة",
"after_which_task_is_due": "بعد ذلك تكون المهمة مستحقة",
"Minutes": "دقائق",
"Hours": "ساعات",
"Days": "أيام",
"Weeks": "أسابيع",
"Months": "أشهر",
"select_icon_and_enter_title": "اختر رمزًا وأدخل عنوانًا",
"Tasks": "المهام",
"Categories": "الفئات",
"task_reminder_title": "تذكير بالمهمة",
"is_due": "مستحقة!"
}

67
translations/de.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "Willkommen",
"hello_world": "Hallo, Welt!",
"fetching_categories": "Kategorien werden geladen...",
"default_category_name": "Nicht kategorisiert",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "Fehler beim Laden der Kategorien:",
"fetching_tasks": "Aufgaben werden geladen...",
"error_fetching_tasks": "Fehler beim Laden der Aufgaben:",
"app_name": "Taskeep!",
"your_tasks": "Ihre Aufgaben",
"no_tasks_available": "Keine Aufgaben verfügbar.",
"add_task_to_get_started": "Fügen Sie eine Aufgabe hinzu, um zu beginnen!",
"tasks_fetched": "Aufgaben geladen:",
"error_refreshing_tasks": "Fehler beim Aktualisieren der Aufgaben:",
"error_refreshing_categories": "Fehler beim Aktualisieren der Kategorien:",
"focus_event": "Fokusereignis ausgelöst.",
"oops": "Hoppla!",
"screen_does_not_exist": "Dieser Bildschirm existiert nicht.",
"go_to_home_screen": "Zur Startseite gehen!",
"your_categories": "Ihre Kategorien",
"add_new_category": "Neue Kategorie hinzufügen",
"category_title_placeholder": "Kategorietitel *",
"add_new_task": "Neue Aufgabe hinzufügen",
"task_title_placeholder": "Aufgabentitel *",
"cannot_delete_category": "Kategorie kann nicht gelöscht werden",
"category_has_tasks": "Diese Kategorie hat zugewiesene Aufgaben. Bitte entfernen oder weisen Sie die Aufgaben neu zu, bevor Sie die Kategorie löschen.",
"ok": "OK",
"hello_tasks": "Hallo, Aufgaben!",
"no_tasks_created": "Keine Aufgaben erstellt.",
"left": "verbleibend",
"overdue": "Überfällig",
"select_action": "Wählen Sie eine Aktion",
"choose_action_for_task": "Wählen Sie eine Aktion für diese Aufgabe",
"choose_action_for_category": "Wählen Sie eine Aktion für diese Kategorie",
"edit": "Bearbeiten",
"remove": "Entfernen",
"cancel": "Abbrechen",
"select_icon": "Symbol auswählen",
"change_icon": "Symbol ändern",
"select_iconand_enter_title": "Wählen Sie ein Symbol und geben Sie einen Titel ein",
"add_category": "Kategorie hinzufügen",
"add_task": "Aufgabe hinzufügen",
"update_category": "Kategorie aktualisieren",
"update_task": "Aufgabe aktualisieren",
"task_updated_successfully": "Aufgabe erfolgreich aktualisiert!",
"category_updated_successfully": "Kategorie erfolgreich aktualisiert!",
"error_updating_task": "Fehler beim Aktualisieren der Aufgabe:",
"error_updating_category": "Fehler beim Aktualisieren der Kategorie:",
"task_added_successfully": "Aufgabe erfolgreich hinzugefügt!",
"category_added_successfully": "Kategorie erfolgreich hinzugefügt!",
"please_fill_in_all_fields_correctly": "Bitte füllen Sie alle Felder korrekt aus.",
"task_deleted_successfully": "Aufgabe erfolgreich gelöscht!",
"category_deleted_successfully": "Kategorie erfolgreich gelöscht!",
"create_new_category": "Neue Kategorie erstellen",
"after_which_task_is_due": "nachdem die Aufgabe fällig ist",
"Minutes": "Minuten",
"Hours": "Stunden",
"Days": "Tage",
"Weeks": "Wochen",
"Months": "Monate",
"select_icon_and_enter_title": "Wählen Sie ein Symbol und geben Sie einen Titel ein",
"Tasks": "Aufgaben",
"Categories": "Kategorien",
"task_reminder_title": "Aufgabenerinnerung",
"is_due": "ist fällig!"
}

View File

@ -1,4 +1,67 @@
{
"welcome": "Welcome",
"hello_world": "Hello, World!"
"hello_world": "Hello, World!",
"fetching_categories": "Fetching categories...",
"default_category_name": "Uncategorized",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "Error fetching categories:",
"fetching_tasks": "Fetching tasks...",
"error_fetching_tasks": "Error fetching tasks:",
"app_name": "Taskeep!",
"your_tasks": "Your Tasks",
"no_tasks_available": "No tasks available.",
"add_task_to_get_started": "Add a task to get started!",
"tasks_fetched": "Tasks fetched:",
"error_refreshing_tasks": "Error refreshing tasks:",
"error_refreshing_categories": "Error refreshing categories:",
"focus_event": "Focus event triggered.",
"oops": "Oops!",
"screen_does_not_exist": "This screen does not exist.",
"go_to_home_screen": "Go to home screen!",
"your_categories": "Your Categories",
"add_new_category": "Add a new category",
"category_title_placeholder": "Category title *",
"add_new_task": "Add a new task",
"task_title_placeholder": "Task title *",
"cannot_delete_category": "Cannot delete category",
"category_has_tasks": "This category has tasks assigned to it. Please remove or reassign tasks before deleting the category.",
"ok": "OK",
"hello_tasks": "Hello, Tasks!",
"no_tasks_created": "No tasks created.",
"left": "left",
"overdue": "Overdue",
"select_action": "Select an action",
"choose_action_for_task": "Choose an action for this task",
"choose_action_for_category": "Choose an action for this category",
"edit": "Edit",
"remove": "Remove",
"cancel": "Cancel",
"select_icon": "Select an icon",
"change_icon": "Change icon",
"select_iconand_enter_title": "Select an icon and enter a title",
"add_category": "Add Category",
"add_task": "Add Task",
"update_category": "Update Category",
"update_task": "Update Task",
"task_updated_successfully": "Task updated successfully!",
"category_updated_successfully": "Category updated successfully!",
"error_updating_task": "Error updating task:",
"error_updating_category": "Error updating category:",
"task_added_successfully": "Task added successfully!",
"category_added_successfully": "Category added successfully!",
"please_fill_in_all_fields_correctly": "Please fill in all fields correctly.",
"task_deleted_successfully": "Task deleted successfully!",
"category_deleted_successfully": "Category deleted successfully!",
"create_new_category": "Create new category",
"after_which_task_is_due": "after which task is due",
"Minutes": "Minutes",
"Hours": "Hours",
"Days": "Days",
"Weeks": "Weeks",
"Months": "Months",
"select_icon_and_enter_title": "Select an icon and enter a title",
"Tasks": "Tasks",
"Categories": "Categories",
"task_reminder_title": "Task Reminder",
"is_due": "is due!"
}

67
translations/es.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "Bienvenido",
"hello_world": "Hola, Mundo!",
"fetching_categories": "Cargando categorías...",
"default_category_name": "Sin categoría",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "Error al cargar categorías:",
"fetching_tasks": "Cargando tareas...",
"error_fetching_tasks": "Error al cargar tareas:",
"app_name": "Taskeep!",
"your_tasks": "Tus Tareas",
"no_tasks_available": "No hay tareas disponibles.",
"add_task_to_get_started": "¡Agrega una tarea para comenzar!",
"tasks_fetched": "Tareas cargadas:",
"error_refreshing_tasks": "Error al actualizar tareas:",
"error_refreshing_categories": "Error al actualizar categorías:",
"focus_event": "Evento de enfoque activado.",
"oops": "¡Ups!",
"screen_does_not_exist": "Esta pantalla no existe.",
"go_to_home_screen": "¡Ir a la pantalla principal!",
"your_categories": "Tus Categorías",
"add_new_category": "Agregar una nueva categoría",
"category_title_placeholder": "Título de la categoría *",
"add_new_task": "Agregar una nueva tarea",
"task_title_placeholder": "Título de la tarea *",
"cannot_delete_category": "No se puede eliminar la categoría",
"category_has_tasks": "Esta categoría tiene tareas asignadas. Por favor, elimina o reasigna las tareas antes de eliminar la categoría.",
"ok": "OK",
"hello_tasks": "¡Hola, Tareas!",
"no_tasks_created": "No se han creado tareas.",
"left": "restante",
"overdue": "Atrasado",
"select_action": "Selecciona una acción",
"choose_action_for_task": "Elige una acción para esta tarea",
"choose_action_for_category": "Elige una acción para esta categoría",
"edit": "Editar",
"remove": "Eliminar",
"cancel": "Cancelar",
"select_icon": "Seleccionar un ícono",
"change_icon": "Cambiar ícono",
"select_iconand_enter_title": "Selecciona un ícono e ingresa un título",
"add_category": "Agregar Categoría",
"add_task": "Agregar Tarea",
"update_category": "Actualizar Categoría",
"update_task": "Actualizar Tarea",
"task_updated_successfully": "¡Tarea actualizada con éxito!",
"category_updated_successfully": "¡Categoría actualizada con éxito!",
"error_updating_task": "Error al actualizar la tarea:",
"error_updating_category": "Error al actualizar la categoría:",
"task_added_successfully": "¡Tarea añadida con éxito!",
"category_added_successfully": "¡Categoría añadida con éxito!",
"please_fill_in_all_fields_correctly": "Por favor, completa todos los campos correctamente.",
"task_deleted_successfully": "¡Tarea eliminada con éxito!",
"category_deleted_successfully": "¡Categoría eliminada con éxito!",
"create_new_category": "Crear nueva categoría",
"after_which_task_is_due": "después de lo cual vence la tarea",
"Minutes": "Minutos",
"Hours": "Horas",
"Days": "Días",
"Weeks": "Semanas",
"Months": "Meses",
"select_icon_and_enter_title": "Selecciona un ícono e ingresa un título",
"Tasks": "Tareas",
"Categories": "Categorías",
"task_reminder_title": "Recordatorio de Tarea",
"is_due": "está vencida!"
}

67
translations/fr.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "Bienvenue",
"hello_world": "Bonjour, Monde!",
"fetching_categories": "Chargement des catégories...",
"default_category_name": "Non catégorisé",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "Erreur lors du chargement des catégories :",
"fetching_tasks": "Chargement des tâches...",
"error_fetching_tasks": "Erreur lors du chargement des tâches :",
"app_name": "Taskeep!",
"your_tasks": "Vos Tâches",
"no_tasks_available": "Aucune tâche disponible.",
"add_task_to_get_started": "Ajoutez une tâche pour commencer!",
"tasks_fetched": "Tâches chargées :",
"error_refreshing_tasks": "Erreur lors de l'actualisation des tâches :",
"error_refreshing_categories": "Erreur lors de l'actualisation des catégories :",
"focus_event": "Événement de focus déclenché.",
"oops": "Oups!",
"screen_does_not_exist": "Cet écran n'existe pas.",
"go_to_home_screen": "Aller à l'écran d'accueil!",
"your_categories": "Vos Catégories",
"add_new_category": "Ajouter une nouvelle catégorie",
"category_title_placeholder": "Titre de la catégorie *",
"add_new_task": "Ajouter une nouvelle tâche",
"task_title_placeholder": "Titre de la tâche *",
"cannot_delete_category": "Impossible de supprimer la catégorie",
"category_has_tasks": "Cette catégorie a des tâches assignées. Veuillez supprimer ou réassigner les tâches avant de supprimer la catégorie.",
"ok": "OK",
"hello_tasks": "Bonjour, Tâches!",
"no_tasks_created": "Aucune tâche créée.",
"left": "restant",
"overdue": "En retard",
"select_action": "Sélectionnez une action",
"choose_action_for_task": "Choisissez une action pour cette tâche",
"choose_action_for_category": "Choisissez une action pour cette catégorie",
"edit": "Modifier",
"remove": "Supprimer",
"cancel": "Annuler",
"select_icon": "Sélectionner une icône",
"change_icon": "Changer d'icône",
"select_iconand_enter_title": "Sélectionnez une icône et entrez un titre",
"add_category": "Ajouter une catégorie",
"add_task": "Ajouter une tâche",
"update_category": "Mettre à jour la catégorie",
"update_task": "Mettre à jour la tâche",
"task_updated_successfully": "Tâche mise à jour avec succès!",
"category_updated_successfully": "Catégorie mise à jour avec succès!",
"error_updating_task": "Erreur lors de la mise à jour de la tâche:",
"error_updating_category": "Erreur lors de la mise à jour de la catégorie:",
"task_added_successfully": "Tâche ajoutée avec succès!",
"category_added_successfully": "Catégorie ajoutée avec succès!",
"please_fill_in_all_fields_correctly": "Veuillez remplir tous les champs correctement.",
"task_deleted_successfully": "Tâche supprimée avec succès!",
"category_deleted_successfully": "Catégorie supprimée avec succès!",
"create_new_category": "Créer une nouvelle catégorie",
"after_which_task_is_due": "après quoi la tâche est due",
"Minutes": "Minutes",
"Hours": "Heures",
"Days": "Jours",
"Weeks": "Semaines",
"Months": "Mois",
"select_icon_and_enter_title": "Sélectionnez une icône et entrez un titre",
"Tasks": "Tâches",
"Categories": "Catégories",
"task_reminder_title": "Rappel de Tâche",
"is_due": "est due!"
}

0
translations/index.ts Normal file
View File

67
translations/it.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "Benvenuto",
"hello_world": "Ciao, Mondo!",
"fetching_categories": "Caricamento delle categorie...",
"default_category_name": "Non categorizzato",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "Errore durante il caricamento delle categorie:",
"fetching_tasks": "Caricamento delle attività...",
"error_fetching_tasks": "Errore durante il caricamento delle attività:",
"app_name": "Taskeep!",
"your_tasks": "Le tue attività",
"no_tasks_available": "Nessuna attività disponibile.",
"add_task_to_get_started": "Aggiungi un'attività per iniziare!",
"tasks_fetched": "Attività caricate:",
"error_refreshing_tasks": "Errore durante l'aggiornamento delle attività:",
"error_refreshing_categories": "Errore durante l'aggiornamento delle categorie:",
"focus_event": "Evento di focus attivato.",
"oops": "Ops!",
"screen_does_not_exist": "Questa schermata non esiste.",
"go_to_home_screen": "Vai alla schermata principale!",
"your_categories": "Le tue categorie",
"add_new_category": "Aggiungi una nuova categoria",
"category_title_placeholder": "Titolo della categoria *",
"add_new_task": "Aggiungi una nuova attività",
"task_title_placeholder": "Titolo dell'attività *",
"cannot_delete_category": "Impossibile eliminare la categoria",
"category_has_tasks": "Questa categoria ha attività assegnate. Rimuovi o riassegna le attività prima di eliminare la categoria.",
"ok": "OK",
"hello_tasks": "Ciao, Attività!",
"no_tasks_created": "Nessuna attività creata.",
"left": "rimanente",
"overdue": "In ritardo",
"select_action": "Seleziona un'azione",
"choose_action_for_task": "Scegli un'azione per questa attività",
"choose_action_for_category": "Scegli un'azione per questa categoria",
"edit": "Modifica",
"remove": "Rimuovi",
"cancel": "Annulla",
"select_icon": "Seleziona un'icona",
"change_icon": "Cambia icona",
"select_iconand_enter_title": "Seleziona un'icona e inserisci un titolo",
"add_category": "Aggiungi Categoria",
"add_task": "Aggiungi Attività",
"update_category": "Aggiorna Categoria",
"update_task": "Aggiorna Attività",
"task_updated_successfully": "Attività aggiornata con successo!",
"category_updated_successfully": "Categoria aggiornata con successo!",
"error_updating_task": "Errore durante l'aggiornamento dell'attività:",
"error_updating_category": "Errore durante l'aggiornamento della categoria:",
"task_added_successfully": "Attività aggiunta con successo!",
"category_added_successfully": "Categoria aggiunta con successo!",
"please_fill_in_all_fields_correctly": "Si prega di compilare correttamente tutti i campi.",
"task_deleted_successfully": "Attività eliminata con successo!",
"category_deleted_successfully": "Categoria eliminata con successo!",
"create_new_category": "Crea nuova categoria",
"after_which_task_is_due": "dopo di che l'attività è dovuta",
"Minutes": "Minuti",
"Hours": "Ore",
"Days": "Giorni",
"Weeks": "Settimane",
"Months": "Mesi",
"select_icon_and_enter_title": "Seleziona un'icona e inserisci un titolo",
"Tasks": "Attività",
"Categories": "Categorie",
"task_reminder_title": "Promemoria Attività",
"is_due": "è scaduta!"
}

67
translations/ja.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "ようこそ",
"hello_world": "こんにちは、世界!",
"fetching_categories": "カテゴリを読み込んでいます...",
"default_category_name": "未分類",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "カテゴリの読み込み中にエラーが発生しました:",
"fetching_tasks": "タスクを読み込んでいます...",
"error_fetching_tasks": "タスクの読み込み中にエラーが発生しました:",
"app_name": "Taskeep!",
"your_tasks": "あなたのタスク",
"no_tasks_available": "利用可能なタスクはありません。",
"add_task_to_get_started": "タスクを追加して開始してください!",
"tasks_fetched": "タスクが読み込まれました:",
"error_refreshing_tasks": "タスクの更新中にエラーが発生しました:",
"error_refreshing_categories": "カテゴリの更新中にエラーが発生しました:",
"focus_event": "フォーカスイベントがトリガーされました。",
"oops": "おっと!",
"screen_does_not_exist": "この画面は存在しません。",
"go_to_home_screen": "ホーム画面に戻る!",
"your_categories": "あなたのカテゴリ",
"add_new_category": "新しいカテゴリを追加",
"category_title_placeholder": "カテゴリのタイトル *",
"add_new_task": "新しいタスクを追加",
"task_title_placeholder": "タスクのタイトル *",
"cannot_delete_category": "カテゴリを削除できません",
"category_has_tasks": "このカテゴリには割り当てられたタスクがあります。カテゴリを削除する前に、タスクを削除または再割り当てしてください。",
"ok": "OK",
"hello_tasks": "こんにちは、タスク!",
"no_tasks_created": "タスクが作成されていません。",
"left": "残り",
"overdue": "期限切れ",
"select_action": "アクションを選択",
"choose_action_for_task": "このタスクのアクションを選択",
"choose_action_for_category": "このカテゴリのアクションを選択",
"edit": "編集",
"remove": "削除",
"cancel": "キャンセル",
"select_icon": "アイコンを選択",
"change_icon": "アイコンを変更",
"select_iconand_enter_title": "アイコンを選択してタイトルを入力",
"add_category": "カテゴリを追加",
"add_task": "タスクを追加",
"update_category": "カテゴリを更新",
"update_task": "タスクを更新",
"task_updated_successfully": "タスクが正常に更新されました!",
"category_updated_successfully": "カテゴリが正常に更新されました!",
"error_updating_task": "タスクの更新中にエラーが発生しました:",
"error_updating_category": "カテゴリの更新中にエラーが発生しました:",
"task_added_successfully": "タスクが正常に追加されました!",
"category_added_successfully": "カテゴリが正常に追加されました!",
"please_fill_in_all_fields_correctly": "すべてのフィールドを正しく入力してください。",
"task_deleted_successfully": "タスクが正常に削除されました!",
"category_deleted_successfully": "カテゴリが正常に削除されました!",
"create_new_category": "新しいカテゴリを作成",
"after_which_task_is_due": "タスクが期限になるまで",
"Minutes": "分",
"Hours": "時間",
"Days": "日",
"Weeks": "週間",
"Months": "月",
"select_icon_and_enter_title": "アイコンを選択してタイトルを入力",
"Tasks": "タスク",
"Categories": "カテゴリ",
"task_reminder_title": "タスクリマインダー",
"is_due": "期限です!"
}

67
translations/pt.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "Bem-vindo",
"hello_world": "Olá, Mundo!",
"fetching_categories": "Carregando categorias...",
"default_category_name": "Não categorizado",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "Erro ao carregar categorias:",
"fetching_tasks": "Carregando tarefas...",
"error_fetching_tasks": "Erro ao carregar tarefas:",
"app_name": "Taskeep!",
"your_tasks": "Suas Tarefas",
"no_tasks_available": "Nenhuma tarefa disponível.",
"add_task_to_get_started": "Adicione uma tarefa para começar!",
"tasks_fetched": "Tarefas carregadas:",
"error_refreshing_tasks": "Erro ao atualizar tarefas:",
"error_refreshing_categories": "Erro ao atualizar categorias:",
"focus_event": "Evento de foco acionado.",
"oops": "Ops!",
"screen_does_not_exist": "Esta tela não existe.",
"go_to_home_screen": "Ir para a tela inicial!",
"your_categories": "Suas Categorias",
"add_new_category": "Adicionar uma nova categoria",
"category_title_placeholder": "Título da categoria *",
"add_new_task": "Adicionar uma nova tarefa",
"task_title_placeholder": "Título da tarefa *",
"cannot_delete_category": "Não é possível excluir a categoria",
"category_has_tasks": "Esta categoria tem tarefas atribuídas. Remova ou reatribua as tarefas antes de excluir a categoria.",
"ok": "OK",
"hello_tasks": "Olá, Tarefas!",
"no_tasks_created": "Nenhuma tarefa criada.",
"left": "restante",
"overdue": "Atrasado",
"select_action": "Selecione uma ação",
"choose_action_for_task": "Escolha uma ação para esta tarefa",
"choose_action_for_category": "Escolha uma ação para esta categoria",
"edit": "Editar",
"remove": "Remover",
"cancel": "Cancelar",
"select_icon": "Selecionar um ícone",
"change_icon": "Alterar ícone",
"select_iconand_enter_title": "Selecione um ícone e insira um título",
"add_category": "Adicionar Categoria",
"add_task": "Adicionar Tarefa",
"update_category": "Atualizar Categoria",
"update_task": "Atualizar Tarefa",
"task_updated_successfully": "Tarefa atualizada com sucesso!",
"category_updated_successfully": "Categoria atualizada com sucesso!",
"error_updating_task": "Erro ao atualizar a tarefa:",
"error_updating_category": "Erro ao atualizar a categoria:",
"task_added_successfully": "Tarefa adicionada com sucesso!",
"category_added_successfully": "Categoria adicionada com sucesso!",
"please_fill_in_all_fields_correctly": "Por favor, preencha todos os campos corretamente.",
"task_deleted_successfully": "Tarefa excluída com sucesso!",
"category_deleted_successfully": "Categoria excluída com sucesso!",
"create_new_category": "Criar nova categoria",
"after_which_task_is_due": "após o qual a tarefa é devida",
"Minutes": "Minutos",
"Hours": "Horas",
"Days": "Dias",
"Weeks": "Semanas",
"Months": "Meses",
"select_icon_and_enter_title": "Selecione um ícone e insira um título",
"Tasks": "Tarefas",
"Categories": "Categorias",
"task_reminder_title": "Lembrete de Tarefa",
"is_due": "está vencida!"
}

67
translations/ru.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "Добро пожаловать",
"hello_world": "Привет, мир!",
"fetching_categories": "Загрузка категорий...",
"default_category_name": "Без категории",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "Ошибка при загрузке категорий:",
"fetching_tasks": "Загрузка задач...",
"error_fetching_tasks": "Ошибка при загрузке задач:",
"app_name": "Taskeep!",
"your_tasks": "Ваши задачи",
"no_tasks_available": "Нет доступных задач.",
"add_task_to_get_started": "Добавьте задачу, чтобы начать!",
"tasks_fetched": "Задачи загружены:",
"error_refreshing_tasks": "Ошибка при обновлении задач:",
"error_refreshing_categories": "Ошибка при обновлении категорий:",
"focus_event": "Событие фокуса вызвано.",
"oops": "Упс!",
"screen_does_not_exist": "Этот экран не существует.",
"go_to_home_screen": "Перейти на главный экран!",
"your_categories": "Ваши категории",
"add_new_category": "Добавить новую категорию",
"category_title_placeholder": "Название категории *",
"add_new_task": "Добавить новую задачу",
"task_title_placeholder": "Название задачи *",
"cannot_delete_category": "Невозможно удалить категорию",
"category_has_tasks": "В этой категории есть назначенные задачи. Удалите или переназначьте задачи перед удалением категории.",
"ok": "OK",
"hello_tasks": "Привет, задачи!",
"no_tasks_created": "Задачи не созданы.",
"left": "осталось",
"overdue": "Просрочено",
"select_action": "Выберите действие",
"choose_action_for_task": "Выберите действие для этой задачи",
"choose_action_for_category": "Выберите действие для этой категории",
"edit": "Редактировать",
"remove": "Удалить",
"cancel": "Отмена",
"select_icon": "Выберите значок",
"change_icon": "Изменить значок",
"select_iconand_enter_title": "Выберите значок и введите название",
"add_category": "Добавить категорию",
"add_task": "Добавить задачу",
"update_category": "Обновить категорию",
"update_task": "Обновить задачу",
"task_updated_successfully": "Задача успешно обновлена!",
"category_updated_successfully": "Категория успешно обновлена!",
"error_updating_task": "Ошибка при обновлении задачи:",
"error_updating_category": "Ошибка при обновлении категории:",
"task_added_successfully": "Задача успешно добавлена!",
"category_added_successfully": "Категория успешно добавлена!",
"please_fill_in_all_fields_correctly": "Пожалуйста, заполните все поля правильно.",
"task_deleted_successfully": "Задача успешно удалена!",
"category_deleted_successfully": "Категория успешно удалена!",
"create_new_category": "Создать новую категорию",
"after_which_task_is_due": "после чего задача должна быть выполнена",
"Minutes": "Минуты",
"Hours": "Часы",
"Days": "Дни",
"Weeks": "Недели",
"Months": "Месяцы",
"select_icon_and_enter_title": "Выберите значок и введите название",
"Tasks": "Задачи",
"Categories": "Категории",
"task_reminder_title": "Напоминание о задаче",
"is_due": "просрочено!"
}

67
translations/zh.json Normal file
View File

@ -0,0 +1,67 @@
{
"welcome": "欢迎",
"hello_world": "你好,世界!",
"fetching_categories": "正在加载类别...",
"default_category_name": "未分类",
"default_category_icon": "🤷‍♂️",
"error_fetching_categories": "加载类别时出错:",
"fetching_tasks": "正在加载任务...",
"error_fetching_tasks": "加载任务时出错:",
"app_name": "Taskeep!",
"your_tasks": "你的任务",
"no_tasks_available": "没有可用的任务。",
"add_task_to_get_started": "添加一个任务以开始!",
"tasks_fetched": "任务已加载:",
"error_refreshing_tasks": "刷新任务时出错:",
"error_refreshing_categories": "刷新类别时出错:",
"focus_event": "触发了焦点事件。",
"oops": "哎呀!",
"screen_does_not_exist": "此屏幕不存在。",
"go_to_home_screen": "回到主屏幕!",
"your_categories": "你的类别",
"add_new_category": "添加新类别",
"category_title_placeholder": "类别标题 *",
"add_new_task": "添加新任务",
"task_title_placeholder": "任务标题 *",
"cannot_delete_category": "无法删除类别",
"category_has_tasks": "此类别有分配的任务。请在删除类别之前删除或重新分配任务。",
"ok": "好的",
"hello_tasks": "你好,任务!",
"no_tasks_created": "没有创建任务。",
"left": "剩余",
"overdue": "逾期",
"select_action": "选择一个操作",
"choose_action_for_task": "为此任务选择一个操作",
"choose_action_for_category": "为此类别选择一个操作",
"edit": "编辑",
"remove": "移除",
"cancel": "取消",
"select_icon": "选择一个图标",
"change_icon": "更改图标",
"select_iconand_enter_title": "选择一个图标并输入标题",
"add_category": "添加类别",
"add_task": "添加任务",
"update_category": "更新类别",
"update_task": "更新任务",
"task_updated_successfully": "任务更新成功!",
"category_updated_successfully": "类别更新成功!",
"error_updating_task": "更新任务时出错:",
"error_updating_category": "更新类别时出错:",
"task_added_successfully": "任务添加成功!",
"category_added_successfully": "类别添加成功!",
"please_fill_in_all_fields_correctly": "请正确填写所有字段。",
"task_deleted_successfully": "任务删除成功!",
"category_deleted_successfully": "类别删除成功!",
"create_new_category": "创建新类别",
"after_which_task_is_due": "任务到期后",
"Minutes": "分钟",
"Hours": "小时",
"Days": "天",
"Weeks": "周",
"Months": "月",
"select_icon_and_enter_title": "选择一个图标并输入标题",
"Tasks": "任务",
"Categories": "类别",
"task_reminder_title": "任务提醒",
"is_due": "已到期!"
}