CSC/ECE 517 Spring 2025 - E2517. Reimplement internationalization (frontend + backend)

From Expertiza_Wiki
Jump to navigation Jump to search

Important Links

Expertiza

Expertiza is an open-source web application developed primarily for peer assessment and collaborative learning. It allows instructors to create assignments where students can submit their work, review the work of their peers, and receive feedback from both peers and instructors. Expertiza supports various assignment formats, including team-based projects, individual submissions, and metareviews (reviews of reviews). Its flexible design offers features like topic sign-ups, staggered deadlines, plagiarism checks, and detailed rubrics, making it a powerful tool for promoting active learning, accountability, and continuous improvement in academic settings.

Overview

The objective of this project is to reimplement the Internationalization feature for both frontend and backend of Expertiza. This includes: Adding multi-language support, particularly Hindi (Indian) translations. Ensuring all user-facing messages, buttons, flash notices, and model validations can be switched based on the selected locale. Improving the test coverage with RSpec for all major controllers.

Problem

Currently, all Expertiza screens can only be viewed in English. Many Expertiza users are from other countries. This is a problem since this limits the accessibility of Expertiza.


Survey of home country of Expertiza users for a given course


Solution Approach

The frontend interface will support switching between English and Hindi (Indian) languages, ensuring all user-facing buttons, flash messages, and error notices reflect the selected language. Additionally, admin users will have access to a language selector to change the locale dynamically. On the backend, model validation messages and system notices will be moved to locale files to ensure consistency across different languages. Comprehensive RSpec tests have been added for major controllers including User, Course, Institution, Assignment, Participant, and Role to verify correct functionality.

Scope

The scope of this project is limited however only to static strings. i.e. user-generated content will not be automatically translated to other languages. Breaking down internationalization into 3 phases:


1. Language Preference Selection:
Steps:
1.Check if the user has selected a preferred language in their profile.
2.If neither is set, use the application's default language (English).


2: Dynamic Rendering of Views:
Steps:
1.Fetch translations for all static strings (stored in the backend config/locales folder for Rails and respective i18n JSON files in frontend).
2.Replace the UI elements dynamically based on the selected language.
3.Use translation helpers in Rails views and i18n functions in React frontend.
3.Handling Missing Translations:
1.If any text string lacks a translation in the selected language:

  • Fall back to English or another default language.
  • Optionally, log missing translations for developers to update.
  • 2. Ensure UI consistency and no crashes due to missing translations.

    Design

    1. Language Preference Selection:
    The primary goal is to determine the correct language in which to render each page based on user preferences. The logic we adopt is as follows:

  • if a user has explicitly set a preferred language in their profile (e.g., Hindi), all pages are displayed in that language.
  • If the user has no language preference, the application defaults to English.
  • The following logic is added to ApplicationController to enforce this:

      before_action :set_locale
    
    
      def set_locale
        I18n.locale = current_user&.locale || I18n.default_locale
      end
    
    

    This ensures dynamic, per-user language selection.
    Frontend Integration:
    On the frontend, the language preference is selected by the user on the Profile Edit page. Upon submission, the following logic is executed to update the preference:

    const handleSubmit = (values, { setSubmitting }) => {
      const selectedLang = values.language === 'Hindi' ? 'hi' : 'en';
      
      // Update React-i18next and local storage
      i18n.changeLanguage(selectedLang);
      localStorage.setItem('language', selectedLang);
    
      // Send language preference to backend
      const payload = { ...values, locale: selectedLang };
      const userId = localStorage.getItem('userId');
      
      fetch(`/api/v1/users/${userId}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('token')}` },
        body: JSON.stringify(payload),
      })
      .then((res) => res.json())
      .then(() => setSubmitting(false))
      .catch(() => setSubmitting(false));
    };
    

    This mechanism ensures that:

  • The user's selected language is immediately reflected on the frontend.
  • The language preference is saved to the backend database for persistence across sessions.
  • The backend can utilize this preference to determine the locale for all subsequent requests.
  • 2.Rendering Dynamic Content in Selected Language:
    Once the locale is determined, the actual views need to render text in the selected language. Rails' built-in I18n gem allows storing translations in .yml files for each supported language (e.g., en.yml, hi.yml): Example:

    
    en:
      assignments": {
            "create_title": "Create Assignment",
            "update_title": "Update Assignment: {{assignmentName}}",
            "success_message": "Assignment {{assignmentName}} {{mode}}d successfully!",
            "close": "Close",
            "create": "Create",
    
    hi:
      assignments": {
            "create_title": "असाइनमेंट बनाएं",
            "update_title": "असाइनमेंट अपडेट करें: {{assignmentName}}",
            "success_message": "असाइनमेंट {{assignmentName}} सफलतापूर्वक {{mode}} किया गया!",
            "close": "बंद करें",
            "create": "बनाएं",
    

    instead of:

    <h2>Create Assignment</h2>
    

    write:

    <h2><%= t('assignments.create_title') %></h2>
    


    Frontend (React), It’s the React version of i18next, allowing us to dynamically load and render language-specific text.
    Example:
    English (src/locales/en/translation.json):

    {
      "assignments": {
        "create_title": "Create Assignment",
        "create": "Create"
      }
    }
    
    

    Hindi (src/locales/hi/translation.json):

    {
      "assignments": {
        "create_title": "असाइनमेंट बनाएं",
        "create": "बनाएं"
      }
    }
    

    Usage in React component:
    Before (hardcoded English):

    <h2>Create Assignment</h2>
    <button>Create</button>
    

    After (dynamic, supports multi-language):

    import { useTranslation } from 'react-i18next';
    
    const AssignmentComponent = () => {
      const { t } = useTranslation();
    
      return (
        <div>
          <h2>{t('assignments.create_title')}</h2>
          <button>{t('assignments.create')}</button>
        </div>
      );
    };
    

    Language switching:
    React will automatically reload the translations and the page content will change to Hindi, without reloading the entire app!

    i18n.changeLanguage('hi'); // Hindi
    localStorage.setItem('language', 'hi');
    



    3.Handling Missing Translations Gracefully:
    What if some keys are not translated in the target language (e.g., Hindi)?

    We enable fallback mechanisms:

  • Backend (Rails) in application.rb:
  •     config.i18n.default_locale = :en  
        config.i18n.available_locales = [:en, :hi]  
        config.i18n.fallbacks = true 
        config.i18n.enforce_available_locales = true
    
  • Frontend (React-i18next) i18n.ts :
  • 
    import i18n from "i18next";
    import { initReactI18next } from "react-i18next";
    
    import enTranslation from "./locales/en/translation.json";
    import hiTranslation from "./locales/hi/translation.json";
    
    i18n.use(initReactI18next).init({
      resources: {
        en: { translation: enTranslation },
        hi: { translation: hiTranslation },
      },
      lng: localStorage.getItem("language") || "en",
      fallbackLng: "en",
      interpolation: { escapeValue: false },
    });
    
    export default i18n;
    

    Thus, if a key is missing, the system falls back to English, ensuring no broken UI.

    Alternate approaches

    1. Why not simply swap out entire views for each language instead of translating each text individually?
    At first glance, creating separate static views for each supported language (e.g., one view file for English, one for Hindi, etc.) might seem simpler. However, this method has major drawbacks:

  • Duplication of Logic:Views in Expertiza are not just static text; they contain dynamic content, embedded Ruby (ERB) code, loops, conditions, and partials. Replicating the same logic in multiple files for each language would violate the DRY principle.
  • Maintainability:Any changes in view logic (e.g., UI improvements, bug fixes) would need to be made in all language versions, increasing the risk of inconsistency and making maintenance difficult.
  • Scalability:Adding new languages would require duplicating entire views repeatedly, which becomes infeasible as the number of supported languages grows.
    Instead, we opted for dynamic translation at the element level using the I18n.t helper function, keeping the view logic in one place and swapping out only the text as per the selected language.
    2. Why did we choose the I18n gem instead of building our own translation mechanism? While implementing a custom translation mechanism from scratch seemed flexible, we decided to leverage Rails' built-in I18n gem due to the following reasons:
  • Session Locale Management:I18n handles locale persistence across multiple requests and sessions, even when users navigate between pages or tabs.
  • Community Support:18n is well-maintained, widely adopted in the Ruby on Rails ecosystem, and tested for edge cases.
  • File Management & Flexibility:t supports YAML-based language files, fallback mechanisms, pluralization, interpolation, and more, all out-of-the-box.
  • Less Reinventing, More Focus:Building a custom solution would reimplement features already available in I18n, adding unnecessary development and maintenance overhead. "In short", by using I18n, we focused on implementing the actual business logic (language selection rules) rather than reinventing the wheel for translation management. 3. Why did we store the default language preferences in the database instead of session?
    We evaluated two options for storing user language preferences:

    Use Case Diagram

    Actors:

  • Instructor
  • Student
  • Admin
  • System
  • Description: This use case addresses how language preferences are handled in Expertiza. All users (students, instructors, and admins) have the ability to set their preferred language on their profile page. The system dynamically renders all pages based on these preferences, with fallback mechanisms in case of missing translations.
    Main Flow: 1.User sets preferred language:

  • Any user logs into Expertiza.
  • User navigates to their profile settings page.
  • User selects their preferred language.
  • System stores the preferred language in the database (persisted across sessions).
  • 3.Rendering pages based on language:

  • All pages are displayed in their preferred language.
  • If any translation key is missing in the selected language, the system defaults to English. Exceptions:
  • Missing translation keys will fall back to English without breaking functionality.
  • If no user preference or course default is set, English is used by default.
  • Preconditions:

  • User is logged into Expertiza.
  • Postconditions:

  • User’s preferred language is saved.
  • Pages are rendered in the appropriate language based on settings.
  • Major design patterns and principles used

    1.DRY Principle - Usage of i18n t Function
    The (DRY) principle is heavily used in this project by leveraging the t function provided by the i18n library. Instead of hardcoding language strings throughout the views, the t function abstracts the logic of fetching the correct translation based on the current locale. Additionally, it handles fallback mechanisms automatically. This ensures that the localization logic is not repeated at every view element but is centralized and reusable, promoting clean, maintainable code.
    2.Chain of Responsibility

    The fallback mechanism in i18n adheres to the Chain of Responsibility design pattern. When a translation for a key in the user's selected language is not found, the library proceeds to check fallback languages (e.g., defaulting to English). This chain continues until a valid translation is found. This pattern decouples the translation request from the specific language files, making the system extensible and resilient to missing translations.
    3.Open/Closed Principle & Strategy Pattern - Dynamic Language Selection:' The decision-making process for selecting the appropriate language adheres to the Open/Closed Principle, meaning the system is open to future extensions (like course-specific language settings) without requiring changes to the existing core logic. Currently, the system selects the locale based on the user's profile preference:

  • All users see pages in their explicitly set preferred language.
  • If no preference is set, all pages default to English
  • To support this with flexibility, a Strategy Pattern is conceptually applied — the language selection logic is delegated to a single method (set_locale) in the controller. This method can be easily extended (e.g., to include course-specific behavior later) without modifying the parts that rely on it.

    Database Design

    To enable Internationalization (i18n) and allow users to set their preferred language, modifications have been made to the database schema, particularly in the users table.

  • A new column locale has been added to the users table.
  • Migration Snippet:

    t.string :locale
    
  • This locale column stores the user's preferred language as a string (e.g., "en", "hi").
  • The default behavior is:
  • Application default locale (en).
    Benefits:
    1.Persistence Across Sessions:
    User’s language preference is saved in the database, so it remains consistent across multiple logins.
    2.Flexibility:
    Supports multiple languages dynamically without major schema changes. You can add new locales anytime.
    3.Hierarchy of Preference:

  • User preference (users.locale)
  • Default application language (en)
  • Test Plan

    Objective:
    To verify that internationalization features (specifically Hindi translations) are correctly implemented across the Expertiza platform. The testing focuses on ensuring that static strings on various pages (Profile, Assignments, Courses) are translated correctly based on the user’s language preference. It also verifies fallback behavior when translations are missing.

    Manual Testing:
    Through manual testing, we aim to identify if all the features of the application are working as intended when the language conversion occurs. Screenshots of translated pages (Profile, Assignments, Courses) will be attached to verify correct translation rendering.

    Login Credentials
    username: admin2@example.com
    password: password123

    Scenarios:
    Scenario 1: User Preference Hindi - Profile Page
    1.Log in to Expertiza as a student.
    2.Navigate to the “User Profile Information” page.
    3.Navigate to the “User Profile Information” page.
    4.Verify:

  • Profile page labels, buttons, and flash messages are translated to Hindi.
  • No structural changes in layout (only text changes).


    Scenario 2: User Preference Hindi - Assignment & Course Page 1.Log in as a student.
    2.Change preferred language to Hindi.
    3.Visit the Assignments page and verify Hindi translations.
    4.Visit the Courses page and verify Hindi translations.
    5.If translation keys are missing:

  • Verify: Those strings should be displayed in English (fallback).



  • Scenario 3: Missing Translation Key Fallback 1.Log in as any user with Hindi preference.
    2.Visit a page with incomplete Hindi translation keys.
    3.Verify:

  • Unspecified strings are displayed in English.
  • No errors or broken strings on UI.
  • RSpec Test Cases

    As part of this project, we ensured robust backend testing by writing comprehensive RSpec tests for core entities related to the internationalization (I18n) feature. These tests validate CRUD operations, locale handling, and edge cases across various controllers. Additionally, we implemented locale-specific tests to ensure proper internationalized error messages and responses.
    Entities Covered: 1. Assignment Controller Tests:

  • Verified locale-specific error messages and validations for assignments..
  • Assignment Rspec Test Snippet:

      # -------------------------------------------------------------------------
    # DELETE /api/v1/assignments/{id}
    # -------------------------------------------------------------------------
    path '/api/v1/assignments/{id}' do
      parameter name: 'id', in: :path, type: :integer, description: 'Assignment ID'
    
      delete('Delete an assignment') do
        tags 'Assignments'
        produces 'application/json'
        consumes 'application/json'
        parameter name: 'Content-Type', in: :header, type: :string
        let('Content-Type') { 'application/json' }
    
        response(200, 'successful') do
          let(:id) { assignment.id }
    
          run_test! do |response|
            data = JSON.parse(response.body)
            expect(response).to have_http_status(:ok)
            expect(data['message']).to eq(I18n.t('assignment.deleted_successfully', locale: :en))
          end
        end
    
        response(404, 'Assignment not found') do
          let(:id) { 999 }
    
          run_test! do |response|
            data = JSON.parse(response.body)
            expect(response).to have_http_status(:not_found)
            expect(data['error']).to eq(I18n.t('assignment.not_found', locale: :en))
          end
        end
    
        response(404, 'Assignment not found in Hindi') do
          let(:id) { 999 }
          let!(:hindi_user) do
            User.create!(
              name: "hindiuser",
              full_name: "Hindi User",
              email: "hindi@example.com",
              password_digest: "password",
              role_id: @roles[:instructor].id,
              locale: :hi
            )
          end
          let(:token) { JsonWebToken.encode({ id: hindi_user.id }) }
          let(:Authorization) { "Bearer #{token}" }
    
          run_test! do |response|
            data = JSON.parse(response.body)
            expect(response).to have_http_status(:not_found)
            expect(data['error']).to eq(I18n.t('assignment.not_found', locale: :hi))
          end
        end
      end
    end
    

    2. Course Controller Tests:

  • Localized responses and validations for course creation and updates.
  • Course Rspec Test Snippet:

    esponse(200, 'TA removed message is internationalized') do
            let!(:hindi_user) do
              User.create!(
                name: "hindiuser",
                full_name: "Hindi User",
                email: "hindi@example.com",
                password_digest: "password",
                role_id: @roles[:instructor].id,
                locale: :hi
              )
            end
      
            let(:token) { JsonWebToken.encode({ id: hindi_user.id }) }
            let(:Authorization) { "Bearer #{token}" }
      
            before do
              allow_any_instance_of(Course).to receive(:remove_ta).and_return({ success: true, ta_name: ta.name })
            end
      
            run_test! do |response|
              data = JSON.parse(response.body)
              expected_message = I18n.t('course.ta_removed', ta_name: ta.name, locale: :hi)
              expect(response).to have_http_status(:ok)
              expect(data['message']).to eq(expected_message)
            end
          end
    

    3.Roles Controller Tests:

  • Ensured correct localized error handling during role management operations.
  • 4. Institution Controller Tests:

  • Ensured localized responses during CRUD operations.
  • 5. Participant Controller Tests:

  • Tested internationalized error responses for participant creation, updates, authorization, and deletion operations.
  • Test Suite: All RSpec tests validating I18n functionality pass successfully, ensuring correct localized responses across controllers.

    Future Scope

    1. Expand to More Languages While the current implementation focuses on supporting Hindi as an additional language alongside English, the system has been designed with scalability in mind. The modularity of the i18n framework, combined with the structured approach to handling locale preferences in both the frontend and backend, allows seamless extension to other languages in the future. Adding new languages such as Spanish, Chinese, French, etc., would simply involve providing appropriate translations in the YAML files and ensuring their integration with the existing locale management system.

    2. Introduce Course-Specific Language Overrides Currently, the application supports language selection based solely on individual user preferences. In the future, support for course-level default languages can be introduced — allowing instructors to specify a preferred language for their courses. This would be particularly useful for course-specific pages viewed by students who haven't set a personal language preference. Implementing this feature would involve auditing course-related views and ensuring the locale dynamically respects either the student’s preference or the course’s default, thereby providing a more personalized and consistent multilingual experience.

    team

    members
    Ishani Rajput(irajput@ncsu.edu)
    Elnaz Ghotbitaheri(eghotbi@ncsu.edu)
    Ryan Gallagher(rtgalla2@ncsu.edu)
    Mentor
    Vihar Manojkumar Shah(vshah23@ncsu.edu)