필요한 라이브러리를 찾았는데, 혹은 정착한 라이브러리라 하더라도 더 좋은게 있는데 놓치고 있진 않는가 확인하는 용도로 http://npmtrends.com 사이트를 이용하곤 한다.

어쩌다 다국어 관련하여 https://npmtrends.com/next-i18next-vs-next-translate 이걸 확인해보니 애정(?)하는 next-translate의 이용자 수가 미미한 것에 자극받아 귀차니즘을 무릎쓰고 이 글을 쓰게 되었다.

 

1. next-i18next

next.js 기반에서 가장 많이 쓰는 라이브러리고, 위의 두번째 링크를 타고 가서 봐도 알 수 있듯이 사용자 수가 급속히 늘고 있다. 나도 실제로 가장 처음으로 도입했었었다. 그렇게 몇 달 쓰다가 매 페이지마다 serverSideTranslations 이런거 넣어주는거에 현타가 와서 next-translate 테스트 후 바로 넘어가게 되었다. 

import { serverSideTranslations } from "next-i18next/serverSideTranslations"
import { useTranslation } from "next-i18next"

function Page () {
  const { t } = useTranslation("button")

  return (
    <div>
      <p>{t("ok")}</p>
    </div>
  );
};

export default Page

// 이거 페이지마다 해줘야 함
export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ["common", "button", ... ])), // 해당 페이지에서 쓸 파일명 나열
  },
});

그러다보니 next-i18next에 대해서 많이 잊었다가 이 글을 쓰기 위해 리마인드하기 위해 검색하다 보니 url에 /ko/ /en/ 이런것도 붙었었던 것 같기도 하고 아무튼 뭔가 많이 너저분했었던 기억만 남아있을 정도랄까?

 

2. next-translate

 next.config.js에서 

const nextTranslate = require('next-translate-plugin')
const nextConfig = {
	.
    .
    .
	...nextTranslate(),
}
module.exports = nextConfig

이 설정과 

/locale/에 언어별로 json  파일을 두고,

i18n.json에서는 pages라는 항목에 경로별로 어떤 파일을 적용할껀지 설정해준다. 여기가 바로 핵심적으로 감동했던 부분이었다.

{
	"locales": [
		"ko",
		"en",
        .
        .
        .
	],
	"defaultLocale": "ko",
	"pages": {
		"*": [
			"common",
			"button",
			"navigation",
            .
            .
            .
		]
	}
}
// "*" 은 경로=1.x 버전일때 저렇게 경로를 안 잡으면 components 폴더 안의 파일들이 안되길래 급한 마음에 저렇게 하고 그 후 테스트를 놨었는데 다른 해결책이 있을 수도 있다.
// []에는 /locales/ 하위 언어별 파일명

마지막으로 매 페이지에서는 다음과 같은 방식으로 쓰기만하면 된다.

import useTranslation from 'next-translate/useTranslation'
import getT from 'next-translate/getT'

// Front
function Page () {
	const { t } = useTranslation('') // '' 안에 locale의 특정 파일명을 넣을 수도 있지만 비워두면 다 불러옴
	return 
    	<>
            <button>{t('button:back')}</button>
            <button>{t('button:confirm.ok')}</button>
            <button>{t('button:confirm.cancel')}</button>
    	</>
}
// button => 파일명
// back, confirm.ok, confirm.cancel => json key

export default Page

// Server
export async function getServerSideProps(context) {
     const { locale } = context
    const t = await getT(locale, '') // '' 안에 특정 파일명 가능
    
    console.log(t('button:back');
    
    return {
        props: {}
    }
}

1.xx버전때 설정한 이후로 쭉 쓰고 있는데 초기 설정 당시 useTranslation를 매 페이지에서 하니 부하가 있는 듯해 react의 context에서 한번 한 후 꺼내 쓰는 방식으로 해서 속도를 드라마틱하게 개선했었는데 그 후 관련 테스트를 안해서 요즘은 모르겠지만 아무튼 사용성이 편해서 잘 쓰고 있다.

 

결론: next-translate 쓰세요!

인증이 된 사용자만 파일 다운로드가 가능해야하는 상황이라서 일반적인 a href를 쓸 수 없었다. 이런 경우 이런 식으로 파일 다운로드를 진행해야하는 것 같았다. 

간단해보이는 소스지만, 이 조합을 찾아내기까지 하루이상의 시간이 소요되었기 때문에 다음에는 헤매지 않기 위해 기록해둔다.

 

1. Express

import { join } from 'path';

...

router.get('/subpath/:id', async (req, res, next) => {
    const id = req.params.id;

    const filePath = join(__dirname, `서버에 저장된하위경로`);
    const filename = '파일명.확장자';
    
    // 1안
    res.setHeader('Content-Disposition', `attachment; filename=다른파일명가능`); // 필수
    res.sendFile(filePath + filename);

    // 2안
    res.download(filePath + filename); // 서버에 저장된 파일명으로만 가능

    // 3안
    res.setHeader('Content-Disposition', `attachment; filename=다른파일명가능`); // 필수
    const stream = fs.createReadStream(filePath + filename);
    stream.pipe(res);
});

출처: 불특정 다수

 

2. Front

const authHeader = 'token값'
const url = `/api/subpath/${id}`
let filename = '' // 서버에서 보내는 파일명이 담길 변수

fetch(url, { headers: { 'X-Auth-Token': authHeader } })
    .then((response) => {
        if (!response.ok) {
            throw Error(response.statusText)
        }

        const header = response.headers.get('Content-Disposition')
        const parts = header.split(';')
        filename = parts[1].split('=')[1].replaceAll('"', '')

        return response.blob()
    })
    .then((blob) => {
        if (blob != null) {
            var url = window.URL.createObjectURL(blob)
            var a = document.createElement('a')
            a.href = url
            a.download = filename
            document.body.appendChild(a)
            a.click()
            a.remove()
        }
    })
    .catch((err) => {
        console.log(err)
    })

출처: https://christosmonogios.com/2022/05/10/Use-the-Fetch-API-to-download-files-with-their-original-filename-and-the-proper-way-to-catch-server-errors/

 

front의 경우 이거 말고도 new ReadableStream에 new Response 동원해서 하는 복잡한 소스도 있었는데 위가 더 심플하고 속도도 체감될 정도로 빨라서 이 걸로 낙찰했다.

 

'Javascript' 카테고리의 다른 글

정렬 sort  (0) 2021.04.01
jQuery to VanillaJS  (0) 2021.03.16
키보드를 이용한 Input text 포커스 이동  (0) 2021.03.15
String to Date / Date to String  (0) 2020.12.17
숫자에 콤마와 언콤마를 편하게  (0) 2016.06.27

잘쓰던 포트였는데 갑자기 문제가 발생했다.

아래의 내용은 윈도우OS에서의 해결책이다.

 

내 원인

나의 경우에는 intellij에서 해당 포트와의 연결을 끊고 프로그램을 종료 했는데 그 후 재부팅을 하고 어쩌고 해도 저 포트를 사용할 수 없었다. 일반 콘솔에서도 동일한 에러가 발생하고 있었다.

 

해결

관리자 모드로 콘솔 혹은 파워셀을 연다.

net stop winnat
net start winnat

잘 된다.

 

출처: https://stackoverflow.com/questions/9164915/node-js-eacces-error-when-listening-on-most-ports

'오류노트 > Javascript' 카테고리의 다른 글

Material UI - RadioGroup , Radio 관련  (0) 2021.06.02

trigger를 통해서 값이 insert되면 외부 프로그램, 혹은 API를 호출하고 싶었다.

그때마다 검색되는 UDF인 sys_exec() 는 my_global.h가 없어 컴파일이 안되어 사용할 수 없는 상황이었다.

(MySQL 5에서는 있었지만 8 혹은 마이너 버전부터 제외한다는 글을 본 것 같다.)

mysql 내부에서 해결하는 것은 포기하고 돌도 돌다 node.js에 binlog를 이용하여 테이블을 감시하는 것을 찾았다.

https://www.npmjs.com/package/@vlasky/mysql-live-select

원래는 목적으로는 특정 레코드의 컬럼까지 감시가 가능한 것 같지만, 나는 테이블만 감시하는 목적으로 사용하려고 했고, 제공된 예제를 아래와 같이 수정하니 목적을 달성할 수 있게 되었다.

const LiveMysql = require('@vlasky/mysql-live-select');
const LiveMysqlKeySelector = require('@vlasky/mysql-live-select/lib/LiveMysqlKeySelector');

const settings = {
  host: '호스트주소',
  user: '아이디',
  password: '비밀번호',
  database: 'DB명'
};

const liveConnection = new LiveMysql(settings);

liveConnection
  .select('select version()', [], LiveMysqlKeySelector.Index(), [
    {
      table: '테이블명',
      condition: function (row, newRow, rowDeleted) {
        if (row && newRow === null) {
          if (rowDeleted === false) {
            // insert 상황            
          } else {
            // delete 상황
          }
        } else {
          // update 상황
        }
        return true;
      }
    }
  ]);
git config core.ignorecase false

 

+ Recent posts