DEV Community

Fernando Paladini
Fernando Paladini

Posted on

Build a Multilingual React Reflection Portal with GitHub Pages

If a daily-content project starts as a folder of JSON files, the first useful web interface does not need a database server. It needs a predictable data shape, a small client-side state model, and a deployment target that can serve static files.

This tutorial builds that path with AA Daily Reflections Database, an open-source React portal that publishes English, Spanish, French, and Brazilian Portuguese data as JSON, CSV, and SQLite. The goal is not to reproduce its reflection text here. The goal is to understand the reusable engineering pattern: select a language, select a date, load one static data file, and deploy the result as a static site.

TL;DR

Clone the repository, install its Node.js dependencies, run the data-copy step, and build the React app. The portal keeps the selected language and date in localStorage, fetches the matching language JSON from public/data, and uses PUBLIC_URL so the same build can work locally or beneath a GitHub Pages project path.

The repository currently targets React 18.2.0 and react-scripts 5.0.1 in package.json. The live site is available on GitHub Pages.

Prerequisites

You need:

  • Node.js and npm
  • Git
  • A browser
  • A GitHub repository if you plan to publish your own fork

The project also includes Python, PHP, Go, and JavaScript examples for reading the SQLite database. This walkthrough focuses on the React portal and its static JSON boundary. See the repository's examples guide for the other language-specific paths.

1. Get the project and inspect the data boundary

Clone the repository and enter it:

git clone https://github.com/paladini/aa-daily-reflections-database.git
cd aa-daily-reflections-database
Enter fullscreen mode Exit fullscreen mode

The repository stores the browser-facing files in public/data. Each JSON record has a date, language, title, quote, reflection text, and source reference. The data README documents the same fields and also shows the SQLite schema.

That separation matters. The React app does not need a server endpoint for every date. It can fetch one language file, keep it in memory, and find the requested date locally. Static hosting is enough for this read-only workflow.

2. Install dependencies and build the portal

Install from the lockfile, then run the production build:

npm ci
npm run build
Enter fullscreen mode Exit fullscreen mode

The prebuild script runs npm run copy-data first. That script copies the four language files into public/data, which is the location the browser service reads at runtime. The build then emits a static build directory.

If you want to inspect the development version before building, run:

npm start
Enter fullscreen mode Exit fullscreen mode

Open the local address shown by Create React App. The application uses the current date as its initial selection and starts in Brazilian Portuguese. You can change both values from the interface.

3. See how language selection becomes a file request

The central mapping lives in src/context/AppContext.js. It maps an application language key to a concrete public filename:

export const LANGUAGES = {
  'pt-br': {
    code: 'pt-br',
    name: 'Portuguese (Brazil)',
    file: 'daily_reflections_brazilian-portuguese.json',
  },
  en: {
    code: 'en',
    name: 'English',
    file: 'daily_reflections_english.json',
  },
};
Enter fullscreen mode Exit fullscreen mode

The current source contains all four languages. The important design choice is that the UI does not construct arbitrary paths from user input. It selects a file from a fixed allowlist. That keeps the fetch boundary easy to audit.

The same context stores language and selectedDate, then persists them under two local-storage keys. A visitor can return later and keep the last selected language and date on that browser. This is convenience state, not account data, and it never needs a server session.

4. Load one language file and find one date

The data service in src/services/reflectionService.js caches each loaded language file. Its core path is equivalent to this:

const dataCache = {};

const loadLanguageData = async (lang) => {
  if (dataCache[lang]) return dataCache[lang];

  const languageInfo = LANGUAGES[lang];
  const baseUrl = process.env.PUBLIC_URL || '';
  const response = await fetch(
    `${baseUrl}/data/${languageInfo.file}`,
  );

  if (!response.ok) {
    throw new Error(`Could not load data: ${response.status}`);
  }

  const data = await response.json();
  dataCache[lang] = data;
  return data;
};

export const getReflection = async (lang, date) => {
  const data = await loadLanguageData(lang);
  return data.find((item) => item.date === date) || null;
};
Enter fullscreen mode Exit fullscreen mode

The PUBLIC_URL prefix is important on GitHub Pages. A project site is served below a path such as /aa-daily-reflections-database, not necessarily from /. The prefix lets Create React App provide the correct base path during the build while keeping local development simple.

5. Verify the result without trusting the screen alone

Use the build output and the browser network panel as two separate checks.

First, confirm that the build created the copied files:

test -f build/data/daily_reflections_english.json
test -f build/data/daily_reflections_brazilian-portuguese.json
Enter fullscreen mode Exit fullscreen mode

On PowerShell, use:

Test-Path build/data/daily_reflections_english.json
Test-Path build/data/daily_reflections_brazilian-portuguese.json
Enter fullscreen mode Exit fullscreen mode

Then open the app and change the language. In the browser's Network panel, verify that the request changes to the corresponding file under data/. Change the date and confirm that the displayed record changes without a server-side API request.

Finally, refresh the page. The selected language and date should be restored because the context writes them to localStorage. If a JSON request returns 404, inspect the base path first. A missing PUBLIC_URL is a common static-hosting failure mode.

6. Publish the static build

The repository already includes a deploy script using the gh-pages package:

npm run deploy
Enter fullscreen mode Exit fullscreen mode

Before using it, review the generated build directory and confirm that the repository's Pages configuration matches the branch and path you intend to publish. GitHub Pages is a static host here. It does not execute the React code on the server and it cannot provide private storage for reflection data or user preferences.

The project also generates a sitemap as part of its deployment-oriented script. Read scripts/generate-sitemap.js before adapting the domain or path for a fork. SEO metadata is only useful when its canonical URLs match the site that is actually being served.

Why this pattern works

This architecture matches the problem's access pattern:

  1. The data is read-heavy and changes infrequently.
  2. Each language can be downloaded independently.
  3. Date selection is a local lookup over an already loaded array.
  4. The UI needs no authentication or mutation endpoint.
  5. The deployment artifact is ordinary HTML, JavaScript, CSS, and JSON.

The tradeoff is equally clear. Every browser that uses a language downloads that language's JSON file, and searching the full collection happens in the browser. If the corpus grows substantially, replace the array lookup with a generated index or a query service. Do not add a database server merely because the source data is also available as SQLite.

Limitations and security boundaries

The repository's MIT license covers the software compilation and examples, but its legal notice separately says that the original Alcoholics Anonymous materials remain copyrighted and that the compilation is unofficial. Do not treat the MIT notice as permission to republish the underlying literature without checking the rights for your use case. The project links to aa.org for official resources.

The portal also is not a medical service, a replacement for a recovery group, or a guarantee that a translation is official. Keep those boundaries visible if you fork it. Do not put private user notes into this static dataset: GitHub Pages publishes the generated files publicly.

FAQ

Do I need a backend?

Not for the included read-only portal. The browser fetches static JSON and performs the date lookup locally.

Why keep JSON, CSV, and SQLite?

They serve different consumers. JSON is convenient for browser and small scripts, CSV is useful for analysis, and SQLite is useful when a client needs indexed queries or joins.

Can I add another language?

Add a validated data file, register it in the language allowlist, copy it into public/data, and update the UI labels and documentation. Verify dates, encoding, attribution, and rights before publishing.

Takeaway

A multilingual daily-content portal can remain simple when the data contract is explicit. Start with a fixed language allowlist, cache one static file per language, persist only non-sensitive preferences, and make the deployment path part of the verification checklist.

What would you add first to this pattern: a generated search index, an accessibility test suite, or a small API for clients that cannot download the full language file?

AI assistance disclosure: AI assistance was used to organize and edit this tutorial. The repository links, commands, architecture notes, limitations, and security boundaries were checked against the current public repository and live site before publication.

Top comments (0)