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.
{['a', 'b'].map(item => ( - {item}
))}
// expected e.g. "<0>0>", not e.g. "<0><0>a0><1>b1>0>"
stringNode += `<${childIndex}>${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}${child.type}>`;
@@ -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);
+ },
+ );
+});