๐Ÿ“‚ ์Šคํ”„๋ง/๊ธฐ๋ณธ ๊ฐœ๋…

๋ฉ”์‹œ์ง€ & ๊ตญ์ œํ™” (Message & Internationalization)

Amenable 2023. 4. 25. 00:22

1. ๊ฐœ๋… ๐Ÿ“

1. ๋ฉ”์‹œ์ง€ (Message)

  ํ™”๋ฉด์— ๋ณด์ด๋Š” '์ƒํ’ˆ๋ช…'์ด๋ผ๋Š” ๋‹จ์–ด๋ฅผ '์ƒํ’ˆ์ด๋ฆ„'์œผ๋กœ ๋ฐ”๊พธ๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ? ๊ฐ„๋‹จํ•˜๊ฒŒ๋Š” item.html์—์„œ ํ•ด๋‹น ๋‹จ์–ด๋ฅผ ๋ฐ”๊พธ๋ฉด ๋œ๋‹ค.

  ํ•˜์ง€๋งŒ ์ƒํ’ˆ๋ช…์ด๋ผ๋Š” ๋‹จ์–ด๊ฐ€ item.html, addItem.html, updateItem.html, deleteItem.html, ... ์—์„œ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค๋ฉด ๋ชจ๋“  ํŒŒ์ผ์„ ๊ณ ์ณ์•ผ ํ•œ๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

  ์ด๋ ‡๊ฒŒ HTML ํŒŒ์ผ์— ๋ฉ”์‹œ์ง€๋ฅผ ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ๋ฉ”์‹œ์ง€๋ฅผ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ์ด๊ฒƒ์ด ๋ฐ”๋กœ ๋ฉ”์‹œ์ง€ ๊ธฐ๋Šฅ์ด๋‹ค.

2. ๊ตญ์ œํ™” (Internationalization)

  ํ•œ๊ตญ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ์—๊ฒŒ๋Š” '์ƒํ’ˆ๋ช…', ์˜์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ์—๊ฒŒ๋Š” 'Item Name'์œผ๋กœ ๋ณด์—ฌ์ฃผ๊ณ ์ž ํ•œ๋‹ค. ์ด๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ๊ตญ์ œํ™” ๊ธฐ๋Šฅ์ด๋‹ค.

 

2. ๋ฉ”์‹œ์ง€ ์†Œ์Šค ์„ค์ • ๐Ÿ‰

  ๋ฉ”์‹œ์ง€์™€ ๊ตญ์ œํ™”๋Š” /resources/messages.properties์— ์žˆ๋Š” ๋‚ด์šฉ์„ ์ฝ์Œ์œผ๋กœ์จ ๋™์ž‘ํ•œ๋‹ค. (๋ฌผ๋ก  messsages.properties๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ํŒŒ์ผ์„ ์ฝ์„ ์ˆ˜๋„ ์žˆ๋‹ค.) messages.properties๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์ง์ ‘ ๋“ฑ๋ก(์Šคํ”„๋ง ๋นˆ)๊ณผ ์ž๋™ ๋“ฑ๋ก(์Šคํ”„๋ง ๋ถ€ํŠธ)์ด ์žˆ๋‹ค.

1. ์ง์ ‘ ๋“ฑ๋ก (์Šคํ”„๋ง ๋นˆ)

  ์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” MessageSource๋ฅผ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜์—ฌ ๋ฉ”์‹œ์ง€ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("messages");
    messageSource.setDefaultEncoding("utf-8");
    return messageSource;
}

  MessageSourc๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋ฏ€๋กœ ResourceBundleMessageSource๋ผ๋Š” ๊ตฌํ˜„์ฒด๋ฅผ ์ด์šฉํ•ด์•ผ ํ•œ๋‹ค.

  • basename
      ๊ฐ’์œผ๋กœ messages๋กœ ์ง€์ •ํ•˜๋ฉด messages.properties, messages_ko.properties, messages_en.properties ๋“ฑ์„ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค.
      ์ด๋•Œ ์ฃผ์˜ํ•  ์ ์€ ์ด๋ฆ„์— '.'์„ ๋„ฃ์ง€ ์•Š๋„๋ก ํ•˜์ž. '.'์„ path๊ตฌ๋ถ„์ž๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. (ex. error.message)
  • defaultEncoding
      ์ธ์ฝ”๋”ฉ ์ •๋ณด๋ฅผ ์ €์žฅํ•œ๋‹ค.

  ํŒŒ์ผ์˜ ์œ„์น˜๋Š” /resources/messages.properties์ด๋‹ค.

2. ์ž๋™ ๋“ฑ๋ก (์Šคํ”„๋ง ๋ถ€ํŠธ)

์Šคํ”„๋ง ๋ถ€ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ MessageSource๋ฅผ ์ž๋™์œผ๋กœ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค. ๋ฉ”์‹œ์ง€ ์†Œ์Šค๋ฅผ application.properties์— ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

spring.messages.basename=messages

  ๊ธฐ๋ณธ ๊ฐ’์ด spring.messages.basename=messages ๋ผ์„œ ๋ณ„๋„๋กœ ์„ค์ •์„ ํ•˜์ง€ ์•Š์•„๋„ ๋˜๊ธด ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ถ”๊ฐ€๋กœ ๋ฉ”์‹œ์ง€ ๊ฐ’์„ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

#์˜ˆ์‹œ
spring.messages.basename=messages, config.i18n.messages

 

3. ๋ฉ”์‹œ์ง€ ํŒŒ์ผ ์„ค์ • ๐Ÿฅ

  ์•„๋ž˜์™€ ๊ฐ™์ด /resources์— 'messages.properties', 'messages_en.properties'์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

# /resources/messages.properties
label.item=์ƒํ’ˆ
label.item.id=์ƒํ’ˆ ID
label.item.itemName=์ƒํ’ˆ๋ช…

page.addItem=์ƒํ’ˆ ๋“ฑ๋ก
page.updateItem=์ƒํ’ˆ ์ˆ˜์ •

hello.name=์•ˆ๋…• {0}
# /resources/messages_en.properties
label.item=Item
label.item.id=Item ID
label.item.itemName=Item Name

page.addItem=Item Registration
page.updateItem=Item Modification

hello.name=hello {0}

 

4. ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ (ํƒ€์ž„๋ฆฌํ”„) ๐Ÿ‡

  ๊ทธ๋ฆฌ๊ณ  'xxx.html'ํŒŒ์ผ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <title>Title</title>
</head>
<body>
    <div th:text="#{label.item}"></div>
    <div th:text="#{label.item.id}"></div>
    <div th:text="#{label.item.itemName}"></div>
    <br>
    <div th:text="#{page.addItem}"></div>
    <div th:text="#{page.updateItem}"></div>
    <br>
    <div th:text="#{hello.name(${userName})}"></div> // ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉ
</body>
</html>

1. ๋ฉ”์‹œ์ง€

  ์œ„์˜ HTML์„ ์ด์šฉํ•˜๋ฉด ํ™”๋ฉด์—๋Š” ๋ฉ”์‹œ์ง€ ํŒŒ์ผ(messages.properties)์— ์žˆ๋Š” ๊ฐ’๋“ค์ด ์ถœ๋ ฅ๋œ๋‹ค. (ํ•œ๊ธ€์ด ๋ฌผ์Œํ‘œ๋กœ ๋‚˜์˜ฌ ๋•Œ์—๋Š” '[Settings] - [File Encodings] - Default encoding for properties files : UTF-8'๋กœ ์„ค์ •ํ•˜๋„๋ก ํ•˜์ž. ์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด messages.properties๊ฐ€ ๋‹ค์‹œ ๋ฌผ์Œํ‘œ๋กœ ๋ฐ”๊พธ์–ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š”๋ฐ ๊ทธ๊ฒƒ๋„ ํ™•์ธํ•˜์ž.)

  ๋งจ ์ฒ˜์Œ์— ๊ณ ๋ คํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ '์ƒํ’ˆ๋ช…'์„ '์ƒํ’ˆ์ด๋ฆ„'์ด๋ผ๊ณ  ๋ฉ”์‹œ์ง€ ํŒŒ์ผ์—์„œ ์ˆ˜์ •ํ•ด ๋ณด์ž.

# /resources/messages.properties
# '์ƒํ’ˆ๋ช…'์„ '์ƒํ’ˆ์ด๋ฆ„'์œผ๋กœ ์ˆ˜์ •
label.item=์ƒํ’ˆ
label.item.id=์ƒํ’ˆ ID
label.item.itemName=์ƒํ’ˆ์ด๋ฆ„ #์—ฌ๊ธฐ๋ฅผ ์ˆ˜์ •

page.addItem=์ƒํ’ˆ ๋“ฑ๋ก
page.updateItem=์ƒํ’ˆ ์ˆ˜์ •

hello.name=์•ˆ๋…• {0}

  ๊ทธ๋Ÿฌ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด '์ƒํ’ˆ๋ช…'์ด '์ƒํ’ˆ์ด๋ฆ„'์ด๋ผ๊ณ  ๋ณด์ด๊ฒŒ ๋œ๋‹ค. ํ•˜๋‚˜์”ฉ ๋ฐ”๊ฟ”์ค„ ํ•„์š”๊ฐ€ ์—†๋‹ค!

2. ๊ตญ์ œํ™”

  ๊ตญ์ œํ™”๋Š” Locale ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค. Locale์— ๋งž์ถ”์–ด ๊ตฌ์ฒด์ ์ธ ๊ฒƒ์ด ์žˆ์œผ๋ฉด ๊ตฌ์ฒด์ ์ธ ๊ฒƒ์„ ์ฐพ๊ณ , ์—†์œผ๋ฉด ๋””ํดํŠธ(messages.properties)๋ฅผ ์ฐพ๋Š”๋‹ค.

  ํ˜„์žฌ์—๋Š” Locale์ด ko์ธ๋ฐ messages_ko.properties๊ฐ€ ์—†์œผ๋ฏ€๋กœ messages.properties๊ฐ€ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•œ๊ธ€ ํ™”๋ฉด์ด ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์ด๋‹ค. (Locale์€ ๊ฐœ๋ฐœ์ž๋„๊ตฌ(F12) โ†’ Network โ†’ Request Headers โ†’ Accept-Language์—์„œ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.)

  ๋งŒ์•ฝ Locale์ด ์˜์–ด๋กœ ๋“ค์–ด์˜จ๋‹ค๋ฉด messages_en.properties๊ฐ€ ์‚ฌ์šฉ๋˜์–ด ์•„๋ž˜์™€ ๊ฐ™์€ ์˜์–ด ํ™”๋ฉด์ด ๋‚˜์˜ฌ ๊ฒƒ์ด๋‹ค.

  ์˜์–ด๋กœ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ 'Chrome ๋งž์ถค์„ค์ • ๋ฐ ์ œ์–ด โ†’ ์–ธ์–ด โ†’ ์˜์–ด โ†’ ๊ฐ€์žฅ ์œ„๋กœ ์ด๋™'์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด Accept-Language๊ฐ€ en์ด ๋œ๋‹ค.

 

5. ์Šคํ”„๋ง์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ๐Ÿ

@SpringBootTest
public class MessageSourceTest {

    @Autowired
    MessageSource ms;

    @Test
    void itemMessage() {
        String result = ms.getMessage("label.item", null, null);
        Assertions.assertThat(result).isEqualTo("์ƒํ’ˆ");
    }
    
    // ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉ
    @Test
    void argumentMessage() {
        String result = ms.getMessage("hello.name", new Object[]{"Amenable"}, null);
        Assertions.assertThat(result).isEqualTo("์•ˆ๋…• Amenable");
    }
}

 

ํ•ด๋‹น ๊ธ€์€
๊น€์˜ํ•œ ๋‹˜์˜ '์Šคํ”„๋ง MVC 2ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ™œ์šฉ ๊ธฐ์ˆ ',
H.Kwon ๋‹˜์˜ 'Spring Boot(Spring) i18n ์„ค์ • ์‹œ ์ฃผ์˜์‚ฌํ•ญ',
hanker ๋‹˜์˜ 'Spring boot - ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ ์‹œ ํ•œ๊ธ€ ๋ฌผ์Œํ‘œ(?)๋กœ ๋‚˜์˜ฌ๋•Œ'
์„ ์ฐธ๊ณ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
๋Œ“๊ธ€์ˆ˜0