I am currently working on an app that does translation work. The English language should be used as the default setting. The actual translation is done by ngx-translate. It is temporarily stored in the variable translateService, which is declared in the constructor.
the constructor:
constructor(private translateService: TranslateService, private platform: Platform, private globalization: Globalization, private storage: Storage)
The variable defaultLang is provided with the string 'en', which stands for English. The translateService now receives the variable defaultLang. But still, English is not taken as the default language. What is the reason for this?
async initialize() {
if (this.isInitialized) { return Promise.resolve(); } if (this.initializeListeners.length > 0) { // a parallel call has already been started
return new Promise(success => { this.initializeListeners.push(success); });
} return new Promise(async success => {
let defaultLang = 'en'; this.translateService.setDefaultLang(defaultLang); this.initializeListeners.push(success); await this.platform.ready(); let lang = await this.storage.get(LanguageService.STORAGE_KEY); if (!lang && checkAvailability(Globalization.getPluginRef(), null, Globalization.getPluginName()) === true) {
// no previous language found -> get device language
try {
let preferredLanguage = await this.globalization.getPreferredLanguage(); if (preferredLanguage && preferredLanguage.value) {
lang = preferredLanguage.value; if (lang.indexOf('-') > -1) { lang = lang.split('-')[0]; } if (lang.indexOf('_') > -1) { lang = lang.split('_')[0]; } lang = lang.toLowerCase(); if (this.availableLanguages.indexOf(lang) == -1) {
// detected language is not available
lang = null;
}
}
} catch (e) { }
} if (!lang) { lang = defaultLang; } this.textDirection = this.rtlLanguages.indexOf(lang) >= 0 ? 'rtl' : 'ltr';
this.platform.setDir(this.rtlLanguages.indexOf(lang) >= 0 ? 'rtl' : 'ltr', true); await this.translateService.use(lang); setTimeout(() => {
// use timeout to let language switch propagate
this.isInitialized = true; this.initializeListeners.map(success => { success(); }); this.initializeListeners = null;
}, 100);
});
}
I assumed that the declaration of a variable passing the corresponding string would be sufficient.
Related
I have the following code, which is very repetitious:
const flags = {
get logged() {
return localStorage.getItem("logged") === "true";
},
set logged(val: boolean) {
if (val) {
localStorage.setItem("logged", "true");
} else {
localStorage.removeItem("logged");
}
},
get notificationsMuted() {
return localStorage.getItem("notifications-muted") === "true";
},
set notificationsMuted(val: boolean) {
if (val) {
localStorage.setItem("notifications-muted", "true");
} else {
localStorage.removeItem("notifications-muted");
}
}
}
As you can see, the get and set for each flag type is identical, save for the property names. I would like to do something like this instead:
function getter(prop: string) {
return localStorage.getItem(prop) === "true";
}
function setter(prop: string, val: boolean) {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
}
const flags = {
get logged: getter("logged")
set logged: setter("logged")
get notificationsMuted: getter("notifications-muted")
set notificationsMuted: setter("notifications-muted")
}
But I'm not sure if Javascript / Typescript has support for this sort of thing. Is such a thing possible, and if so, how? If not, is there any other way I can cut down on the repetition here?
You can use a proxy with get and set traps, use TS types to allow only props you wish to handle (TS playground)):
interface Flags {
logged: boolean,
'notifications-muted': boolean;
}
type Prop = keyof Flags;
const handlers = {
get(_: Flags, prop: Prop) {
return localStorage.getItem(prop) === "true";
},
set(_: Flags, prop: Prop, val: any) {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
return true;
}
};
const flags = new Proxy<Flags>({} as Flags, handlers);
All you really need is to use Object.defineProperty with an object with a get and set properties. Or, with multiple properties, use Object.defineProperties to define them all at once.
One approach which will help with code organization is to not use lots of local storage keys, but instead use a single object that gets stored.
const props = ['logged', 'notificationsMuted'] as const;
const defaultStorage = Object.fromEntries(props.map(prop => [prop, false]));
const getStorage = () => JSON.parse(localStorage.getItem('settings') || JSON.stringify(defaultStorage));
const flags = Object.defineProperties(
{},
Object.fromEntries(
props.map(
prop => [
prop,
{
get: () => getStorage()[prop],
set: (newVal: boolean) => {
const store = getStorage();
store.prop = newVal;
localStorage.setItem('settings', JSON.stringify(store));
}
}
]
)
)
) as Record<(typeof props)[number], boolean>;
This is the current "best" solution I can come up with. Open to anyone who can provide an improvement over this:
function getter(prop: string): boolean {
return localStorage.getItem(prop) === "true";
}
function setter(prop: string, val: boolean): void {
if (val) {
localStorage.setItem(prop, "true");
} else {
localStorage.removeItem(prop);
}
}
const flags = {
get logged() { return getter("logged") },
set logged(val: boolean) { setter("logged", val) },
get notificationsMuted() { return getter("notifications-muted"); },
set notificationsMuted(val: boolean) { setter("notifications-muted", val); }
}
In this code I retrieve data about a country as an observable. I then try to compare my string this.city with this.capital which I retrieved from the Observable. If the two do not equal I want to display a new paragraph in the html by changing the hidden boolean to false. I know that this.city and the observable this.capital are not equal but it does not display the paragraph in the html after calling showHeader().
I wonder if you can compare Observable data with strings in this way?
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { SettingsPage } from '../../pages/settings/settings';
import { Storage } from '#ionic/storage';
import { CityDataProvider } from '../../providers/city-data/city-data';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
hidden: boolean = true;
hiddenTwo: boolean = true;
city: string;
cityData: any[];
capital: string;
cityLowerCase: string;
constructor(public navCtrl: NavController, private storage: Storage, private cdp: CityDataProvider) {
}
async ionViewWillEnter() {
const data = await this.storage.get("city")
.then((value) => {
if (value == null) { this.hidden = false; } else if (value !== null) { this.hidden = true; }
this.city = value;
})
.catch((error) => {
alert("Error accessing storage.")
})
this.cdp.getCityData(this.city).subscribe(data => {
this.cityData = data;
this.capital = data[0].capital.toString().toLowerCase();
this.cityLowerCase = this.city.toLowerCase();
this.showHeader(this.cityLowerCase, this.capital);
});
}
showHeader(a: string, b: string) {
if (a != b){
this.hiddenTwo = false;
}
}
openSettingsPage() {
this.navCtrl.push(SettingsPage);
};`enter code here`
}
As you are using then on this.storage.get("city"), this.city has not been set yet when you call this.cdp.getCityData(this.city). Just use await properly.
Also, some basic tips:
if(a == b){...} else if(a != b){...} is essentially just if(a == b){...}else{...}
if(condition){value = true}else{value = false} is essentially just value = condition
async ionViewWillEnter() {
try
{
this.city = await this.storage.get("city");
this.hidden = this.city == null;
}
catch (error)
{
alert("Error accessing storage.")
}
this.cdp.getCityData(this.city).subscribe(data =>
{
this.cityData = data;
this.capital = data[0].capital.toString().toLowerCase();
this.cityLowerCase = this.city.toLowerCase();
this.showHeader(this.cityLowerCase, this.capital);
});
}
showHeader(a: string, b: string) {
this.hiddenTwo = a != b;
}
With Jupyter Notebooks, I could have a cell
%%javascript IPython.notebook.kernel.execute('x = 42')
Then, elsewhere in the document a Python code cell with x would show it bound to 42 as expected.
I'm trying to produce something similar with JupyterLab. I understand I'm supposed to write a plugin rather than using ad-hoc JS, and that's fine, but I'm not finding an interface to the kernel similar to the global IPython from notebooks:
import { JupyerLab, JupyterLabPlugin } from '#jupyterlab/application';
const extension: JupyterLabPlugin<void> = {
// ...
requires: [],
activate: (app: JupyterLab) => {
// can I get to Python evaluation through app?
// by adding another class to `requires` above?
}
}
export default extension;
Here's a hacky attempt that "works". Could still use advice if anyone knows where there is a public promise for the kernel being ready, how to avoid the intermediate class, or any other general improvements:
import { JupyterLab, JupyterLabPlugin } from '#jupyterlab/application';
import { DocumentRegistry } from '#jupyterlab/docregistry';
import { INotebookModel, NotebookPanel } from '#jupyterlab/notebook';
import { IDisposable, DisposableDelegate } from '#phosphor/disposable';
declare global {
interface Window {
'execPython': {
'readyState': string,
'exec': (code: string) => any,
'ready': Promise<void>
} | null
}
}
class ExecWidgetExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
createNew(nb: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
if (window.execPython) {
return;
}
window.execPython = {
'readyState': 'waiting',
'exec': null,
'ready': new Promise((resolve) => {
const wait = setInterval(() => {
if (!context.session.kernel || window.execPython.readyState === 'ready') {
return;
}
clearInterval(wait);
window.execPython.readyState = 'ready';
window.execPython.exec = (code: string) =>
context.session.kernel.requestExecute({ code }, true);
resolve();
}, 50);
})
};
// Usage elsewhere: execPython.ready.then(() => execPython.exec('x = 42').done.then(console.log, console.error))
return new DisposableDelegate(() => {});
}
}
const extension: JupyterLabPlugin<void> = {
'id': 'jupyterlab_foo',
'autoStart': true,
'activate': (app: JupyterLab) => {
app.docRegistry.addWidgetExtension('Notebook', new ExecWidgetExtension())
}
};
export default extension;
I am working on a react project but I think this question relates to all JS
I have a Token file that contains the following functions:
export default {
set(token) {
Cookies.set(key, token);
return localStorage.setItem(key, token);
},
clear() {
Cookies.remove(key);
return localStorage.removeItem(key);
},
get() {
try {
const retVal = localStorage.getItem(key) || '';
return retVal;
} catch (e) {
return '';
}
},
Now I want to add a set of what are essentially environment variables for the domain of these 3 functions. In my case its based on window.location.hostname but could really be anything.
In this instance lets say we want key to be dev, uat or prod based on window.location.hostname
getKey = host => {
if(host === 'a')
return 'dev'
elseIf (host === 'b')
return 'uat'
else
return 'prod'
}
I think the above is fairly standard to return the key you want. but what if your key has 6 vars, or 8, or 20. How could you set all the vars so that when you call set(), clear() and get() that they have access to them?
Basically I want to wrap the export in a function that sets some vars?
To illustrate this a bit more
class session extends Repo {
static state = {
current: false,
};
current(bool) {
this.state.current = bool;
return this;
}
query(values) {
<Sequelize Query>
});
}
export session = new Session();
using this I can call current(true).session() and sessions state would be set to true. I want to apply a similar pattern to the Token file but I don't want to change all my calls from Token.set(token) to Token.env(hostname).set(token)
This acomplished what I wanted, I had to call the function from within the others as window is not available on load. It essentially illustrates the pattern I was looking for. Thanks for Jim Jeffries for pointing me towards the answer.
class Token {
constructor(props) {
this.state = {
testKey: null,
};
}
setCred = host => {
if (host === 'uat') {
this.state.testKey = 'uat';
} else if (host === 'prod') {
this.state.testKey = 'prod';
} else {
this.state.testKey = 'dev';
}
};
set(token) {
this.setCred(window.location.hostname);
Cookies.set(testKey, token);
return localStorage.setItem(testKey, token);
}
clear() {
this.setCred(window.location.hostname);
Cookies.remove(testKey);
return localStorage.removeItem(testKey);
}
get() {
this.setCred(window.location.hostname);
try {
const retVal = localStorage.getItem(key) || '';
return retVal;
} catch (e) {
return '';
}
}
}
export default new Token();
If anyone else has another idea please share.
My Problem is with the following function:
() => this.handleRes(res, this.lobbyInitFn(this.$log, this.lobbyData))
What is the best way to get a reference to my lobbyData variable on the Ctrl?
As a result of the fat arrow syntax I get a pointer to the global window object.
At the moment I just get a reference to $log function in the lobbyInitFn method and an undefined for the dataCol parameter.
module lobby.controllers {
'use strict';
class LobbyCtrl{
public lobbyData : Array<string>;
constructor(private $scope, private $log : ng.ILogService, private lobbyStorage) {
this.init();
}
private init(){
this.initializeLobbyData();
}
private initializeLobbyData(){
var res = this.lobbyStorage.LobbyRoom().query(
() => this.handleRes(res, this.lobbyInitFn(this.$log, this.lobbyData)),
() => this.handleErr("Error while initializing the lobby data."));
}
private lobbyInitFn(logger, dataCol){
return function(data){
for (var i = 0; i < data.length; ++i) {
logger.log(data[i]);
}
dataCol = data;
}
}
// Common functions => outsourcing
private handleRes(res : any, resFn? : callbackFn) {
if(typeof resFn != 'undefined') {
resFn(res)
} else{
this.$log.log(res);
}
}
private handleErr(err : string, errFn? : callbackFn) {
if(typeof errFn != 'undefined') {
errFn(err)
} else{
this.$log.error(err);
}
}
}
Can you try:
() => this.handleRes(res, () => this.lobbyInitFn(this.$log, this.lobbyData))