toniebox-reverse-engineering / teddycloud_web

A better web interface for TeddyCloud
https://github.com/toniebox-reverse-engineering/teddycloud/
16 stars 9 forks source link

Add page for missing translation strings #127

Closed SciLor closed 1 month ago

SciLor commented 1 month ago

It may be helpful to have a page that shows missing translation-strings, like the one with the missing tonie information.

henryk86 commented 1 month ago

not sure if this is something for the frontend.

As far as a know, you can show the missing translations if you were on a page where such missing translation appears, so if you do not visit every page in every variation, you will not get all missing translation strings.

another solution would be maintaining another file, which stores all translation keys and compare it to the actual ones. Also not really handy...

For now comparing the number of lines gives a good hint if something is missing (even there are currently some differences between all files in size of 1 line)

Can i create a page where all translations string are shown which are missing?

ChatGPT: Yes, you can create a page in your React app that shows all the missing translation keys using i18next. To do this, you can take advantage of i18next's missingKeyHandler or manually check for missing translations within your components.

Here’s how you can achieve that:

Approach 1: Using i18next missingKeyHandler You can track all the missing translation keys in the missingKeyHandler and store them in a state or a global variable. Then, display these missing keys on a separate page.

Step-by-Step Guide:

  1. Update i18n.js to Track Missing Keys Modify your i18n.js file to store missing keys in an array:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

const missingTranslations = []; // Array to store missing keys

i18n
  .use(initReactI18next) // Bind react-i18next to the instance
  .init({
    resources: {
      en: {
        translation: {
          "welcome": "Welcome to the app!",
          // Add your translations here
        }
      },
      fr: {
        translation: {
          "welcome": "Bienvenue dans l'application!",
          // Add your translations here
        }
      }
    },
    lng: 'en', // Default language
    fallbackLng: 'en', // Fallback language if key is missing
    debug: true, // Enable debug mode to track missing translations
    saveMissing: true, // Enable saving missing translations
    missingKeyHandler: (lng, ns, key, fallbackValue) => {
      console.warn(`Missing translation for ${key} in ${lng}`);
      if (!missingTranslations.includes(key)) {
        missingTranslations.push(key); // Add missing key to the array
      }
    },
    interpolation: {
      escapeValue: false, // React already handles XSS
    }
  });

export { missingTranslations }; // Export the missing translations array
export default i18n;
  1. Create a Missing Translations Page Now, you can create a React component that displays all the missing translation keys.
import React from 'react';
import { missingTranslations } from './i18n';

const MissingTranslationsPage = () => {
  return (
    <div>
      <h1>Missing Translations</h1>
      {missingTranslations.length > 0 ? (
        <ul>
          {missingTranslations.map((key, index) => (
            <li key={index}>{key}</li>
          ))}
        </ul>
      ) : (
        <p>All translations are available!</p>
      )}
    </div>
  );
};

export default MissingTranslationsPage;
  1. Route to the Missing Translations Page In your main App.js or wherever you're defining routes, you can add a route to display this page:
Code kopieren
import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import MissingTranslationsPage from './MissingTranslationsPage';
import HomePage from './HomePage'; // Example of another page

const App = () => {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/missing-translations" element={<MissingTranslationsPage />} />
      </Routes>
    </Router>
  );
};

export default App;
  1. View the Missing Translations Page When you visit /missing-translations, the app will display all the keys that were missing during the runtime of the app.

Approach 2: Manually Check Missing Translations Alternatively, you can manually check for missing translations by iterating through the keys in your language files and comparing them. Here’s how to do that:

  1. Define All Translation Keys in a Centralized File Create a file (translationKeys.js) that holds all your translation keys.
Code kopieren
export const translationKeys = [
  'welcome',
  'greeting',
  'goodbye',
  // Add all keys here
];
  1. Compare Against Available Translations Create a function that compares the available translations with the keys, and shows any missing ones.
Code kopieren
import React from 'react';
import i18n from './i18n'; // Import your i18n configuration
import { translationKeys } from './translationKeys'; // Import all defined keys

const MissingTranslationsPage = () => {
  const missingKeys = [];

  // Check for missing translations in the current language
  translationKeys.forEach((key) => {
    if (!i18n.exists(key)) {
      missingKeys.push(key);
    }
  });

  return (
    <div>
      <h1>Missing Translations</h1>
      {missingKeys.length > 0 ? (
        <ul>
          {missingKeys.map((key, index) => (
            <li key={index}>{key}</li>
          ))}
        </ul>
      ) : (
        <p>All translations are available!</p>
      )}
    </div>
  );
};

export default MissingTranslationsPage;

Explanation: Approach 1 dynamically logs any missing translation keys during runtime via missingKeyHandler. It is easy to implement, but you need to navigate through the app for it to detect all the missing keys.

Approach 2 compares all your translation keys at once against the existing translations. It requires you to maintain a list of all translation keys (translationKeys.js), but it can show all the missing keys at once without needing to interact with the app.

Conclusion You can choose either approach depending on your use case. The first approach works well for dynamic, real-time missing translation checks as the user navigates the app. The second is more suitable if you want a pre-defined overview of all missing keys without having to traverse through the app.

Let me know which approach works best for your use case!

SciLor commented 1 month ago

The idea I have in mind would be: Expect the English translation is complete and compare it with the other ones, including non used strings.

henryk86 commented 1 month ago

ok. then chatGPT proposes a solution. will test that:

To implement this translation comparison logic within a React page, you can use a similar approach as the script I described but move the logic into a React component. This component can display the missing and extra keys in the browser.

Here’s a step-by-step guide on how to create a React page that compares the translation JSON files and shows missing and extra keys dynamically:

Step 1: Organize Your Translation Files Assume your translation files are stored locally in the public folder, like this:

public/ ├── translations/ │ ├── en.json │ ├── fr.json │ ├── de.json │ ├── es.json

These will be fetched dynamically within the React app.

Step 2: Create the React Component Here’s a React component that fetches the translation files, compares them, and displays missing and extra keys for each language.

import React, { useEffect, useState } from 'react';

// Recursive function to find missing keys
const findMissingKeys = (baseObj, otherObj, parentKey = '') => {
  const missingKeys = [];
  Object.keys(baseObj).forEach((key) => {
    const fullKey = parentKey ? `${parentKey}.${key}` : key;
    if (typeof baseObj[key] === 'object' && !Array.isArray(baseObj[key])) {
      missingKeys.push(...findMissingKeys(baseObj[key], otherObj[key] || {}, fullKey));
    } else if (!(key in otherObj)) {
      missingKeys.push(fullKey);
    }
  });
  return missingKeys;
};

// Recursive function to find extra keys
const findExtraKeys = (baseObj, otherObj, parentKey = '') => {
  const extraKeys = [];
  Object.keys(otherObj).forEach((key) => {
    const fullKey = parentKey ? `${parentKey}.${key}` : key;
    if (typeof otherObj[key] === 'object' && !Array.isArray(otherObj[key])) {
      extraKeys.push(...findExtraKeys(baseObj[key] || {}, otherObj[key], fullKey));
    } else if (!(key in baseObj)) {
      extraKeys.push(fullKey);
    }
  });
  return extraKeys;
};

const TranslationComparison = () => {
  const [translations, setTranslations] = useState({});
  const [missingKeys, setMissingKeys] = useState({});
  const [extraKeys, setExtraKeys] = useState({});
  const [loading, setLoading] = useState(true);

  const languages = ['en', 'fr', 'de', 'es']; // Add other languages as needed
  const baseLang = 'en'; // Base language to compare against

  useEffect(() => {
    // Fetch all translation files
    const fetchTranslations = async () => {
      const fetchedTranslations = {};
      for (let lang of languages) {
        const response = await fetch(`/translations/${lang}.json`);
        const data = await response.json();
        fetchedTranslations[lang] = data;
      }
      setTranslations(fetchedTranslations);
      setLoading(false);
    };
    fetchTranslations();
  }, []);

  useEffect(() => {
    if (!loading && translations[baseLang]) {
      const baseTranslations = translations[baseLang];
      const missing = {};
      const extra = {};

      // Compare each language against the base language
      languages.forEach((lang) => {
        if (lang !== baseLang) {
          const otherTranslations = translations[lang];
          missing[lang] = findMissingKeys(baseTranslations, otherTranslations);
          extra[lang] = findExtraKeys(baseTranslations, otherTranslations);
        }
      });

      setMissingKeys(missing);
      setExtraKeys(extra);
    }
  }, [loading, translations]);

  if (loading) return <p>Loading translations...</p>;

  return (
    <div>
      <h1>Translation Comparison</h1>

      {languages.map((lang) => (
        lang !== baseLang && (
          <div key={lang}>
            <h2>Language: {lang.toUpperCase()}</h2>

            <h3>Missing Keys:</h3>
            {missingKeys[lang]?.length > 0 ? (
              <ul>
                {missingKeys[lang].map((key, index) => (
                  <li key={index}>{key}</li>
                ))}
              </ul>
            ) : (
              <p>No missing keys.</p>
            )}

            <h3>Extra Keys:</h3>
            {extraKeys[lang]?.length > 0 ? (
              <ul>
                {extraKeys[lang].map((key, index) => (
                  <li key={index}>{key}</li>
                ))}
              </ul>
            ) : (
              <p>No extra keys.</p>
            )}
          </div>
        )
      ))}
    </div>
  );
};

export default TranslationComparison;

Step 3: Explanation Fetching the JSON files:

In the useEffect hook, the component fetches the translation files for each language from the /locales folder using fetch. These are stored in the translations state object, where each language's translations are keyed by the language code (e.g., en, fr, de). Comparing translations:

Once all the translation files are fetched, the code compares the translations using the recursive findMissingKeys and findExtraKeys functions. It compares each language’s translation against the base language (English, in this case) and stores the missing and extra keys in the missingKeys and extraKeys state objects. Displaying results:

For each language other than the base language, the component displays the list of missing and extra keys. If no missing or extra keys are found, a message like "No missing keys" or "No extra keys" is shown. Step 4: Styling and Routing (Optional) If you want to add this page to your app's navigation, you can include it in your App.js or routes configuration like this:

import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import TranslationComparison from './TranslationComparison';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/translation-comparison" element={<TranslationComparison />} />
        {/* Other routes */}
      </Routes>
    </Router>
  );
}

export default App;

Step 5: Testing Ensure your translation files (en.json, fr.json, de.json, etc.) are located in the public/translations/ folder. Visit the /translation-comparison page in your app to see the missing and extra keys for each language compared to the base language. Conclusion This React page dynamically loads and compares the translation files, showing any missing or extra keys for each language. You can expand it to support more languages, improve the styling, or add features like exporting the results to a file.

Let me know if you need any additional details!