Update frontend to fetch locale on load
This commit is contained in:
parent
0f9757f2c2
commit
4670e046bb
|
@ -14,11 +14,15 @@ export const appListeners = (app, state, emitter) => {
|
|||
emitter.on('set-language', newLanguage => {
|
||||
app.setSettingsItem('lang', newLanguage);
|
||||
state.language = newLanguage;
|
||||
emitter.emit('render');
|
||||
state.i18n.fetchLocaleUI().then(() => {
|
||||
emitter.emit('render');
|
||||
});
|
||||
});
|
||||
|
||||
app.checkIfLoggedIn(state).then(isLoggedIn => {
|
||||
emitter.emit('render'); // This should hopefully only run once after the DOM is loaded. It prevents routing issues where 'render' hasn't been defined yet
|
||||
});
|
||||
state.i18n.fetchLocaleUI().then(() => {
|
||||
app.checkIfLoggedIn(state).then(isLoggedIn => {
|
||||
emitter.emit('render'); // This should hopefully only run once after the DOM is loaded. It prevents routing issues where 'render' hasn't been defined yet
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { globalView } from './views/global';
|
||||
import { homeView } from './views/home';
|
||||
import { aboutView } from './views/about';
|
||||
import { loginView } from './views/login';
|
||||
import { searchView } from './views/search';
|
||||
import { errorView } from './views/404';
|
||||
|
@ -7,6 +8,8 @@ import { errorView } from './views/404';
|
|||
export const appRoutes = (app) => {
|
||||
app.route('/', (state, emit) => globalView(state, emit, homeView));
|
||||
|
||||
app.route('/about', (state, emit) => globalView(state, emit, aboutView));
|
||||
|
||||
app.route('/login', (state, emit) => globalView(state, emit, loginView));
|
||||
|
||||
app.route('/logout', () => window.location.reload()); // If Choo navigates here, refresh the page instead so the server can handle it and log out
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
export class I18n {
|
||||
constructor(appState) {
|
||||
this.appState = appState;
|
||||
this.availableLanguages = null;
|
||||
this.language = null;
|
||||
this.default = null;
|
||||
this.pages = {};
|
||||
}
|
||||
|
||||
get needsFetch () {
|
||||
return !this.availableLanguages && !this.language && !this.default;
|
||||
}
|
||||
|
||||
fetchLocaleUI () {
|
||||
return fetch(`/locales/${this.appState.language}/ui`).then(response => response.json()).then(response => {
|
||||
this.availableLanguages = response.available;
|
||||
this.default = response.default;
|
||||
this.language = response.locale;
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
fetchLocalePage (page) {
|
||||
return fetch(`/locales/${this.appState.language}/page/${page}`).then(response => response.text()).then(response => {
|
||||
this.pages[page] = response;
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
translate (section, phrase) {
|
||||
let result;
|
||||
let language = this.default;
|
||||
|
||||
if (!this.needsFetch && this.appState.language !== this.language.locale) {
|
||||
console.warn(`The target language (${this.appState.language}) does not exist. Defaulting to ${this.default.name} (${this.default.locale}).`);
|
||||
} else if (typeof this.language[section] == 'undefined' || typeof this.language[section][phrase] == 'undefined') {
|
||||
console.warn(`The translation for "${section}.${phrase}" is not set in the ${this.language.locale} locale. Using ${this.default.name} (${this.default.locale}) instead.`);
|
||||
} else {
|
||||
language = this.language;
|
||||
}
|
||||
|
||||
if (typeof language[section] !== 'undefined' && typeof language[section][phrase] !== 'undefined') {
|
||||
result = language[section][phrase];
|
||||
} else {
|
||||
console.error(`The translation for "${section}.${phrase}" is set up in neither the target nor default locale.`);
|
||||
result = `${section}.${phrase}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
__ (translation) {
|
||||
const pieces = translation.split('.');
|
||||
const result = this.translate(pieces[0], pieces[1]);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import en from './locales/en.json';
|
||||
|
||||
export class I18n {
|
||||
constructor(appState) {
|
||||
// Available languages should be kept up to date with the available locales.
|
||||
this.availableLanguages = {
|
||||
default: en,
|
||||
en,
|
||||
};
|
||||
this.appState = appState;
|
||||
}
|
||||
|
||||
get language () {
|
||||
return this.appState.language;
|
||||
}
|
||||
|
||||
translate (section, phrase) {
|
||||
let result;
|
||||
let language = this.availableLanguages.default;
|
||||
|
||||
if (typeof this.availableLanguages[this.language] == 'undefined') {
|
||||
console.warn(`The target language (${this.language}) does not exist. Defaulting to ${this.availableLanguages.default.name} (${this.availableLanguages.default.locale}).`);
|
||||
} else if (typeof this.availableLanguages[this.language][section] == 'undefined' || typeof this.availableLanguages[this.language][section][phrase] == 'undefined') {
|
||||
console.warn(`The translation for "${section}.${phrase}" is not set in the ${this.language} locale. Using ${this.availableLanguages.default.name} (${this.availableLanguages.default.locale}) instead.`);
|
||||
} else {
|
||||
language = this.availableLanguages[this.language];
|
||||
}
|
||||
|
||||
if (typeof language[section] !== 'undefined' && typeof language[section][phrase] !== 'undefined') {
|
||||
result = language[section][phrase];
|
||||
} else {
|
||||
console.error(`The translation for "${section}.${phrase}" is set up in neither the target nor default locale.`);
|
||||
result = `${section}.${phrase}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
__ (translation) {
|
||||
const pieces = translation.split('.');
|
||||
const result = this.translate(pieces[0], pieces[1]);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
{
|
||||
"name": "English",
|
||||
"locale": "en",
|
||||
"global": {
|
||||
"menu_search": "Search for Books",
|
||||
"menu_about": "About",
|
||||
"menu_login": "Log In / Create Account",
|
||||
"menu_account": "My Profile",
|
||||
"menu_logout": "Log Out",
|
||||
"footer_repo": "Repo",
|
||||
"footer_chat": "Chat",
|
||||
"change_language": "Change Language"
|
||||
},
|
||||
"home": {
|
||||
"logged_out_subtitle": "All the Book Buzz in Once Place",
|
||||
"logged_out_track_books": "Keep track of books you've read, want to read, and are currently reading.",
|
||||
"logged_out_share_friends": "Share your thoughts about what you're reading and see what your friends think of their books.",
|
||||
"logged_out_read_rate": "Rate, review, and recommmend books or something. I dunno. It's early days, my friends!",
|
||||
"logged_out_community_header": "A Look Inside the Hive",
|
||||
"logged_out_recent_reviews": "Recent Reviews",
|
||||
"logged_out_recent_updates": "Recent Updates",
|
||||
"logged_out_join_now": "Join Now!",
|
||||
"logged_in_subtitle": "Welcome!",
|
||||
"logged_in_updates": "Updates",
|
||||
"logged_in_interactions": "Interactions"
|
||||
},
|
||||
"404": {
|
||||
"header": "Oops!",
|
||||
"subheader": "It looks like the page you requested doesn't exist. Please try a different one!"
|
||||
},
|
||||
"login": {
|
||||
"log_in": "Log In",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"login_button": "Log In!",
|
||||
"create_account": "Create a New Account",
|
||||
"confirm_password": "Confirm Password",
|
||||
"username": "Username",
|
||||
"display_name": "Display Name",
|
||||
"create_account_button": "Create Account!",
|
||||
"login_required_field_blank": "You must enter both a valid email address and password.",
|
||||
"create_required_field_blank": "You must complete all required fields.",
|
||||
"create_password_confirm_mismatch": "Both password fields must match."
|
||||
},
|
||||
"search": {
|
||||
"header": "Search",
|
||||
"placeholder": "Search for Books",
|
||||
"button_text": "Search",
|
||||
"search_source_label": "Search Source",
|
||||
"search_source_help_button": "What's This?",
|
||||
"search_source_help_header": "What does \"Search Source\" mean?",
|
||||
"search_source_help_text": "This refers to where the search tries to look for data. Each source can be easily contributed to if you want to add missing books or correct errors.",
|
||||
"search_source_help_inventaire": "Sources and extends data from WikiData, the service structure that powers Wikipedia and the like. Offers as many language options as possible.",
|
||||
"search_source_help_openLibrary": "Sources data from Internet Archive and may provide a digital copy to read. Only offers English unless a work is in another language.",
|
||||
"search_by_label": "Search By",
|
||||
"search_by_title": "Title",
|
||||
"search_by_author": "Author",
|
||||
"loading": "Loading...",
|
||||
"no_results": "None Found",
|
||||
"no_results_suggestion": "If you're expecting book data, go and help fill out the Inventaire database!",
|
||||
"people_header": "People",
|
||||
"series_header": "Series",
|
||||
"books_header": "Books",
|
||||
"see_interaction_details": "See All Interactions",
|
||||
"see_book_details": "See Book Details"
|
||||
},
|
||||
"interaction": {
|
||||
"required": "Required",
|
||||
"reload": "Reload",
|
||||
"heart": "Like",
|
||||
"add": "Add to Shelf",
|
||||
"average_rating": "Average Rating",
|
||||
"reviews_written": "Total Reviews Written"
|
||||
},
|
||||
"api": {
|
||||
"account_already_logged_in": "You are already logged in! You cannot create an account or log in again.",
|
||||
"account_create_required_data_missing": "Could not create account because required data is missing.",
|
||||
"account_create_invalid_email": "The email address entered is not valid.",
|
||||
"account_create_invalid_username": "The username entered is not valid. Usernames must be at least 2 characters long and can only contain letters a–z, numbers 0–9, and underscores",
|
||||
"account_email_exists": "The email address entered is already in use.",
|
||||
"account_username_exists": "The username entered is already in use.",
|
||||
"account_email_send_fail": "Your account was created successfully, but we were unable to send the confirmation email!",
|
||||
"account_confirm_email": "A confirmation email has been sent to the address you specified. Please confirm your account using the link provided.",
|
||||
"account_create_success": "Account created successfully! You may now log in using the email address and password you provided.",
|
||||
"account_confirm_required_data_missing": "Could not confirm account because required data is missing.",
|
||||
"account_confirm_invalid_code": "The specified confirmation code is not valid.",
|
||||
"account_confirm_update_fail": "Something went wrong and we couldn't confirm your account. Please try again later!",
|
||||
"account_confirm_email_send_fail": "Your account has been confirmed, but we were unable to send the email notification about it. You can log in anyway.",
|
||||
"account_confirm_success_email": "Your account has been confirmed, and an email notification has been sent! You may now log in using your email address and password.",
|
||||
"account_confirm_success": "Your account has been confirmed! You may now log in using your email address and password.",
|
||||
"account_login_required_data_missing": "Could not attempt login because required data is missing.",
|
||||
"account_login_invalid_email": "The email address specified does not have an associated account.",
|
||||
"account_login_not_confirmed": "The specified account has not been confirmed. Please use the link you received to confirm your email address.",
|
||||
"account_login_invalid_password": "The password specified is not correct.",
|
||||
"account_login_success": "You have been successfully logged in! You will now be redirected to the home screen.",
|
||||
"account_validate_missing_token": "User not logged in: There is no login token to validate.",
|
||||
"account_validate_invalid_token": "User not logged in: The stored token is not a valid token.",
|
||||
"account_validate_renewed_token": "User logged in, and the token has been renewed."
|
||||
}
|
||||
}
|
|
@ -4,6 +4,9 @@ import headerImage from '../../dev/images/header.png';
|
|||
|
||||
export const globalView = (state, emit, view) => {
|
||||
const { i18n } = state;
|
||||
if (i18n.needsFetch) {
|
||||
return html`<body>Loading...</body>`;
|
||||
}
|
||||
// Create a wrapper for view content that includes global header/footer
|
||||
return html`<body>
|
||||
<header>
|
||||
|
@ -55,9 +58,7 @@ export const globalView = (state, emit, view) => {
|
|||
<label class="flex">
|
||||
<span class="third">${i18n.__('global.change_language')}:</span>
|
||||
<select class="two-third" onchange=${e => emit('set-language', e.target.value)}>
|
||||
${Object.keys(i18n.availableLanguages).map(languageKey => {
|
||||
if (languageKey === 'default') return null;
|
||||
const language = i18n.availableLanguages[languageKey];
|
||||
${i18n.availableLanguages.map(language => {
|
||||
return html`<option value=${language.locale} ${state.language === language.locale ? 'selected' : null}>
|
||||
${language.name}
|
||||
</option>`;
|
||||
|
|
Loading…
Reference in New Issue