mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
Added the i18n translation for desktop-client (#3832)
This commit is contained in:
@@ -3,6 +3,8 @@ import React, {
|
||||
type CSSProperties,
|
||||
} from 'react';
|
||||
|
||||
import { t } from 'i18next';
|
||||
|
||||
import { envelopeBudget } from 'loot-core/client/queries';
|
||||
|
||||
import { useCategory } from '../../hooks/useCategory';
|
||||
@@ -66,7 +68,7 @@ export function EnvelopeBalanceMenuModal({
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Balance
|
||||
{t('Balance')}
|
||||
</Text>
|
||||
<BalanceWithCarryover
|
||||
isDisabled
|
||||
|
||||
@@ -5,6 +5,8 @@ import React, {
|
||||
type CSSProperties,
|
||||
} from 'react';
|
||||
|
||||
import { t } from 'i18next';
|
||||
|
||||
import { envelopeBudget } from 'loot-core/client/queries';
|
||||
import { amountToInteger, integerToAmount } from 'loot-core/shared/util';
|
||||
|
||||
@@ -82,7 +84,7 @@ export function EnvelopeBudgetMenuModal({
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Budgeted
|
||||
{t('Budgeted')}
|
||||
</Text>
|
||||
<FocusableAmountInput
|
||||
value={integerToAmount(budgeted || 0)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @ts-strict-ignore
|
||||
import React, { useState, type CSSProperties } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
@@ -56,6 +57,7 @@ export function EnvelopeBudgetMonthMenuModal({
|
||||
};
|
||||
|
||||
const displayMonth = monthUtils.format(month, 'MMMM ‘yy');
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -113,7 +115,7 @@ export function EnvelopeBudgetMonthMenuModal({
|
||||
height={20}
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
Edit notes
|
||||
{t('Edit notes')}
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
@@ -143,7 +145,7 @@ export function EnvelopeBudgetMonthMenuModal({
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
)}
|
||||
Actions
|
||||
{t('Actions')}
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
@@ -155,14 +157,20 @@ export function EnvelopeBudgetMonthMenuModal({
|
||||
onBudgetAction(month, 'copy-last');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} budgets have all been set to last month’s budgeted amounts.`,
|
||||
message: t(
|
||||
'{displayMonth} budgets have all been set to last month’s budgeted amounts.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onSetBudgetsToZero={() => {
|
||||
onBudgetAction(month, 'set-zero');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} budgets have all been set to zero.`,
|
||||
message: t(
|
||||
'{displayMonth} budgets have all been set to zero.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onSetMonthsAverage={numberOfMonths => {
|
||||
@@ -180,21 +188,30 @@ export function EnvelopeBudgetMonthMenuModal({
|
||||
onBudgetAction(month, 'apply-goal-template');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} budget templates have been applied.`,
|
||||
message: t(
|
||||
'{displayMonth} budget templates have been applied.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onOverwriteWithBudgetTemplates={() => {
|
||||
onBudgetAction(month, 'overwrite-goal-template');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} budget templates have been overwritten.`,
|
||||
message: t(
|
||||
'{displayMonth} budget templates have been overwritten.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onEndOfMonthCleanup={() => {
|
||||
onBudgetAction(month, 'cleanup-goal-template');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} end-of-month cleanup templates have been applied.`,
|
||||
message: t(
|
||||
'{displayMonth} end-of-month cleanup templates have been applied.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { collapseModals, pushModal } from 'loot-core/client/actions';
|
||||
@@ -35,11 +36,12 @@ export function EnvelopeBudgetSummaryModal({
|
||||
const { showUndoNotification } = useUndo();
|
||||
const { list: categories } = useCategories();
|
||||
const categoriesById = groupById(categories);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const openTransferAvailableModal = () => {
|
||||
dispatch(
|
||||
pushModal('transfer', {
|
||||
title: 'Transfer: To Budget',
|
||||
title: t('Transfer: To Budget'),
|
||||
month,
|
||||
amount: sheetValue,
|
||||
onSubmit: (amount, toCategoryId) => {
|
||||
@@ -60,7 +62,7 @@ export function EnvelopeBudgetSummaryModal({
|
||||
const openCoverOverbudgetedModal = () => {
|
||||
dispatch(
|
||||
pushModal('cover', {
|
||||
title: 'Cover: Overbudgeted',
|
||||
title: t('Cover: Overbudgeted'),
|
||||
month,
|
||||
showToBeBudgeted: false,
|
||||
onSubmit: categoryId => {
|
||||
@@ -112,7 +114,7 @@ export function EnvelopeBudgetSummaryModal({
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
title="Budget Summary"
|
||||
title={t('Budget Summary')}
|
||||
rightContent={<ModalCloseButton onPress={close} />}
|
||||
/>
|
||||
<NamespaceContext.Provider value={sheetForMonth(month)}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { loadBackup, makeBackup } from 'loot-core/client/actions';
|
||||
@@ -73,13 +74,14 @@ export function LoadBackupModal({
|
||||
const previousBackups = backups.filter(
|
||||
backup => !('isLatest' in backup ? backup.isLatest : false),
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Modal name="load-backup" containerProps={{ style: { maxWidth: '30vw' } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
title="Load Backup"
|
||||
title={t('Load Backup')}
|
||||
rightContent={<ModalCloseButton onPress={close} />}
|
||||
/>
|
||||
<View style={{ marginBottom: 30 }}>
|
||||
@@ -95,10 +97,11 @@ export function LoadBackupModal({
|
||||
<Block>
|
||||
<Block style={{ marginBottom: 10 }}>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
You are currently working from a backup.
|
||||
{t('You are currently working from a backup.')}
|
||||
</Text>{' '}
|
||||
You can load a different backup or revert to the original
|
||||
version below.
|
||||
{t(
|
||||
'You can load a different backup or revert to the original version below.',
|
||||
)}
|
||||
</Block>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -106,18 +109,19 @@ export function LoadBackupModal({
|
||||
dispatch(loadBackup(budgetIdToLoad, latestBackup.id))
|
||||
}
|
||||
>
|
||||
Revert to original version
|
||||
{t('Revert to original version')}
|
||||
</Button>
|
||||
</Block>
|
||||
) : (
|
||||
<View style={{ alignItems: 'flex-start' }}>
|
||||
<Block style={{ marginBottom: 10 }}>
|
||||
Select a backup to load. After loading a backup, you will
|
||||
have a chance to revert to the current version in this
|
||||
screen.{' '}
|
||||
{t(
|
||||
'Select a backup to load. After loading a backup, you will have a chance to revert to the current version in this screen.',
|
||||
)}{' '}
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
If you use a backup, you will have to setup all your
|
||||
devices to sync from the new budget.
|
||||
{t(
|
||||
'If you use a backup, you will have to setup all your devices to sync from the new budget.',
|
||||
)}
|
||||
</Text>
|
||||
</Block>
|
||||
<Button
|
||||
@@ -125,14 +129,14 @@ export function LoadBackupModal({
|
||||
isDisabled={backupDisabled}
|
||||
onPress={() => dispatch(makeBackup())}
|
||||
>
|
||||
Backup now
|
||||
{t('Backup now')}
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{previousBackups.length === 0 ? (
|
||||
<Block style={{ color: theme.tableTextLight, marginLeft: 20 }}>
|
||||
No backups available
|
||||
{t('No backups available')}
|
||||
</Block>
|
||||
) : (
|
||||
<BackupTable
|
||||
|
||||
@@ -3,6 +3,8 @@ import React, {
|
||||
type CSSProperties,
|
||||
} from 'react';
|
||||
|
||||
import { t } from 'i18next';
|
||||
|
||||
import { trackingBudget } from 'loot-core/client/queries';
|
||||
|
||||
import { useCategory } from '../../hooks/useCategory';
|
||||
@@ -64,7 +66,7 @@ export function TrackingBalanceMenuModal({
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Balance
|
||||
{t('Balance')}
|
||||
</Text>
|
||||
<BalanceWithCarryover
|
||||
isDisabled
|
||||
|
||||
@@ -5,6 +5,8 @@ import React, {
|
||||
type CSSProperties,
|
||||
} from 'react';
|
||||
|
||||
import { t } from 'i18next';
|
||||
|
||||
import { trackingBudget } from 'loot-core/client/queries';
|
||||
import { amountToInteger, integerToAmount } from 'loot-core/shared/util';
|
||||
|
||||
@@ -82,7 +84,7 @@ export function TrackingBudgetMenuModal({
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Budgeted
|
||||
{t('Budgeted')}
|
||||
</Text>
|
||||
<FocusableAmountInput
|
||||
value={integerToAmount(budgeted || 0)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @ts-strict-ignore
|
||||
import React, { useState, type CSSProperties } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
@@ -27,6 +28,7 @@ export function TrackingBudgetMonthMenuModal({
|
||||
onBudgetAction,
|
||||
onEditNotes,
|
||||
}: TrackingBudgetMonthMenuModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const originalNotes = useNotes(`budget-${month}`);
|
||||
const { showUndoNotification } = useUndo();
|
||||
|
||||
@@ -84,7 +86,9 @@ export function TrackingBudgetMonthMenuModal({
|
||||
}}
|
||||
>
|
||||
<Notes
|
||||
notes={originalNotes?.length > 0 ? originalNotes : 'No notes'}
|
||||
notes={
|
||||
originalNotes?.length > 0 ? originalNotes : t('No notes')
|
||||
}
|
||||
editable={false}
|
||||
focused={false}
|
||||
getStyle={() => ({
|
||||
@@ -113,7 +117,7 @@ export function TrackingBudgetMonthMenuModal({
|
||||
height={20}
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
Edit notes
|
||||
{t('Edit notes')}
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
@@ -143,7 +147,7 @@ export function TrackingBudgetMonthMenuModal({
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
)}
|
||||
Actions
|
||||
{t('Actions')}
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
@@ -155,14 +159,20 @@ export function TrackingBudgetMonthMenuModal({
|
||||
onBudgetAction(month, 'copy-last');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} budgets have all been set to last month’s budgeted amounts.`,
|
||||
message: t(
|
||||
'{displayMonth} budgets have all been set to last month’s budgeted amounts.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onSetBudgetsToZero={() => {
|
||||
onBudgetAction(month, 'set-zero');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} budgets have all been set to zero.`,
|
||||
message: t(
|
||||
'{displayMonth} budgets have all been set to zero.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onSetMonthsAverage={numberOfMonths => {
|
||||
@@ -180,14 +190,20 @@ export function TrackingBudgetMonthMenuModal({
|
||||
onBudgetAction(month, 'apply-goal-template');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} budget templates have been applied.`,
|
||||
message: t(
|
||||
'{displayMonth} budget templates have been applied.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onOverwriteWithBudgetTemplates={() => {
|
||||
onBudgetAction(month, 'overwrite-goal-template');
|
||||
close();
|
||||
showUndoNotification({
|
||||
message: `${displayMonth} budget templates have been overwritten.`,
|
||||
message: t(
|
||||
'{displayMonth} budget templates have been overwritten.',
|
||||
{ displayMonth },
|
||||
),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { sheetForMonth } from 'loot-core/src/shared/months';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
@@ -18,13 +19,14 @@ type TrackingBudgetSummaryModalProps = {
|
||||
export function TrackingBudgetSummaryModal({
|
||||
month,
|
||||
}: TrackingBudgetSummaryModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const currentMonth = monthUtils.currentMonth();
|
||||
return (
|
||||
<Modal name="tracking-budget-summary">
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
title="Budget Summary"
|
||||
title={t('Budget Summary')}
|
||||
rightContent={<ModalCloseButton onPress={close} />}
|
||||
/>
|
||||
<NamespaceContext.Provider value={sheetForMonth(month)}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type ComponentProps, type ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
import {
|
||||
@@ -60,6 +61,7 @@ export function Header({
|
||||
onConditionsOpChange,
|
||||
children,
|
||||
}: HeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
|
||||
const { isNarrowWidth } = useResponsive();
|
||||
|
||||
@@ -92,7 +94,7 @@ export function Header({
|
||||
onChangeDates(newStart, newEnd, newMode);
|
||||
}}
|
||||
>
|
||||
{mode === 'static' ? 'Static' : 'Live'}
|
||||
{mode === 'static' ? t('Static') : t('Live')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -111,7 +113,7 @@ export function Header({
|
||||
defaultLabel={monthUtils.format(start, 'MMMM, yyyy')}
|
||||
options={allMonths.map(({ name, pretty }) => [name, pretty])}
|
||||
/>
|
||||
<View>to</View>
|
||||
<View>{t('to')}</View>
|
||||
<Select
|
||||
onChange={newValue =>
|
||||
onChangeDates(
|
||||
@@ -135,26 +137,26 @@ export function Header({
|
||||
variant="bare"
|
||||
onPress={() => onChangeDates(...getLatestRange(1))}
|
||||
>
|
||||
1 month
|
||||
{t('1 month')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="bare"
|
||||
onPress={() => onChangeDates(...getLatestRange(2))}
|
||||
>
|
||||
3 months
|
||||
{t('3 months')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="bare"
|
||||
onPress={() => onChangeDates(...getLatestRange(5))}
|
||||
>
|
||||
6 months
|
||||
{t('6 months')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="bare"
|
||||
onPress={() => onChangeDates(...getLatestRange(11))}
|
||||
>
|
||||
1 Year
|
||||
{t('1 Year')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="bare"
|
||||
@@ -164,7 +166,7 @@ export function Header({
|
||||
)
|
||||
}
|
||||
>
|
||||
All Time
|
||||
{t('All Time')}
|
||||
</Button>
|
||||
|
||||
{filters && (
|
||||
|
||||
@@ -246,14 +246,12 @@ function NetWorthInner({ widget }: NetWorthInnerProps) {
|
||||
<View style={{ marginTop: 30, userSelect: 'none' }}>
|
||||
<Trans>
|
||||
<Paragraph>
|
||||
<strong>How is net worth calculated?</strong>
|
||||
<strong>{t('How is net worth calculated?')}</strong>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
Net worth shows the balance of all accounts over time, including
|
||||
all of your investments. Your “net worth” is considered to be the
|
||||
amount you’d have if you sold all your assets and paid off as much
|
||||
debt as possible. If you hover over the graph, you can also see
|
||||
the amount of assets and debt individually.
|
||||
{t(
|
||||
'Net worth shows the balance of all accounts over time, including all of your investments. Your “net worth” is considered to be the amount you’d have if you sold all your assets and paid off as much debt as possible. If you hover over the graph, you can also see the amount of assets and debt individually.',
|
||||
)}
|
||||
</Paragraph>
|
||||
</Trans>
|
||||
</View>
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { t } from 'i18next';
|
||||
|
||||
import { sendCatch } from 'loot-core/src/platform/client/fetch';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
import { getRecurringDescription } from 'loot-core/src/shared/schedules';
|
||||
@@ -315,7 +317,7 @@ function MonthlyPatterns({
|
||||
/>
|
||||
<Button
|
||||
variant="bare"
|
||||
aria-label="Remove recurrence"
|
||||
aria-label={t('Remove recurrence')}
|
||||
style={{ padding: 7 }}
|
||||
onPress={() =>
|
||||
dispatch({
|
||||
@@ -328,7 +330,7 @@ function MonthlyPatterns({
|
||||
</Button>
|
||||
<Button
|
||||
variant="bare"
|
||||
aria-label="Add recurrence"
|
||||
aria-label={t('Add recurrence')}
|
||||
style={{ padding: 7, marginLeft: 5 }}
|
||||
onPress={() => dispatch({ type: 'add-recurrence' })}
|
||||
>
|
||||
@@ -392,7 +394,7 @@ function RecurringScheduleTooltip({
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
|
||||
<label htmlFor="start">From</label>
|
||||
<label htmlFor="start">{t('From')}</label>
|
||||
<InitialFocus>
|
||||
<DateSelect
|
||||
id="start"
|
||||
@@ -429,7 +431,7 @@ function RecurringScheduleTooltip({
|
||||
{config.endMode === 'on_date' && (
|
||||
<DateSelect
|
||||
id="end_date"
|
||||
inputProps={{ placeholder: 'End Date' }}
|
||||
inputProps={{ placeholder: t('End Date') }}
|
||||
value={config.endDate}
|
||||
onSelect={value => updateField('endDate', value)}
|
||||
containerProps={{ style: { width: 100 } }}
|
||||
@@ -444,7 +446,7 @@ function RecurringScheduleTooltip({
|
||||
style={{ marginTop: 10 }}
|
||||
spacing={1}
|
||||
>
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>Repeat every</Text>
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>{t('Repeat every')}</Text>
|
||||
<Input
|
||||
id="interval"
|
||||
style={{ width: 40 }}
|
||||
@@ -467,7 +469,7 @@ function RecurringScheduleTooltip({
|
||||
}}
|
||||
onPress={() => dispatch({ type: 'add-recurrence' })}
|
||||
>
|
||||
Add specific days
|
||||
{t('Add specific days')}
|
||||
</Button>
|
||||
) : null}
|
||||
</Stack>
|
||||
@@ -503,7 +505,7 @@ function RecurringScheduleTooltip({
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
Move schedule{' '}
|
||||
{t('Move schedule')}{' '}
|
||||
</label>
|
||||
<Select
|
||||
id="solve_dropdown"
|
||||
@@ -520,7 +522,7 @@ function RecurringScheduleTooltip({
|
||||
style={{ userSelect: 'none', marginLeft: 5 }}
|
||||
>
|
||||
{' '}
|
||||
weekend
|
||||
{t('weekend')}
|
||||
</label>
|
||||
</View>
|
||||
</Stack>
|
||||
@@ -528,13 +530,13 @@ function RecurringScheduleTooltip({
|
||||
<div
|
||||
style={{ display: 'flex', marginTop: 15, justifyContent: 'flex-end' }}
|
||||
>
|
||||
<Button onPress={onClose}>Cancel</Button>
|
||||
<Button onPress={onClose}>{t('Cancel')}</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onPress={() => onSave(unparseConfig(config))}
|
||||
style={{ marginLeft: 10 }}
|
||||
>
|
||||
Apply
|
||||
{t('Apply')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@@ -570,7 +572,7 @@ export function RecurringSchedulePicker({
|
||||
>
|
||||
{value
|
||||
? getRecurringDescription(value, dateFormat)
|
||||
: 'No recurring date'}
|
||||
: t('No recurring date')}
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
|
||||
6
upcoming-release-notes/3832.md
Normal file
6
upcoming-release-notes/3832.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [awaisalee]
|
||||
---
|
||||
|
||||
Enhance app with i18n translations
|
||||
Reference in New Issue
Block a user