From a904e07d0708395068c3b55f70a6c5ce13f9e315 Mon Sep 17 00:00:00 2001 From: Viktor Andersson <30777521+VIKTORVAV99@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:46:34 +0200 Subject: [PATCH] create a isString helper to avoid code duplication (#1763) * create a isString helper to avoid code duplication * add test --- src/TransWithoutContext.js | 12 ++++++------ src/useTranslation.js | 8 ++++---- src/utils.js | 14 ++++++++------ test/utils.spec.js | 15 +++++++++++++++ 4 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 test/utils.spec.js diff --git a/src/TransWithoutContext.js b/src/TransWithoutContext.js index e047913c0..f6d00f103 100644 --- a/src/TransWithoutContext.js +++ b/src/TransWithoutContext.js @@ -1,6 +1,6 @@ import { Fragment, isValidElement, cloneElement, createElement, Children } from 'react'; import HTML from 'html-parse-stringify'; -import { warn, warnOnce } from './utils.js'; +import { isString, warn, warnOnce } from './utils.js'; import { getDefaults } from './defaults.js'; import { getI18n } from './i18nInstance.js'; @@ -44,7 +44,7 @@ export const nodesToString = (children, i18nOptions) => { // e.g. lorem
ipsum {{ messageCount, format }} dolor bold amet childrenArray.forEach((child, childIndex) => { - if (typeof child === 'string') { + if (isString(child)) { // actual e.g. lorem // expected e.g. lorem stringNode += `${child}`; @@ -66,7 +66,7 @@ export const nodesToString = (children, i18nOptions) => { // e.g. // expected e.g. "<0>", not e.g. "<0><0>a<1>b" stringNode += `<${childIndex}>`; - } else if (shouldKeepChild && childPropsCount === 1 && typeof childChildren === 'string') { + } else if (shouldKeepChild && childPropsCount === 1 && isString(childChildren)) { // actual e.g. dolor bold amet // expected e.g. dolor bold amet stringNode += `<${child.type}>${childChildren}`; @@ -121,7 +121,7 @@ const renderNodes = (children, targetString, i18n, i18nOptions, combinedTOpts, s const childrenArray = getAsArray(childs); childrenArray.forEach((child) => { - if (typeof child === 'string') return; + if (isString(child)) return; if (hasChildren(child)) getData(getChildren(child)); else if (typeof child === 'object' && !isValidElement(child)) Object.assign(data, child); }); @@ -209,7 +209,7 @@ const renderNodes = (children, targetString, i18n, i18nOptions, combinedTOpts, s children !== null && Object.hasOwnProperty.call(children, node.name); - if (typeof child === 'string') { + if (isString(child)) { const value = i18n.services.interpolator.interpolate(child, opts, i18n.language); mem.push(value); } else if ( @@ -331,7 +331,7 @@ export function Trans({ // prepare having a namespace let namespaces = ns || t.ns || (i18n.options && i18n.options.defaultNS); - namespaces = typeof namespaces === 'string' ? [namespaces] : namespaces || ['translation']; + namespaces = isString(namespaces) ? [namespaces] : namespaces || ['translation']; const nodeAsString = nodesToString(children, reactI18nextOptions); const defaultValue = diff --git a/src/useTranslation.js b/src/useTranslation.js index f41b7e07e..f180e881e 100644 --- a/src/useTranslation.js +++ b/src/useTranslation.js @@ -1,6 +1,6 @@ import { useState, useEffect, useContext, useRef, useCallback } from 'react'; import { getI18n, getDefaults, ReportNamespaces, I18nContext } from './context.js'; -import { warnOnce, loadNamespaces, loadLanguages, hasLoadedNamespace } from './utils.js'; +import { warnOnce, loadNamespaces, loadLanguages, hasLoadedNamespace, isString } from './utils.js'; const usePrevious = (value, ignore) => { const ref = useRef(); @@ -30,11 +30,11 @@ export const useTranslation = (ns, props = {}) => { if (!i18n) { warnOnce('You will need to pass in an i18next instance by using initReactI18next'); const notReadyT = (k, optsOrDefaultValue) => { - if (typeof optsOrDefaultValue === 'string') return optsOrDefaultValue; + if (isString(optsOrDefaultValue)) return optsOrDefaultValue; if ( optsOrDefaultValue && typeof optsOrDefaultValue === 'object' && - typeof optsOrDefaultValue.defaultValue === 'string' + isString(optsOrDefaultValue.defaultValue) ) return optsOrDefaultValue.defaultValue; return Array.isArray(k) ? k[k.length - 1] : k; @@ -56,7 +56,7 @@ export const useTranslation = (ns, props = {}) => { // prepare having a namespace let namespaces = ns || defaultNSFromContext || (i18n.options && i18n.options.defaultNS); - namespaces = typeof namespaces === 'string' ? [namespaces] : namespaces || ['translation']; + namespaces = isString(namespaces) ? [namespaces] : namespaces || ['translation']; // report namespaces as used if (i18n.reportNamespaces.addUsedNamespaces) i18n.reportNamespaces.addUsedNamespaces(namespaces); diff --git a/src/utils.js b/src/utils.js index e6ab684a2..34552444b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,7 @@ // Do not use arrow function here as it will break optimizations of arguments export function warn(...args) { if (console && console.warn) { - if (typeof args[0] === 'string') args[0] = `react-i18next:: ${args[0]}`; + if (isString(args[0])) args[0] = `react-i18next:: ${args[0]}`; console.warn(...args); } } @@ -9,8 +9,8 @@ export function warn(...args) { const alreadyWarned = {}; // Do not use arrow function here as it will break optimizations of arguments export function warnOnce(...args) { - if (typeof args[0] === 'string' && alreadyWarned[args[0]]) return; - if (typeof args[0] === 'string') alreadyWarned[args[0]] = new Date(); + if (isString(args[0]) && alreadyWarned[args[0]]) return; + if (isString(args[0])) alreadyWarned[args[0]] = new Date(); warn(...args); } @@ -18,7 +18,7 @@ export function warnOnce(...args) { // // export function deprecated(...args) { // if (process && process.env && (!process.env.NODE_ENV || process.env.NODE_ENV === 'development')) { -// if (typeof args[0] === 'string') args[0] = `deprecation warning -> ${args[0]}`; +// if (isString(args[0])) args[0] = `deprecation warning -> ${args[0]}`; // warnOnce(...args); // } // } @@ -46,7 +46,7 @@ export const loadNamespaces = (i18n, ns, cb) => { // should work with I18NEXT >= v22.5.0 export const loadLanguages = (i18n, lng, ns, cb) => { // eslint-disable-next-line no-param-reassign - if (typeof ns === 'string') ns = [ns]; + if (isString(ns)) ns = [ns]; ns.forEach((n) => { if (i18n.options.ns.indexOf(n) < 0) i18n.options.ns.push(n); }); @@ -127,4 +127,6 @@ export const hasLoadedNamespace = (ns, i18n, options = {}) => { export const getDisplayName = (Component) => Component.displayName || Component.name || - (typeof Component === 'string' && Component.length > 0 ? Component : 'Unknown'); + (isString(Component) && Component.length > 0 ? Component : 'Unknown'); + +export const isString = (obj) => typeof obj === 'string'; diff --git a/test/utils.spec.js b/test/utils.spec.js new file mode 100644 index 000000000..ca63b2c8c --- /dev/null +++ b/test/utils.spec.js @@ -0,0 +1,15 @@ +import { describe, it, expect } from 'vitest'; +import { isString } from '../src/utils.js'; + +describe('isString', () => { + it('should return true for strings', () => { + expect(isString('string')).toBe(true); + }); + + it.each([[undefined], [null], [1], [{}], [[]], [() => {}]])( + 'should return false for non-strings, testing %o', + (value) => { + expect(isString(value)).toBe(false); + }, + ); +});