Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add page for missing translation strings #127

Closed
SciLor opened this issue Oct 4, 2024 · 3 comments
Closed

Add page for missing translation strings #127

SciLor opened this issue Oct 4, 2024 · 3 comments
Labels
enhancement New feature or request fixed in develop

Comments

@SciLor
Copy link
Contributor

SciLor commented Oct 4, 2024

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

@SciLor SciLor added the enhancement New feature or request label Oct 4, 2024
@henryk86
Copy link
Collaborator

henryk86 commented Oct 8, 2024

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
Copy link
Contributor Author

SciLor commented Oct 8, 2024

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
Copy link
Collaborator

henryk86 commented Oct 8, 2024

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!

henryk86 added a commit to henryk86/teddycloud_web that referenced this issue Oct 8, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
SciLor added a commit that referenced this issue Oct 9, 2024
gh-127 added translation overview page, prepared gh-128, gh-126, gh-130
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request fixed in develop
Projects
None yet
Development

No branches or pull requests

2 participants