diff --git a/README.md b/README.md
index 17bca479..27557abd 100644
--- a/README.md
+++ b/README.md
@@ -254,6 +254,16 @@ Vercel Postgres can be switched to another Postgres-compatible, pooling provider
1. Ensure connection string is set to "Transaction Mode" via port `6543`
2. Disable SSL by setting `DISABLE_POSTGRES_SSL = 1`
+💬 I18N
+-
+
+Partial internationalization (non-admin, user-facing text) provided for a handful of languages. If you'd like to add support for a new language, [open a PR](https://github.com/sambecker/exif-photo-blog/compare) using [`ES_EN`](https://github.com/sambecker/exif-photo-blog) for reference.
+
+### Supported Languages
+- `ES_ES`
+- `PT_BR`
+- `PT_PT`
+
📖 FAQ
-
#### How do I receive template updates?
diff --git a/src/app/AppViewSwitcher.tsx b/src/app/AppViewSwitcher.tsx
index 3ba9247d..cf03b5b8 100644
--- a/src/app/AppViewSwitcher.tsx
+++ b/src/app/AppViewSwitcher.tsx
@@ -9,6 +9,7 @@ import {
import IconSearch from '../components/icons/IconSearch';
import { useAppState } from '@/state/AppState';
import {
+ APP_TEXT,
GRID_HOMEPAGE_ENABLED,
SHOW_KEYBOARD_SHORTCUT_TOOLTIPS,
} from './config';
@@ -66,7 +67,7 @@ export default function AppViewSwitcher({
hrefRef={refHrefFeed}
active={currentSelection === 'feed'}
tooltip={{...SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
- content: 'Feed',
+ content: APP_TEXT.nav.feed,
keyCommand: KEY_COMMANDS.feed,
}}}
noPadding
@@ -79,7 +80,7 @@ export default function AppViewSwitcher({
hrefRef={refHrefGrid}
active={currentSelection === 'grid'}
tooltip={{...SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
- content: 'Grid',
+ content: APP_TEXT.nav.grid,
keyCommand: KEY_COMMANDS.grid,
}}}
noPadding
@@ -103,7 +104,7 @@ export default function AppViewSwitcher({
noPadding
tooltip={{
...!isAdminMenuOpen && SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
- content: 'Admin Menu',
+ content: APP_TEXT.nav.admin,
keyCommand: KEY_COMMANDS.admin,
},
}}
@@ -116,7 +117,7 @@ export default function AppViewSwitcher({
/>}
tooltip={{
...!isAdminMenuOpen && SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
- content: 'Admin Menu',
+ content: APP_TEXT.nav.admin,
keyCommand: KEY_COMMANDS.admin,
},
}}
@@ -128,7 +129,7 @@ export default function AppViewSwitcher({
icon={}
onClick={() => setIsCommandKOpen?.(true)}
tooltip={{...SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
- content: 'Search',
+ content: APP_TEXT.nav.search,
keyCommandModifier: KEY_COMMANDS.search[0],
keyCommand: KEY_COMMANDS.search[1],
}}}
diff --git a/src/app/config.ts b/src/app/config.ts
index fcda18b7..eef94bf4 100644
--- a/src/app/config.ts
+++ b/src/app/config.ts
@@ -5,6 +5,7 @@ import {
import { getOrderedCategoriesFromString } from '@/category';
import type { StorageType } from '@/platforms/storage';
import { makeUrlAbsolute, shortenUrl } from '@/utility/url';
+import { getContentForLanguage } from '@/i18n';
// HARD-CODED GLOBAL CONFIGURATION
@@ -98,6 +99,10 @@ const SITE_DOMAIN_SHORT = shortenUrl(SITE_DOMAIN);
// SITE META
+export const APP_TEXT = await getContentForLanguage(
+ process.env.NEXT_PUBLIC_LANGUAGE,
+);
+
export const NAV_TITLE =
process.env.NEXT_PUBLIC_NAV_TITLE;
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
new file mode 100644
index 00000000..1d1b65e4
--- /dev/null
+++ b/src/i18n/index.ts
@@ -0,0 +1,17 @@
+import US_EN from './languages/us-en';
+
+export type I18N = typeof US_EN;
+
+export const LANGUAGES: Record<
+ string,
+ (() => Promise>) | undefined
+> = {
+ 'pt-br': () => import('./languages/pt-br').then(module => module.default),
+};
+
+export const getContentForLanguage = async (
+ language = '',
+): Promise => ({
+ ...US_EN,
+ ...await LANGUAGES[language.toLocaleLowerCase()]?.(),
+});
diff --git a/src/i18n/languages/pt-br.ts b/src/i18n/languages/pt-br.ts
new file mode 100644
index 00000000..b0473f29
--- /dev/null
+++ b/src/i18n/languages/pt-br.ts
@@ -0,0 +1,21 @@
+import { I18N } from '..';
+
+const language: Partial = {
+ core: {
+ photo: 'Foto',
+ photoPlural: 'Fotos',
+ },
+ nav: {
+ home: 'InÃcio',
+ feed: 'Feed',
+ grid: 'Grade',
+ admin: 'Menu de Admin',
+ search: 'Pesquisar',
+ prev: 'Anterior',
+ prevShort: 'Ant',
+ next: 'Próximo',
+ nextShort: 'Prox',
+ },
+};
+
+export default language;
diff --git a/src/i18n/languages/us-en.ts b/src/i18n/languages/us-en.ts
new file mode 100644
index 00000000..86c99252
--- /dev/null
+++ b/src/i18n/languages/us-en.ts
@@ -0,0 +1,52 @@
+const language = {
+ core: {
+ photo: 'Photo',
+ photoPlural: 'Photos',
+ },
+ nav: {
+ home: 'Home',
+ feed: 'Feed',
+ grid: 'Grid',
+ admin: 'Admin',
+ search: 'Search',
+ prev: 'Previous',
+ prevShort: 'Prev',
+ next: 'Next',
+ nextShort: 'Next',
+ },
+ categories: {
+ camera: 'Camera',
+ cameraPlural: 'Cameras',
+ lens: 'Lens',
+ lensPlural: 'Lenses',
+ tag: 'Tag',
+ tagPlural: 'Tags',
+ recipe: 'Recipe',
+ recipePlural: 'Recipes',
+ film: 'Film',
+ filmPlural: 'Films',
+ focalLength: 'Focal Length',
+ focalLengthPlural: 'Focal Lengths',
+ },
+ footer: {
+ repo: 'Made with',
+ system: 'System',
+ light: 'Light',
+ dark: 'Dark',
+ },
+ auth: {
+ signIn: 'Sign in',
+ signOut: 'Sign out',
+ email: 'Admin Email',
+ password: 'Admin Password',
+ },
+ tooltips: {
+ '35mm': '35mm Equivalent',
+ imageViewer: 'Open Image Viewer',
+ sharePhoto: 'Share Photo',
+ recipeInfo: 'Recipe Info',
+ recipeCopy: 'Copy Recipe Text',
+ },
+};
+
+export default language;