npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chayns/textstrings

v1.0.1

Published

This package is supposed to replace the textstring component from chayns-components (both v4 and v5).

Readme

@chayns/textstrings

This package is supposed to replace the textstring component from chayns-components (both v4 and v5).

This package also includes CLI tools to update local textstring files, generate textstring files and create/update textstrings. And of couse the textstring component.

Even though the suggested changes to the component could still be integrated into the v5, the CLI tools should not be a part of the chayns-components (controversial opinion: the component should not be either).

TOC

Config

Create a textstrings.config.js in your project root or call npx textstrings init (or npx txt init)

module.exports = {
    libraries: [
        {
            prefix: 'txt_chayns_',
            libraryName: '<YourLibraryName>',
            // alias: 'shared', optional use different alias and filename for named exports. only relevant when singleFile is false or you have multiple libraries 
            // filePath: undefined, optional configurable paths for each library
        },
        // ...
    ],
    defaultLanguage: 'Ger', // default: 'Ger', not all languages supported yet
    folderPath: './src/constants/textStrings', // folder where the constant files should be created, optional
    singleFile: false, // default: true, only available when using exactly one library, optional
    format: 'json', // 'json' | 'js' | 'ts'
};

This will create a "./src/constants/textStrings/index.js" file from which you can import the textstrings. The index.js imports each library and re-exports them using the LibraryName as key. You can also import the desired library directly.

With the singleFile-option only one file will be created. Using <folderPath>.json as fileName (e.g. /src/constants/textStrings.json).

Note: When not using singleFile camel case will be enforced for file name, even when given an alias.

CLI

npx textstrings init
npx textstrings pull
npx textstrings push (-f, --force) (-d, --delete-unused) (--dry-run)

Short alias:

npx txt init

Note: The old aliases tobit-textstrings and tts have been removed.

Recommended usage

Using a postinstall script which executes the pull command is no longer recommended, because this caused errors when building the project via gitlab without executing push before. Current suggestion executing it is manually.

Still not sure about the best way to execute push-command. Maybe via pre-commit/pre-push/pre-merge hook?

Just running push-command manually whenever you updated/added textstrings might be the best approach though.

Generally you should call the pull-command before editing any textstrings.

textStrings.json format

Notice that you can omit the configured prefix (e.g. txt_chayns_example) in the tree structure

{
    "admin": {
        "dialog": {
            "error": {
                "stringName": "txt_chayns_example_admin_dialog_error",
                "fallback": "Error"
            }
        }
    }
}

When editing the file you can also directly assign the fallback instead of creating an object with stringName and fallback. Also note that the file structure is more important than the stringName, which can also be omitted while editing.

Note: You should execute push-command followed by pull-command before commiting when doing so.

Usage

For usage in your project there would be two options now.

  1. direct
return <Translation textString="txt_chayns_admin_intro_headline" tagName="h1" />;
  1. lookup file
import textstrings from 'src/constants/textStrings.json';

return <Translation textString={textstrings.intro.headline} tagName="h1" />;

Even though the first option seems sufficient, using the lookup file is recommended because it will be auto-generated and is not vulnerable to typos. Also you might lose track of the used textstrings if you don't have a central file which contains all known textstrings.

chayns-components v4/v5

Compare to previous usages with textstring constants file

import textstrings from 'src/constants/textStrings.json';

// well, this seems ok
<Textstring {...textstrings.intro.headline}>
    <h1/>
</Textstring>

// but some projects used an object with name and fallback properties instead
// seems redundant already
<Textstring
    stringName={textstrings.intro.headline.name}
    fallback={textstrings.intro.headline.fallback}
>
    <h1/>
</Textstring>

// using the getTextstring function it is even worse
<h1>{Textstring.getTextstring(textstrings.intro.headline.stringName) || textstrings.intro.headline.fallback}</h1>

// 2nd argument is language, but fallback is 3rd argument
// seems odd, right? well, language argument existed first, this was a simple way to maintain backward support (not the best way though)
<h1>{Textstring.getTextstring(textstrings.intro.headline.stringName, undefined, textstrings.intro.headline.fallback)}</h1>

// own getTextstring util saved the day?
const getTextstring = ({ stringName, fallback }, replacements) => {
    const text = Textstring.getTextstring(stringName, fallback);
    if (!replacements) return text;
    // sometimes this even included a custom replacement function
    ...
};
<h1>{getTextstring(textstrings.intro.headline)}</h1>

// while the above examples refer to chayns-components@4 the v5 has other issues
// 1. all components will be rendered and visible before textstrings are loaded and re-rendered as soon they are loaded
// 2. due to context implementation usage in callbacks (e.g. for dialogs) seems clunky
const headline = useTextstringValue({ textstrings: textstrings.intro.headline });
const description = useTextstringValue({ textstrings: textstrings.intro.description });
... // and all the other textstrings you would potentially use in any dialog

createDialog({
    type: DialogType.ALERT,
    title: headline,
    text: description
}).open()

// what about the replacements if the some variable is not resolved yet because e.g. an api request is necessary first. suddenly you have to do the replacement yourself again (simple and naive example) which is redundant and should not happen
createDialog({
    type: DialogType.ALERT,
    title: headline,
    text: description.replace('##SUBSCRIPTION_END##', new Date(response.subscriptionEnd).toLocaleDateString(getLanguage().active))
}).open();

Versioning of TextStrings

Starting with version 0.7.0, this package supports explicit versioning of TextStrings via the key name. Versioning is non-structural and must not introduce additional nesting levels.

⚠️ Breaking change

If you previously used versioned TextStrings with a _v<n> suffix and accessed them via an additional nesting level (e.g. .v2), you must update your code and remove this nesting level.

Rules

1. Version suffix

A TextString is versioned only if its key ends with:

_v<n>

Where n is an integer >= 2 and the suffix is at the very end of the key.

Valid examples:

  • txt_example_component_title_v2
  • txt_example_component_title_v10

Invalid examples:

  • txt_example_component_title_v1
  • txt_example_component_title_ipv4
  • txt_example_component_title_vpn2

The absence of a version suffix always represents version 1.


2. Object structure

Versioning must not change the object path.

Correct: translations.component.title

Incorrect: translations.component.title.v2

Even if the underlying key contains _v<n>, the object structure remains unchanged.


3. Storage behavior

Version 1:

  • stored under the base key

Version >= 2:

  • stored with the _v<n> suffix

Example resolution for: translations.component.title

Possible keys:

  • txt_example_component_title
  • txt_example_component_title_v2
  • txt_example_component_title_v3

4. Reserved suffix

The suffix pattern _v<n> is reserved exclusively for versioning.

It must not be used for other semantics such as IPv4 or IPv6.

Invalid:

  • ..._v4 (IPv4)
  • ..._v6 (IPv6)

Use explicit alternatives instead, e.g.:

  • _ipv4
  • _ipv6

Summary

  • Versioning is key-based, not structure-based
  • _v<n> is the only valid version marker
  • Version 1 is implicit
  • Object paths never change due to versioning

Component

Provider

import { Suspense } from 'react';

const libraries = useMemo(() => {
    const res = ['<yourLibrary_shared'];
    if (isAdminMode) {
        res.push('<yourLibrary_admin');
    }
}, [isAdminMode])

return (
    <Suspense>
        <TextStringProvider
            libraries={libraries}
        >
            <App>
        </TextStringProvider>
    <Suspense>
);

TextStringProvider will suspend on mount when initially loading the libraries, but will not suspend when loading additional libraries or changing the language. TextStrings will then use the previous language until the new language finished loading.

Suspense is handled internally and you no longer need to add a Suspense-Boundary yourself when using v0.5.0-beta.0 or higher.

For usage without chayns-api you can also use the TextStringProviderBase which does not have the dependency to chayns-api and thus also does not support some features like automatic language detection or opening the edit dialog.

Translation

    import { txtPersonsAdmin } from '/src/constants/textstrings'
    const stringName = 'txt_example';

    // just renders the text without surrounding html-tag (default is span)
    // since there is no html-element to assign a event-listener to this will not have the CTRL+click listener
    return (
        <Translation textString={stringName} tagName={null}/>
    );
    // you can either pass the stringName without fallback directly or an object with stringName and fallback (optional)
    return (
        <>
            <Translation textString={txtPersonsAdmin.admin.error.alert}/>
            <Translation textString={txtPersonsAdmin.admin.error.alert.stringName}/>
        </>
    );

    // as above with replacements
    return (
        <Translation textString={stringName} replacements={{ 'COUNT': 2 }}/>
    );

    // renders a span with the text
    // this will have the CTRL+click listener
    return (
        <Translation textString={stringName}/>
    );

    // using the hook
    const { t } = useTranslation();
    ...
    createDialog({
        type: DialogType.ALERT,
        text: t(stringName, { 'COUNT': 2 }),
    }).open();

    createDialog({
        type: DialogType.ALERT,
        text: t(txtPersonAdmin.admin.error.alert, { 'COUNT': 2 }),
    }).open()

    // outside react context, getFixedT behaves same as t-function but is bound to the default store
    import { getFixedT } from '@chayns/textstrings';
    createDialog({
        type: DialogType.ALERT,
        text: getFixedT(txtPersonAdmin.admin.error.alert, { 'COUNT': 2 }),
    }).open()

    // renderProp
    return (
        <Translation>
             {(t, onClick) => <div onClick={onClick}>{t(stringName)}</div>}
        </Translation>
    )

When you have to use dangerouslySetInnerHTML either use the renderProp or the hook. The lack of a "useDangerousSetInnerHTML" (v4) or "isHTML" (v5) prop is an intentional design choice. While the first still includes the hint that this is dangerous according to the design choice by react, the latter looks like it is safe to use.

If possible try to avoid using textstrings which require dangerouslySetInnerHTML. Also check the documentation to dangerouslySetInnerHTML if you are not familiar with the security risks.

const stringName = 'txt_example';

// using hook. be aware that this will not have the CTRL+click handler
const { t } = useTranslation();
return <div dangerouslySetInnerHTML={{ __html: t(stringName) }} />;

// using renderProp. this will have the CTRL+click but only if you pass the onClick
return <Translation textString={stringName}>{(text, onClick) => <div dangerouslySetInnerHTML={{ __html: text }} onClick={onClick} />}</Translation>;

Replacements

When using replacements the hashtags are automatically added now. Instead of { '##count##': 1 } you can simply write { count: 1 } and this will replace ##count## in the text. Explicitly adding the hashtags in the replacement object will not work though.

Plurals

New handling for plurals has been added and gives count special meaning in replacements. You can define separate textstrings for counts of zero and one. The used textstring will the automatically determined by the count.

{
    "zero": {
        "stringName": "YOUR_PREFIX_NAME_zero",
        "fallback": "Keine Person"
    },
    "one": {
        "stringName": "YOUR_PREFIX_NAME_one",
        "fallback": "Eine Person"
    },
    "other": {
        "stringName": "YOUR_PREFIX_NAME_other",
        "fallback": "##count## Personen"
    }
}

Formatting

When passing numbers in replacements those will be formatted with toLocaleString by default (without options). Besides count you could format those yourself or pass formatOptions.

<Translation
    textString={YOUR_TEXTSTRING}
    replacements={{ price: { value: 5.24, formatOptions: { style: 'currency', currency: 'EUR' } } }}
/>

Nesting

Also support for nested textstrings has been added.

Basic example:

{
    "nesting1": "1 $t(nesting2)",
    "nesting2": "2 $t(nesting3)",
    "nesting3": "3"
}

Example with options:

{
    "outer": {
        "stringName": "YOUR_PREFIX_NAME_nesting",
        "fallback": "Sie haben $t(\"YOUR_PREFIX_NAME_boys\", { \"count\": \"boys\" }) und $t(\"YOUR_PREFIX_NAME_girls\", { \"count\": \"girls\" })"
    },
    "boys": {
        "stringName": "YOUR_PREFIX_NAME_boys",
        "fallback": "##count## Jungs"
    },
    "girls": {
        "stringName": "YOUR_PREFIX_NAME_girls",
        "fallback": "##count## Mädchen"
    }
}

The above example has been simplified by not using different textstrings depending on counts but that is possible too.

React Replace

Replacements can only be strings/numbers and no components. In case you have a textString with a link which requires a custom onClick-handling you can use the TranslationReactReplace-component.

Assume following result as example:

Lorem ipsum. <a onClick={selectPage({ id: 93 })}>Hier klicken</a> für weitere Informationen.

Create two textStrings e.g. like this:

linkOuter: Lorem ipsum. ##link## für weitere Informationen.
linkInner: Hier klicken
import { TranslationReactReplace } from '@chayns/textstrings';

return (
    <TranslationReactReplace
        textString="linkOuter"
        regex="##link##"
    >
        {(match) => (
            <a key={match} onClick={() => selectPage({ id: 93 })}>
                <Translation textString="linkInner"/>
            </a>
        )}
    </TranslationReactReplace>
);

Make sure to give components returned in the renderProp a key. Also when using a regex it is important that it has exactly one capture group. However, you may use as many non-capturing groups as you want.

"##link##" is basically the same as /(##link##)/.

For multiple replacements you will need to use a RegExp like /(##link1##|##link2##)/, /##(link1|link2)##/ or /##(link[01])##/. Be aware parts which are not part of the capture group will not be passed as match to the renderProp.

If you only need exactly one replacement you could also pass the replacement directly as child. Note: This will only replace the first match, all other matches will be replaced with null.

import { TranslationReactReplace } from '@chayns/textstrings';

return (
    <TranslationReactReplace
        textString="linkOuter"
        regex="##link##"
    >
        <a onClick={() => selectPage({ id: 93 })}>
            <Translation textString="linkInner"/>
        </a>
    </TranslationReactReplace>
);

Server-side Rendering (SSR)

Example code for usage in SSR. The TextStringProviderSSR will render the TextStringProvider and make sure that the correct language is loaded during SSR.

Requires at least [email protected] which added the 4th parameter in the withHydrationBoundary-HOC.

Without useHydrationId

import { TextStringProviderSSR } from '@chayns/textstrings';

return (
    <ChaynsProvider>
        ...
        <TextStringProviderSSR id="<your project name or unique id>/textStrings" libraries={libraries}>
            {children}
        </TextStringProviderSSR>
        ...
    </ChaynsProvider>
);

With useHydrationId

import { withTextStringProviderSSR } from '@chayns/textstrings';

const useHydrationId = () => {
    const { id } = useCustomData();
    return `<your project name or unique id>/${id}`;
};

const TextStringProviderSSR = withTextStringProviderSSR(useHydrationId);

return (
    <ChaynsProvider>
        ...
        <TextStringProviderSSR libraries={libraries}>{children}</TextStringProviderSSR>
        ...
    </ChaynsProvider>
);

Publishing

To publish the package push your changes and merge to main.

Use npm version patch|minor|major|prerelease [--preid beta] on main branch.

Push the generated commit and also push the tag to trigger build_publish via gitlab-ci.