
Table of Contents(目次)
How to Structure a Python Package for Multilingual Support
In this chapter, we continue refining the “Fortune Cookie” application we have built so far.
At this point, fortune.py is already well-structured. However, as supported languages increase, you'll need to add or update the logic that “switches which module to load based on the language code.”
# Switch modules based on the language code
if lang_code == 'en':
import message.language_en as lang
else: # 'jp' or else
import message.language_jp as lang
To address this, we will extract this management logic into manager/language_manager.py. By splitting the files, we can reduce the impact of changes and make the system safer to modify.
- When updating text or messages: translators or content editors only need to modify
message/language_**.py, reducing the risk of accidentally altering core logic infortune.py. - When adding a new language: simply create a new
language_**.pyfile and register it inlanguage_manager.py. No changes tofortune.pyare required. - Easier testing: language-specific data and the selection logic can be unit-tested independently, making it easier to isolate issues.
In short, separating modules improves readability, safety, extensibility, and team development efficiency. This is the essence of packaging and the Single Responsibility Principle.
Final Package Structure and Module Layout in Python
Ultimately, the message/ directory will “store messages and fortune data,” while the manager/ directory will “contain logic for selection and configuration.”
fortune_cookie/
├─ fortune.py
├─ message/
│ ├─ __init__.py
│ ├─ language_en.py
│ └─ language_jp.py
└─ manager/
├─ __init__.py
└─ language_manager.py
Create the following new directory and files:
manager/__init__.py- This file is required for Python to recognize
manager/as a package.
- This file is required for Python to recognize
language_manager.py
Modularizing Language Management with language_manager
We will create a module dedicated to “selecting and managing languages.”
# manager/language_manager.py
def get_language_module(lang_code):
'''Return the module that matches the given language code'''
if lang_code == 'en':
from message import language_en
return language_en
elif lang_code == 'jp':
from message import language_jp
return language_jp
else:
print('Unsupported language code. Using Japanese instead.')
from message import language_jp
return language_jp
The language_manager module provides the get_language_module() function, which dynamically loads and returns the module associated with the given language code.
Update fortune.py as follows:
# fortune.py
import random
import manager.language_manager as lang_manager
def main():
# Choose language
lang_code = input('Choose a language (jp/en): ')
# Receive the language-specific module
lang_module = lang_manager.get_language_module(lang_code)
messages = lang_module.MESSAGES
fortunes = lang_module.FORTUNES
# Open a cookie?
print(f"=== {messages['app_title']} ===")
answer = input(messages['prompt_open'])
if answer == 'y':
print(f"{messages['fortune_prefix']} {random.choice(fortunes)}")
else:
print(messages['goodbye'])
if __name__ == '__main__':
main()
The changes in fortune.py are as follows:
- Line 4:
import manager.language_manager as lang_manager- Imports the
language_managermodule.
- Imports the
- Line 11:
lang_module = lang_manager.get_language_module(lang_code)- Receives the language module returned by
get_language_module().
- Receives the language module returned by
- Lines 12–13: The variables
lang_module.MESSAGESandlang_module.FORTUNESare reassigned tomessagesandfortunes.- You could access them directly in
printstatements—for example,lang_module.MESSAGES['app_title']—but this reduces readability. Assigning them to shorter local variables improves clarity and keeps the code concise.
- You could access them directly in
- Lines 25–26: Starting in this chapter,
fortune.py’smain()function is wrapped inside theif __name__ == '__main__':block.- This prevents
main()from running automatically whenfortuneis imported from another module.
- This prevents
Summary: Benefits of Modularizing a Multilingual Python App
- New languages can now be added by editing only
language_manager.py. fortune.pycan focus solely on application logic.get_language_module()is now testable on its own.- Previously, you had to run
fortune.py’smain()function to verify whether language modules were imported correctly. Now, you can testlanguage_manager.pyindependently.
- Previously, you had to run
# manager/language_manager.py
def get_language_module(lang_code):
'''Return the module that matches the given language code'''
if lang_code == 'en':
from message import language_en
return language_en
elif lang_code == 'jp':
from message import language_jp
return language_jp
else:
print('Unsupported language code. Using Japanese instead.')
from message import language_jp
return language_jp
if __name__ == '__main__':
# Temporarily modify sys.path for testing
import sys
sys.path.append(f'{__file__}/../../')
from pprint import pprint
lang_module = get_language_module('jp')
pprint(lang_module.MESSAGES)
pprint(lang_module.FORTUNES)
- If you want to test modules individually, you can safely place test code inside the
if __name__ == '__main__':block. sys.pathis a list that stores the search paths Python checks when importing modules. The import statements on lines 6 and 9 above,from message import language_xx, assume thatfortune_cookie/is the project root. However, if you runmanager/language_manager.pydirectly, Python treatsmanager/as the root, causing these imports to fail. To avoid errors, we add the correct root directory tosys.path.__file__is a special variable that holds the path of the file being executed. By combining this path with/../../, we move up two directories relative tolanguage_manager.py, effectively pointing tofortune_cookie/.
(Relative paths will be covered in detail in a dedicated lecture.)
Relative path:
f'{__file__}/../../'
# Example after replacing "__file__" with the actual path
C:\Users\xxx\Documents\python101\fortune_cookie\manager\language_manager.py/../../
\ and forward slashes / in paths. (For strict cross-platform compatibility, however, using the os module to manipulate paths is recommended.)Absolute path:
C:\Users\xxx\Documents\python101\fortune_cookie






