Skip to content

Commit

Permalink
feat(app-config): add i18n and its integration with cypress
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrieleMazzola committed Dec 24, 2020
1 parent 990c3c5 commit 5ae5bd2
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 16 deletions.
15 changes: 8 additions & 7 deletions packages/game-app/cypress/integration/test.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* eslint-disable jest/valid-expect-in-promise */
/// <reference types="cypress" />

context('Index page', () => {
context("Index page", () => {
beforeEach(() => {
cy.visit(Cypress.config().baseUrl)
})
cy.visit(Cypress.config().baseUrl);
});

it('should contain the title', () => {
cy.contains('Pipeline - The Game that Delivers!')
})
})
it("should contain the title", () => {
cy.containsTranslation("home.title");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

Cypress.Commands.add("containsTranslation", (key: string) => {
return cy.window().then((win) => {
return cy.contains((win as any).i18n.t(key));
});
});
7 changes: 7 additions & 0 deletions packages/game-app/cypress/support/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="Cypress" />

declare namespace Cypress {
interface Chainable<Subject = any> {
containsTranslation(key: string): Chainable<Subject>;
}
}
11 changes: 11 additions & 0 deletions packages/game-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/game-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"@reduxjs/toolkit": "^1.5.0",
"firebase": "^8.2.1",
"i18n-js": "^3.8.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
Expand Down Expand Up @@ -46,6 +47,7 @@
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.2",
"@testing-library/user-event": "^12.6.0",
"@types/i18n-js": "^3.0.3",
"@types/jest": "^26.0.19",
"@types/node": "^12.19.9",
"@types/react": "^16.14.2",
Expand Down
7 changes: 6 additions & 1 deletion packages/game-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { useTranslate } from '@pipeline/i18n';

function App() {

const t = useTranslate();


return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Pipeline - The Game that Delivers!
{t("home.title")}
</p>
<a
className="App-link"
Expand Down
17 changes: 17 additions & 0 deletions packages/game-app/src/_shared/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import I18n from 'i18n-js';
import enTranslations from '@assets/i18n/en';
import { reducer, actions, name } from './slice';
import { useTranslate } from './useTranslate';


export function initializeI18n() {
const defaultLocale = "en-EN";

I18n.defaultLocale = defaultLocale;
I18n.locale = defaultLocale;

I18n.translations[defaultLocale] = enTranslations;
console.log(I18n.translations);
}

export {reducer, actions, name, useTranslate}
28 changes: 28 additions & 0 deletions packages/game-app/src/_shared/i18n/slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createSelector, createSlice, PayloadAction } from "@reduxjs/toolkit";

export interface State {
currentLocale: string;
}

const initialState = {
currentLocale: "en-EN"
} as State;

const slice = createSlice({
name: "i18n",
initialState: initialState,
reducers: {
changeLanguage(state, action: PayloadAction<string>){
state.currentLocale = action.payload;
}
}
});

export const getCurrentLanguage = createSelector(
(state: {[name]: State}) => state[name],
(i18nState) => i18nState.currentLocale
);

export const reducer = slice.reducer;
export const actions = slice.actions;
export const name = slice.name;
18 changes: 18 additions & 0 deletions packages/game-app/src/_shared/i18n/useTranslate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getCurrentLanguage } from './slice';
import { useSelector } from 'react-redux';
import { Path } from './utils';
import enTranslations from '@assets/i18n/en';
import I18n from 'i18n-js';



export function useTranslate() {
const currentLanguage = useSelector(getCurrentLanguage);
return translateFactory(currentLanguage);
}

export function translateFactory(language: string){
return function translate(key: Path<typeof enTranslations>){
return I18n.t(key, {locale: language});
}
}
29 changes: 29 additions & 0 deletions packages/game-app/src/_shared/i18n/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
type PathImpl<T, Key extends keyof T> =
Key extends string
? T[Key] extends Record<string, any>
? | `${Key}.${PathImpl<T[Key], Exclude<keyof T[Key], keyof any[]>> & string}`
| `${Key}.${Exclude<keyof T[Key], keyof any[]> & string}`
: never
: never;

type PathImpl2<T> = PathImpl<T, keyof T>;

/**
* Type to extract the union of all the nested paths (in "."-notation).
*
* Example:
*
* The object:
* {
* "a": {
* "a1": "valuea1"
* },
* "b": {
* "b1": "valueb1"
* }
* }
*
* results in the possible paths:
* a.a1 | b.b1
*/
export type Path<T> = PathImpl2<T> extends string | keyof T ? PathImpl2<T> : keyof T;
3 changes: 3 additions & 0 deletions packages/game-app/src/_shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import 'firebase/analytics';
import 'firebase/auth';
import 'firebase/firestore';
import 'firebase/database';
import { initializeI18n } from './i18n/index';

export function bootstrap() {

initializeI18n();

firebase.initializeApp({
apiKey: CONFIG.REACT_APP_FIREBASE_CONFIG_API_KEY,
authDomain: CONFIG.REACT_APP_FIREBASE_CONFIG_AUTH_DOMAIN,
Expand Down
10 changes: 3 additions & 7 deletions packages/game-app/src/_shared/store/reducers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import {Action, ReducersMapObject} from "@reduxjs/toolkit";
import {name as i18nName, reducer as i18nReducer} from '@pipeline/i18n';

function testReducer(state = {}, action: Action) {
return state;
}

const reducers: ReducersMapObject = {
test: testReducer
const reducers = {
[i18nName]: i18nReducer
};

export default reducers;
13 changes: 13 additions & 0 deletions packages/game-app/src/assets/i18n/en.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// eslint-disable-next-line import/no-anonymous-default-export
const translations = {
"home":{
"title": "Pipeline - The Game that Delivers!",
"subtitle": "Random stuff"
},
"page2":{
"test1": "Pipeline - The Game that Delivers!",
"test2": "Random stuff"
}
}

export default translations;
11 changes: 11 additions & 0 deletions packages/game-app/src/cypressThings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Store } from "@reduxjs/toolkit";
import I18n from "i18n-js";

/**
* Makes I18n and the store available for Cypress in order
* to use them during the e2e tests.
*/
export function makeCypressHappy(store: Store) {
(window as any).store = store;
(window as any).i18n = I18n;
}
4 changes: 4 additions & 0 deletions packages/game-app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ ReactDOM.render(
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

if((window as any).Cypress) {
import('./cypressThings').then((module) => module.makeCypressHappy(store));
}
4 changes: 3 additions & 1 deletion packages/game-app/tsconfig.paths.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@pipeline/app-config": ["src/_shared/config"]
"@assets/*": ["src/assets/*"],
"@pipeline/app-config": ["src/_shared/config"],
"@pipeline/i18n": ["src/_shared/i18n"]
}
}
}

0 comments on commit 5ae5bd2

Please sign in to comment.