diegomura / react-pdf

📄 Create PDF files using React
https://react-pdf.org
MIT License
14.66k stars 1.16k forks source link

Translation not working inside of PDF document #1942

Open mirankorkmaz opened 2 years ago

mirankorkmaz commented 2 years ago

I'm using the useTranslation hook in react to place inside my , however it isn't rendering out the text, only the name of variable I'm declaring. Is this a well known issue or should it supposed to work if done correct?

sagargulati commented 2 years ago

+1

https://github.com/diegomura/react-pdf/issues/1734#issuecomment-1190760837

mirankorkmaz commented 2 years ago

@sagargulati I might be solving this issue tomorrow, I will let you know if I come up with a solution.

sagargulati commented 2 years ago

I'm also trying; let me know if you find a fix!

dragi-ns commented 2 years ago

Not sure if this will help you guys and there is probably a better solution, but I had the same problem with react-pdf and I solved it by wrapping react-pdf Document with react-i18next I18nextProvider.

const blob = await pdf(
  <I18nextProvider i18n={i18next}>
    <CVDocument values={formData} />
  </I18nextProvider>
).toBlob();

And I have standard call to i18next.init in index.js of the react app project.

i18next.init({
  interpolation: { escapeValue: false }, // React already does escaping
  lng: 'en', // language to use
  resources: {
    en: {
      main: main_en, // 'main' is our custom namespace
      form: form_en,
    },
    sr: {
      main: main_sr,
      form: form_sr,
    },
  },
});
sagargulati commented 2 years ago

It doesn't seem to work inside the API /api/generate/pdf endpoints

ghost commented 2 years ago

I managed to get it work with react-intl

sagargulati commented 2 years ago

I wouldn't recommend switching to react specific package while using a framework like next.js

ghost commented 2 years ago

react-intl is works with Next.js too. I'm using it for a custom SSR setup and works without issues.

sagargulati commented 1 year ago

Can you share an example?

sdlfkorg commented 1 year ago

any update on this issue?

I'm working on a nextjs project and use next-i18next for i18n feature. About this issue, I try to work around by import the i18n json in pdf-related component directly: import de from '@public/locales/de/crf.json'; import en from '@public/locales/en/crf.json';

and pass locale/language in the component to get the corresponding translation by language: locale === 'en' && en[your-translation-key] || '' locale === 'de' && de[your-translation-key] || ''

still, hope there is a way to make translation work in pdf document.

andreymaklakov commented 1 month ago

any updates??

mirankorkmaz commented 1 month ago

Haven't really touched my test-repository in two years but I quickly tried adding a document now with useTranslation from react-i18next and it worked

https://github.com/mirankorkmaz/useTranslation-pdf-renderer

Again, have not been maintaining the repo so I don't know if the dependencies are up to date etc

andreymaklakov commented 1 month ago

When pdf is located on page its working, but when download it is not const [instance] = usePDF({ document: <Pdf Lib={Lib} /> });

mirankorkmaz commented 1 month ago

I see, made some changes now to my repository using usePDF if you want to clone it. Seems to be working. Can you provide more information about your setup if it doesn't work for you? @andreymaklakov

andreymaklakov commented 1 month ago

i18n config

import { initReactI18next } from 'react-i18next';
import i18n from 'i18next';
import Backend from 'i18next-http-backend';

import adminCommonRu from '../locales/ru/adminCommon.json';
import adminEmployeesRu from '../locales/ru/adminEmployees.json';
import adminProjectsRu from '../locales/ru/adminProjects.json';
import adminRolesRu from '../locales/ru/adminRoles.json';
import agentTicketRu from '../locales/ru/agentTicket.json';
import chatRu from '../locales/ru/chat.json';
import commonRu from '../locales/ru/common.json';
import customerCommonRu from '../locales/ru/customerCommon.json';
import customerTicketRu from '../locales/ru/customerTicket.json';
import pdfRu from '../locales/ru/pdf.json';
import profileRu from '../locales/ru/profile.json';

i18n.use(Backend)
    .use(initReactI18next)
    .init({
        resources: {
            ru: {
                common: commonRu,
                agentTicket: agentTicketRu,
                profile: profileRu,
                chat: chatRu,
                customerCommon: customerCommonRu,
                customerTicket: customerTicketRu,
                adminCommon: adminCommonRu,
                adminProjects: adminProjectsRu,
                adminRoles: adminRolesRu,
                adminEmployees: adminEmployeesRu,
                pdf: pdfRu
            }
        },
        fallbackLng: 'ru',
        lng: 'ru',
        debug: __IS_DEV__,
        interpolation: {
            escapeValue: false
        },
        backend: {
            loadPath: '/locales/{{lng}}/{{ns}}.json'
        }
    });

export { i18n };

download button(lib loads on click)

import { FC, useCallback, useRef, useState } from 'react';

import { PdfLibType } from '@/entities/AgentTickets';
import { IconButton } from '@/shared/ui/IconButton';

import { OpenPdf } from './OpenPdf';

const getAsyncPdfLib = () => {
    return Promise.resolve(import('@react-pdf/renderer'));
};

export const DownloadPdf: FC = () => {
    const pdfLibRef = useRef<PdfLibType>();

    const [isOpen, setIsOpen] = useState(false);

    const handleOpen = () => {
        getAsyncPdfLib().then((pdfLib) => {
            pdfLibRef.current = pdfLib;

            setIsOpen(true);
        });
    };

    const handleClose = useCallback(() => {
        setIsOpen(false);
    }, []);

    return (
        <>
            <IconButton
                name="fileDownload"
                width={25}
                height={25}
                onClick={handleOpen}
            />

            {isOpen && pdfLibRef.current && (
                <OpenPdf Lib={pdfLibRef.current} onClose={handleClose} />
            )}
        </>
    );
};

pdf instance

import { FC, useEffect } from 'react';

import { Spinner } from '4game-ui';

import { Pdf, PdfLibType } from '@/entities/AgentTickets';

import styles from './DownloadPdf.module.css';

interface IProps {
    Lib: PdfLibType;
    onClose: () => void;
}

export const OpenPdf: FC<IProps> = (props) => {
    const { Lib, onClose } = props;

    const [instance] = Lib.usePDF({
        document: <Pdf Lib={Lib} />
    });

    useEffect(() => {
        if (instance.url) {
            window.open(instance.url);

            onClose();
        }
    }, [instance.url, onClose]);

    if (instance.loading) {
        return <Spinner size="large" className={styles.spinner} />;
    }

    return null;
};

pdf

import { FC } from 'react';
import { useTranslation } from 'react-i18next';

import { PdfLibType } from '../../model/types/pdf';

interface IProps {
    Lib: PdfLibType;
}

export const Pdf: FC<IProps> = (props) => {
    const { Lib } = props;

    const { t } = useTranslation('pdf');

    const styles = Lib.StyleSheet.create({
        page: {
            padding: 20
        },
        counter: {
            marginBottom: 30,
            paddingBottom: 10,
            borderBottom: '1px solid black'
        },
        section: {}
    });

    return (
        <Lib.Document>
            <Lib.Page wrap style={styles.page}>
                <Lib.Text
                    style={styles.counter}
                    render={({ pageNumber, totalPages }) =>
                        `${pageNumber} ${t('page')} / ${t('pages', { count: totalPages })}`
                    }
                    fixed
                />

                <Lib.View style={styles.section}>
                    <Lib.Text>Section #1</Lib.Text>
                </Lib.View>

                <Lib.View style={styles.section}>
                    <Lib.Text>Section #2</Lib.Text>
                </Lib.View>
            </Lib.Page>
        </Lib.Document>
    );
};

@mirankorkmaz

andreymaklakov commented 1 month ago

without async lib load is the same I have 1 !B@0=8F0 / 1 !B@0=8F0 in pdf

mirankorkmaz commented 1 month ago

What does your public folder look like? Mine looks like this:

Skärmavbild 2024-07-25 kl  13 53 22

Also, I tried incorporating your solution and it still works. I'm suspecting it has something to do with the way you've set up your i18n config, or maybe the way you are trying to render

     <Lib.Text
                    style={styles.counter}
                    render={({ pageNumber, totalPages }) =>
                        `${pageNumber} ${t('page')} / ${t('pages', { count: totalPages })}`
                    }
                    fixed
                />

Also what does your ruPdf look like? @andreymaklakov It shouldn't matter whether the lib import is a promise.

andreymaklakov commented 1 month ago

I use vite and do not have public folder, but its not an issue, I tried to put locales to common folder in your project and it still works. I setted up config in your project in the same way as mine, still works.

pdfRu is working everywhere except pdf

{
    "page": "Страница",
    "pages_one": "{{count}} Страница",
    "pages_few": "{{count}} Страницы",
    "pages_many": "{{count}} Страниц"
}
andreymaklakov commented 1 month ago

Translation before download is working and is correct, but when download or open pdf in new window its!B@0=8F0

mirankorkmaz commented 1 month ago

I see the issue and you're right, it works everywhere except inside the PDF. It seems to be an issue rendering the Russian alphabet.

Add Font to your document. Font is imported from import { Font } from "@react-pdf/renderer";


export const Pdf: FC<IProps> = (props) => {
    const { Lib } = props;

    const { t } = useTranslation('pdf');

    Font.register({
         family: 'Roboto',
          src: 'https://cdnjs.cloudflare.com/ajax/libs/ink/3.1.10/fonts/Roboto/roboto-light-webfont.ttf',
        })
    const styles = Lib.StyleSheet.create({
        page: {
            padding: 20,
            fontFamily: 'Roboto',
        },
        counter: {
            marginBottom: 30,
            paddingBottom: 10,
            borderBottom: '1px solid black'
        },
        section: {}
    });

    return (
        <Lib.Document>
            <Lib.Page wrap style={styles.page}>
                <Lib.Text
                    style={styles.counter}
                    render={({ pageNumber, totalPages }) =>
                        `${pageNumber} ${t('page')} / ${t('pages', { count: totalPages })}`
                    }
                    fixed
                />

                <Lib.View style={styles.section}>
                    <Lib.Text>Section #1</Lib.Text>
                </Lib.View>

                <Lib.View style={styles.section}>
                    <Lib.Text>Section #2</Lib.Text>
                </Lib.View>
            </Lib.Page>
        </Lib.Document>
    );
};
andreymaklakov commented 1 month ago

Thank you so much, I did not even thought the reason can be alphabet