fnando / i18n

A small library to provide the I18n translations on the JavaScript.
https://fnando.github.io/i18n/
MIT License
169 stars 20 forks source link

Bug: Cannot read property 'get' of undefined #84

Closed thomas-claireau closed 9 months ago

thomas-claireau commented 1 year ago

Description

When I tried to load some translations with i18n-js, I have an error like this one :

CleanShot 2023-10-21 at 08 16 55@2x

How to reproduce

My repository is a Solito monorepo and I want to use your library to translate the React Native part.

To reproduce this error :

What do you expect

[Describe what do you expect to happen]

I would like there to be no more errors when calling the get method

Full backtrace

TypeError: Cannot read property 'get' of undefined

This error is located at:
    in Header (created by HomeScreen)
    in RCTView (created by View)
    in View
    in NativeWind.View
    in NativeWind.NoName
    in Unknown (created by HomeScreen)
    in HomeScreen (created by Home)
    in Home
    in Unknown (created by Route(index))
    in Route (created by Route(index))
    in Route(index) (created by SceneView)
    in StaticContainer
    in EnsureSingleNavigator (created by SceneView)
    in SceneView (created by SceneView)
    in RCTView (created by View)
    in View (created by DebugContainer)
    in DebugContainer (created by MaybeNestedStack)
    in MaybeNestedStack (created by SceneView)
    in RCTView (created by View)
    in View (created by SceneView)
    in RNSScreen
    in Unknown (created by InnerScreen)
    in Suspender (created by Freeze)
    in Suspense (created by Freeze)
    in Freeze (created by DelayedFreeze)
    in DelayedFreeze (created by InnerScreen)
    in InnerScreen (created by Screen)
    in Screen (created by SceneView)
    in SceneView (created by NativeStackViewInner)
    in Suspender (created by Freeze)
    in Suspense (created by Freeze)
    in Freeze (created by DelayedFreeze)
    in DelayedFreeze (created by ScreenStack)
    in RNSScreenStack (created by ScreenStack)
    in ScreenStack (created by NativeStackViewInner)
    in NativeStackViewInner (created by NativeStackView)
    in RCTView (created by View)
    in View (created by SafeAreaInsetsContext)
    in SafeAreaProviderCompat (created by NativeStackView)
    in NativeStackView (created by NativeStackNavigator)
    in PreventRemoveProvider (created by NavigationContent)
    in NavigationContent
    in Unknown (created by NativeStackNavigator)
    in NativeStackNavigator
    in Unknown (created by Root)
    in RNCSafeAreaProvider (created by SafeAreaProvider)
    in SafeAreaProvider (created by Provider)
    in Provider (created by Root)
    in Root
    in Unknown (created by Route())
    in Route (created by Route())
    in Route() (created by ContextNavigator)
    in RNCSafeAreaProvider (created by SafeAreaProvider)
    in SafeAreaProvider (created by wrapper)
    in RCTView (created by View)
    in View (created by GestureHandlerRootView)
    in GestureHandlerRootView (created by GestureHandlerRootView)
    in GestureHandlerRootView (created by wrapper)
    in wrapper (created by ContextNavigator)
    in EnsureSingleNavigator
    in BaseNavigationContainer
    in ThemeProvider
    in NavigationContainerInner (created by ContextNavigator)
    in ContextNavigator (created by ExpoRoot)
    in ExpoRoot (created by App)
    in App (created by withDevTools(App))
    in withDevTools(App) (created by ErrorOverlay)
    in ErrorToastContainer (created by ErrorOverlay)
    in ErrorOverlay
    in RCTView (created by View)
    in View (created by AppContainer)
    in RCTView (created by View)
    in View (created by AppContainer)
    in AppContainer
    in main(RootComponent), js engine: hermes
patlux commented 11 months ago

Same issue

stout-ni commented 9 months ago

I also encountered this issue, and my solution was to abandon this library and implement a global i18n solution using createContext myself.

import React from "react";
import { getLocales } from "expo-localization";
import * as SecureStore from "expo-secure-store";

import type { Settings } from "@/interfaces";
import en from "@/locales/en.json";
import zh from "@/locales/zh.json";

type Locale = "zh" | "en";

const fallbackLocale: Locale = "zh";
const translations: Record<Locale, Record<string, string>> = {
  en,
  zh,
};

export interface ILocaleContext {
  t: (key: string) => string;
  locale: Locale;
}
export interface ILocaleProviderProps {}

export const LocaleContext = React.createContext<ILocaleContext>({
  t: (key: string) => translations[fallbackLocale][key],
  locale: fallbackLocale,
});
export const LocaleProvider = (
  props: React.PropsWithChildren<ILocaleProviderProps>
) => {
  const { children } = props;

  const settings = SecureStore.getItem("settings") || "{}";

  const locale = React.useMemo<Locale>(
    () =>
      // First select the language based on the user's preferred setting.
      (JSON.parse(settings) as Settings).locale ||
      // Then consider the system's preferred language.
      getLocales()[0].languageTag ||
      // If both are unavailable, use the default language.
      fallbackLocale,
    [settings]
  );

  const t = React.useCallback(
    (key: string) => translations[locale][key],
    [locale]
  );

  return (
    <LocaleContext.Provider
      value={{
        t,
        locale,
      }}
    >
      {children}
    </LocaleContext.Provider>
  );
};
fnando commented 9 months ago

@patlux @thomas-claireau can you please post a snippet of code reproducing the issue? Extra points if it's not a react-native project.

zRelux commented 9 months ago

Just faced a similar issue, I was trying to spread i18n and that was causing issues. Then I changed t like under and now works

export const useTranslations = () => {
  const [locale, setLocale] = useState(i18n.locale || 'en');

  const handleLanguageChange = (newLocale: string) => {
    i18n.locale = newLocale;
    setLocale(newLocale); // This will trigger a re-render
  };

  return { handleLanguageChange, locale, t: (...args) => i18n.t(args) };
};
fnando commented 9 months ago

@zRelux When you do const t = i18n.t, your not necessarily binding this to the object that owns the function. Instead, you need to use const t = i18n.t.bind(i18n). This will ensure that t is bound to i18n.

That said, I don't think it's the same issue @thomas-claireau posted. Is it?

fnando commented 9 months ago

I'm closing this issue as nobody included a sample that reproduces the issue. Feel free to reopen it once a sample is available.

viernullvier commented 3 months ago

@fnando I ran into the same issue with the latest 4.4.3 release - here's a minimal setup to reproduce:

const i18n = new I18n({
  en: {
    hello: "world",
  },
})

console.log(i18n.t("hello")) // this one works
const t = i18n.t
console.log(t("hello")) // this one fails
const t2 = i18n.t.bind(i18n)
console.log(t2("hello")) // this one works again

With later versions of i18n-js, the error message actually changes to Cannot read property 'locale' of undefined instead of get, so it took me a moment to find this GH issue.

Maybe one of the following options would be helpful for future users:

  1. Mention this pitfall and the bind-based workaround in the README.
  2. Refactor the class or its method declarations so that all of the public methods are already bound to the right this value from the start, which would make destructuring risk-free.