tum 3 mesi fa
parent
commit
2fc72766d2

+ 1 - 0
Dockerfile

14
         nodejs npm \
14
         nodejs npm \
15
         graphviz libgraphviz-dev \
15
         graphviz libgraphviz-dev \
16
         build-essential pkg-config \
16
         build-essential pkg-config \
17
+        gettext \
17
     && rm -rf /var/lib/apt/lists/*
18
     && rm -rf /var/lib/apt/lists/*
18
 
19
 
19
 # Install dependencies
20
 # Install dependencies

+ 25 - 16
admin_frontend/templates/admin_frontend/base.html

1
 <!DOCTYPE html>
1
 <!DOCTYPE html>
2
-<html lang="en" x-data="{ open:false }">
2
+{% load tailwind_tags public_urls static i18n %}
3
+{% get_current_language as LANGUAGE_CODE %}
4
+<html lang="{{ LANGUAGE_CODE }}" x-data="{ open:false }">
3
 <head>
5
 <head>
4
   <meta charset="utf-8" />
6
   <meta charset="utf-8" />
5
   <meta name="viewport" content="width=device-width, initial-scale=1" />
7
   <meta name="viewport" content="width=device-width, initial-scale=1" />
6
   <title>{{ current_site.name|default:'Web Admin' }} — {% block title %}{% endblock %}</title>
8
   <title>{{ current_site.name|default:'Web Admin' }} — {% block title %}{% endblock %}</title>
7
-  {% load tailwind_tags public_urls static %}
8
   {% tailwind_css %}
9
   {% tailwind_css %}
9
   <script defer src="{% static 'alpinejs/dist/cdn.min.js' %}"></script>
10
   <script defer src="{% static 'alpinejs/dist/cdn.min.js' %}"></script>
10
 </head>
11
 </head>
30
                 {% csrf_token %}
31
                 {% csrf_token %}
31
                 <button class="text-sm text-red-600 hover:text-red-700">Logout</button>
32
                 <button class="text-sm text-red-600 hover:text-red-700">Logout</button>
32
               </form>
33
               </form>
34
+              <form method="post" action="{% url 'set_language' %}" class="ml-2">
35
+                {% csrf_token %}
36
+                <input type="hidden" name="next" value="{{ request.get_full_path }}" />
37
+                {% get_available_languages as langs %}
38
+                {% get_current_language as LANGUAGE_CODE %}
39
+                {% get_language_info_list for langs as languages %}
40
+                <label for="lang" class="sr-only">Language</label>
41
+                <select id="lang" name="language" class="text-sm border border-gray-300 rounded px-2 py-1 bg-white"
42
+                        onchange="this.form.submit()">
43
+                  {% for lang in languages %}
44
+                    <option value="{{ lang.code }}" {% if lang.code == LANGUAGE_CODE %}selected{% endif %}>{{ lang.name_local }}</option>
45
+                  {% endfor %}
46
+                </select>
47
+              </form>
33
             {% else %}
48
             {% else %}
34
               <a class="text-sm text-blue-600 hover:text-blue-700" href="{% url 'admin_frontend:login' %}">Login</a>
49
               <a class="text-sm text-blue-600 hover:text-blue-700" href="{% url 'admin_frontend:login' %}">Login</a>
35
             {% endif %}
50
             {% endif %}
43
         <div id="toasts" class="fixed bottom-4 right-4 z-50 space-y-2">
58
         <div id="toasts" class="fixed bottom-4 right-4 z-50 space-y-2">
44
           {% for message in messages %}
59
           {% for message in messages %}
45
             <div class="toast px-4 py-3 rounded border shadow {{ message.tags|default:'' }}">
60
             <div class="toast px-4 py-3 rounded border shadow {{ message.tags|default:'' }}">
46
-              {{ message }}
61
+              {% trans message %}
47
             </div>
62
             </div>
48
           {% endfor %}
63
           {% endfor %}
49
         </div>
64
         </div>
53
         <aside class="md:col-span-3">
68
         <aside class="md:col-span-3">
54
           <nav class="bg-white rounded shadow p-4">
69
           <nav class="bg-white rounded shadow p-4">
55
             <div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Navigation</div>
70
             <div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Navigation</div>
71
+
56
             {% app_menu 'admin_frontend' as menu_items %}
72
             {% app_menu 'admin_frontend' as menu_items %}
57
             <ul class="space-y-1">
73
             <ul class="space-y-1">
58
-              {% for it in menu_items %}
59
-                <li>
60
-                  <a href="{{ it.url }}" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 {% if request.path == it.url %}bg-blue-50 text-blue-700{% else %}text-gray-700{% endif %}">
61
-                    <span class="truncate">{{ it.label }}</span>
62
-                  </a>
63
-                </li>
64
-              {% endfor %}
65
               <div x-data="{ open: {% if '/webadmin/settings/' in request.path %}true{% else %}false{% endif %} }">
74
               <div x-data="{ open: {% if '/webadmin/settings/' in request.path %}true{% else %}false{% endif %} }">
66
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
75
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
67
-                      <span class="truncate">Settings</span>
76
+                      <span class="truncate">{% trans "Settings" %}</span>
68
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
77
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
69
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
78
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
70
                       </svg>
79
                       </svg>
84
               </div>
93
               </div>
85
               <div x-data="{ open: {% if '/webadmin/cms/' in request.path %}true{% else %}false{% endif %} }">
94
               <div x-data="{ open: {% if '/webadmin/cms/' in request.path %}true{% else %}false{% endif %} }">
86
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
95
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
87
-                      <span class="truncate">CMS</span>
96
+                      <span class="truncate">{% trans "CMS" %}</span>
88
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
97
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
89
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
98
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
90
                       </svg>
99
                       </svg>
105
 
114
 
106
               <div x-data="{ open: {% if '/webadmin/frontend/' in request.path %}true{% else %}false{% endif %} }">
115
               <div x-data="{ open: {% if '/webadmin/frontend/' in request.path %}true{% else %}false{% endif %} }">
107
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
116
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
108
-                      <span class="truncate">Frontend</span>
117
+                      <span class="truncate">{% trans "Frontend" %}</span>
109
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
118
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
110
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
119
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
111
                       </svg>
120
                       </svg>
125
               </div>
134
               </div>
126
               <div x-data="{ open: {% if '/webadmin/orgs/' in request.path %}true{% else %}false{% endif %} }">
135
               <div x-data="{ open: {% if '/webadmin/orgs/' in request.path %}true{% else %}false{% endif %} }">
127
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
136
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
128
-                      <span class="truncate">Organization</span>
137
+                      <span class="truncate">{% trans "Organization" %}</span>
129
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
138
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
130
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
139
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
131
                       </svg>
140
                       </svg>
145
               </div>
154
               </div>
146
               <div x-data="{ open: {% if '/webadmin/billings/' in request.path %}true{% else %}false{% endif %} }">
155
               <div x-data="{ open: {% if '/webadmin/billings/' in request.path %}true{% else %}false{% endif %} }">
147
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
156
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
148
-                      <span class="truncate">Billing</span>
157
+                      <span class="truncate">{% trans "Billing" %}</span>
149
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
158
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
150
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
159
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
151
                       </svg>
160
                       </svg>
165
               </div>
174
               </div>
166
               <div x-data="{ open: {% if '/webadmin/recycle/' in request.path %}true{% else %}false{% endif %} }">
175
               <div x-data="{ open: {% if '/webadmin/recycle/' in request.path %}true{% else %}false{% endif %} }">
167
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
176
                   <div @click="open = !open" class="flex items-center justify-between rounded px-3 py-2 hover:bg-gray-50 cursor-pointer">
168
-                      <span class="truncate">Recycle Ops</span>
177
+                      <span class="truncate">{% trans "Recycle Ops" %}</span>
169
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
178
                       <svg :class="{'rotate-180': open}" class="w-4 h-4 transform transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
170
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
179
                           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
171
                       </svg>
180
                       </svg>

+ 4 - 2
admin_frontend/templatetags/public_urls.py

3
 from typing import Any, Dict, List, Optional, Tuple
3
 from typing import Any, Dict, List, Optional, Tuple
4
 
4
 
5
 from django import template
5
 from django import template
6
+from django.utils.translation import gettext as _
6
 from django.urls import URLPattern, URLResolver, get_resolver, reverse, NoReverseMatch
7
 from django.urls import URLPattern, URLResolver, get_resolver, reverse, NoReverseMatch
7
 
8
 
8
 register = template.Library()
9
 register = template.Library()
71
         if public_only and not meta:
72
         if public_only and not meta:
72
             continue
73
             continue
73
 
74
 
74
-        label = meta.get("label") if meta else full_name.split(":")[-1].replace("_", " ").title()
75
+        raw_label = meta.get("label") if meta else full_name.split(":")[-1].replace("_", " ").title()
76
+        # Translate the menu label at render time
77
+        label = _(raw_label)
75
         order = meta.get("order") if meta else 1000
78
         order = meta.get("order") if meta else 1000
76
         icon = meta.get("icon") if meta else None
79
         icon = meta.get("icon") if meta else None
77
 
80
 
94
     Then: {% for it in items %}<a href="{{ it.url }}">{{ it.label }}</a>{% endfor %}
97
     Then: {% for it in items %}<a href="{{ it.url }}">{{ it.label }}</a>{% endfor %}
95
     """
98
     """
96
     return collect_public_urls(app_namespace=app_namespace, public_only=public_only)
99
     return collect_public_urls(app_namespace=app_namespace, public_only=public_only)
97
-

+ 0 - 2
docker-compose.yml

1
-version: '3.8'
2
-
3
 services:
1
 services:
4
   web:
2
   web:
5
     build: .
3
     build: .

+ 6 - 0
ecoloop/formats/__init__.py

1
+"""Custom date/time format modules for the project.
2
+
3
+Django will look up locale-specific formats under this package when
4
+FORMAT_MODULE_PATH includes 'ecoloop.formats'.
5
+"""
6
+

+ 8 - 0
ecoloop/formats/en/formats.py

1
+# English (generic) custom formats
2
+
3
+DATE_FORMAT = "m/d/y"
4
+TIME_FORMAT = "H:i"
5
+DATETIME_FORMAT = "m/d/y H:i"
6
+SHORT_DATE_FORMAT = DATE_FORMAT
7
+SHORT_DATETIME_FORMAT = DATETIME_FORMAT
8
+

+ 8 - 0
ecoloop/formats/en_US/formats.py

1
+# English (United States) custom formats
2
+
3
+DATE_FORMAT = "m/d/y"
4
+TIME_FORMAT = "H:i"
5
+DATETIME_FORMAT = "m/d/y H:i"
6
+SHORT_DATE_FORMAT = DATE_FORMAT
7
+SHORT_DATETIME_FORMAT = DATETIME_FORMAT
8
+

+ 35 - 0
ecoloop/settings.py

123
 MIDDLEWARE = [
123
 MIDDLEWARE = [
124
     'django.middleware.security.SecurityMiddleware',
124
     'django.middleware.security.SecurityMiddleware',
125
     'django.contrib.sessions.middleware.SessionMiddleware',
125
     'django.contrib.sessions.middleware.SessionMiddleware',
126
+    'django.middleware.locale.LocaleMiddleware',
126
     'django.middleware.common.CommonMiddleware',
127
     'django.middleware.common.CommonMiddleware',
127
     'django.middleware.csrf.CsrfViewMiddleware',
128
     'django.middleware.csrf.CsrfViewMiddleware',
128
     'django.contrib.auth.middleware.AuthenticationMiddleware',
129
     'django.contrib.auth.middleware.AuthenticationMiddleware',
201
 
202
 
202
 LANGUAGE_CODE = 'en-us'
203
 LANGUAGE_CODE = 'en-us'
203
 
204
 
205
+# Internationalization languages supported
206
+LANGUAGES = (
207
+    ('en', 'English'),
208
+    ('th', 'Thai'),
209
+    ('ja', 'Japanese'),
210
+)
211
+
212
+# Where compiled translations (.mo) live
213
+LOCALE_PATHS = [
214
+    BASE_DIR / 'locale',
215
+]
216
+
204
 # Set application timezone to Bangkok (Asia/Bangkok)
217
 # Set application timezone to Bangkok (Asia/Bangkok)
205
 TIME_ZONE = 'Asia/Bangkok'
218
 TIME_ZONE = 'Asia/Bangkok'
206
 
219
 
208
 
221
 
209
 USE_TZ = True
222
 USE_TZ = True
210
 
223
 
224
+# Default date/time display formats (used by templates and localization)
225
+# Example: 09/25/24 14:30
226
+DATE_FORMAT = "m/d/y"
227
+TIME_FORMAT = "h:i"
228
+DATETIME_FORMAT = "m/d/y h:i"
229
+SHORT_DATE_FORMAT = DATE_FORMAT
230
+SHORT_DATETIME_FORMAT = DATETIME_FORMAT
231
+
211
 
232
 
212
 # Static and media files
233
 # Static and media files
213
 # https://docs.djangoproject.com/en/4.2/howto/static-files/
234
 # https://docs.djangoproject.com/en/4.2/howto/static-files/
242
     'SERVE_INCLUDE_SCHEMA': False,
263
     'SERVE_INCLUDE_SCHEMA': False,
243
 }
264
 }
244
 
265
 
266
+# Use custom format modules to override locale defaults (e.g., en-us)
267
+FORMAT_MODULE_PATH = [
268
+    'ecoloop.formats',
269
+]
270
+
271
+# Accept HTML5 datetime-local inputs and common alternatives
272
+DATETIME_INPUT_FORMATS = [
273
+    "%Y-%m-%dT%H:%M",  # HTML5 datetime-local
274
+    "%m/%d/%y %H:%M",
275
+    "%m/%d/%Y %H:%M",
276
+    "%Y-%m-%d %H:%M:%S",
277
+    "%Y-%m-%d %H:%M",
278
+]
279
+
245
 # Auth redirects
280
 # Auth redirects
246
 LOGIN_URL = '/webadmin/login/'
281
 LOGIN_URL = '/webadmin/login/'
247
 LOGIN_REDIRECT_URL = '/webadmin/'
282
 LOGIN_REDIRECT_URL = '/webadmin/'

+ 2 - 0
ecoloop/urls.py

35
     path('webadmin/settings/', include('settings.urls')),
35
     path('webadmin/settings/', include('settings.urls')),
36
     path('webadmin/cms/', include('cms.urls')),
36
     path('webadmin/cms/', include('cms.urls')),
37
     path('webadmin/recycle/', include('recycle_core.urls')),
37
     path('webadmin/recycle/', include('recycle_core.urls')),
38
+    # i18n: language switcher and related utilities
39
+    path('i18n/', include('django.conf.urls.i18n')),
38
     path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
40
     path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
39
     path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
41
     path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
40
     path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
42
     path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),

BIN
locale/ja/LC_MESSAGES/django.mo


+ 371 - 0
locale/ja/LC_MESSAGES/django.po

1
+# SOME DESCRIPTIVE TITLE.
2
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+# This file is distributed under the same license as the PACKAGE package.
4
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+#
6
+#, fuzzy
7
+msgid ""
8
+msgstr ""
9
+"Project-Id-Version: PACKAGE VERSION\n"
10
+"Report-Msgid-Bugs-To: \n"
11
+"POT-Creation-Date: 2025-09-25 22:51+0700\n"
12
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+"Language-Team: LANGUAGE <LL@li.org>\n"
15
+"Language: ja\n"
16
+"MIME-Version: 1.0\n"
17
+"Content-Type: text/plain; charset=UTF-8\n"
18
+"Content-Transfer-Encoding: 8bit\n"
19
+"Plural-Forms: nplurals=1; plural=0;\n"
20
+
21
+#: admin_frontend/templates/admin_frontend/base.html:76
22
+#: translations/menu_labels.py:12
23
+msgid "Settings"
24
+msgstr "設定"
25
+
26
+#: admin_frontend/templates/admin_frontend/base.html:96
27
+msgid "CMS"
28
+msgstr "CMS"
29
+
30
+#: admin_frontend/templates/admin_frontend/base.html:117
31
+msgid "Frontend"
32
+msgstr "フロントエンド"
33
+
34
+#: admin_frontend/templates/admin_frontend/base.html:137
35
+msgid "Organization"
36
+msgstr "組織"
37
+
38
+#: admin_frontend/templates/admin_frontend/base.html:157
39
+msgid "Billing"
40
+msgstr "請求"
41
+
42
+#: admin_frontend/templates/admin_frontend/base.html:177
43
+msgid "Recycle Ops"
44
+msgstr "リサイクル業務"
45
+
46
+#: public_frontend/templates/public_frontend/base.html:17
47
+#: public_frontend/templates/public_frontend/materials_list.html:3
48
+#: translations/menu_labels.py:18
49
+msgid "Materials"
50
+msgstr "素材"
51
+
52
+#: public_frontend/templates/public_frontend/base.html:18
53
+#: public_frontend/templates/public_frontend/listings_list.html:3
54
+msgid "Listings"
55
+msgstr "出品"
56
+
57
+#: public_frontend/templates/public_frontend/base.html:19
58
+#: public_frontend/templates/public_frontend/pickup_request.html:3
59
+msgid "Request Pickup"
60
+msgstr "回収依頼"
61
+
62
+#: public_frontend/templates/public_frontend/base.html:20
63
+#: public_frontend/templates/public_frontend/blog_list.html:3
64
+msgid "Blog"
65
+msgstr "ブログ"
66
+
67
+#: public_frontend/templates/public_frontend/base.html:21
68
+#: public_frontend/templates/public_frontend/contact.html:3
69
+msgid "Contact"
70
+msgstr "お問い合わせ"
71
+
72
+#: public_frontend/templates/public_frontend/base.html:53
73
+msgid "Staff Login"
74
+msgstr "スタッフログイン"
75
+
76
+#: public_frontend/templates/public_frontend/blog_list.html:14
77
+msgid "No image"
78
+msgstr "画像なし"
79
+
80
+#: public_frontend/templates/public_frontend/blog_list.html:24
81
+#: public_frontend/templates/public_frontend/home.html:92
82
+msgid "No posts yet."
83
+msgstr "まだ投稿はありません。"
84
+
85
+#: public_frontend/templates/public_frontend/contact.html:5
86
+#: public_frontend/templates/public_frontend/home.html:99
87
+msgid "Contact Us"
88
+msgstr "お問い合わせ"
89
+
90
+#: public_frontend/templates/public_frontend/contact.html:10
91
+#: public_frontend/templates/public_frontend/home.html:104
92
+#: public_frontend/templates/public_frontend/materials_list.html:9
93
+#: public_frontend/templates/public_frontend/pickup_request.html:38
94
+msgid "Name"
95
+msgstr "氏名"
96
+
97
+#: public_frontend/templates/public_frontend/contact.html:14
98
+#: public_frontend/templates/public_frontend/home.html:108
99
+#: public_frontend/templates/public_frontend/pickup_request.html:40
100
+msgid "Email"
101
+msgstr "メール"
102
+
103
+#: public_frontend/templates/public_frontend/contact.html:20
104
+#: public_frontend/templates/public_frontend/pickup_request.html:41
105
+msgid "Phone"
106
+msgstr "電話"
107
+
108
+#: public_frontend/templates/public_frontend/contact.html:24
109
+#: public_frontend/templates/public_frontend/home.html:113
110
+msgid "Subject"
111
+msgstr "件名"
112
+
113
+#: public_frontend/templates/public_frontend/contact.html:29
114
+#: public_frontend/templates/public_frontend/home.html:117
115
+msgid "Message"
116
+msgstr "メッセージ"
117
+
118
+#: public_frontend/templates/public_frontend/contact.html:33
119
+msgid "Send"
120
+msgstr "送信"
121
+
122
+#: public_frontend/templates/public_frontend/home.html:3
123
+msgid "Home"
124
+msgstr "ホーム"
125
+
126
+#: public_frontend/templates/public_frontend/home.html:9
127
+msgid "Streamline Your Factory's Recycling"
128
+msgstr "工場のリサイクルを効率化"
129
+
130
+#: public_frontend/templates/public_frontend/home.html:10
131
+msgid ""
132
+"We partner with businesses like yours to manage scrap materials efficiently. "
133
+"Browse listings, request a pickup, and turn your waste into a resource."
134
+msgstr "貴社のような企業と連携し、スクラップ素材を効率的に管理します。リスティングを閲覧し、回収を依頼し、廃棄物を資源へと変えましょう。"
135
+
136
+#: public_frontend/templates/public_frontend/home.html:12
137
+#: public_frontend/templates/public_frontend/pickup_request.html:6
138
+msgid "Request a Pickup"
139
+msgstr "回収を依頼"
140
+
141
+#: public_frontend/templates/public_frontend/home.html:13
142
+msgid "Browse Listings"
143
+msgstr "リスティングを見る"
144
+
145
+#: public_frontend/templates/public_frontend/home.html:21
146
+msgid "Our Services"
147
+msgstr "サービス"
148
+
149
+#: public_frontend/templates/public_frontend/home.html:40
150
+msgid "Materials We Accept"
151
+msgstr "受け入れ可能な素材"
152
+
153
+#: public_frontend/templates/public_frontend/home.html:41
154
+#: public_frontend/templates/public_frontend/home.html:82
155
+msgid "View all"
156
+msgstr "すべて表示"
157
+
158
+#: public_frontend/templates/public_frontend/home.html:51
159
+#: public_frontend/templates/public_frontend/materials_list.html:22
160
+msgid "No materials published."
161
+msgstr "公開中の素材はありません。"
162
+
163
+#: public_frontend/templates/public_frontend/home.html:60
164
+msgid "Open Listings"
165
+msgstr "公開中のリスティング"
166
+
167
+#: public_frontend/templates/public_frontend/home.html:61
168
+msgid "Browse all"
169
+msgstr "すべて見る"
170
+
171
+#: public_frontend/templates/public_frontend/home.html:66
172
+#: public_frontend/templates/public_frontend/listing_detail.html:7
173
+#: public_frontend/templates/public_frontend/listings_list.html:8
174
+msgid "Ends"
175
+msgstr "終了"
176
+
177
+#: public_frontend/templates/public_frontend/home.html:71
178
+msgid "No public listings at the moment."
179
+msgstr "現在公開中のリスティングはありません。"
180
+
181
+#: public_frontend/templates/public_frontend/home.html:81
182
+msgid "Industry Insights"
183
+msgstr "業界インサイト"
184
+
185
+#: public_frontend/templates/public_frontend/home.html:121
186
+msgid "Send Message"
187
+msgstr "メッセージを送信"
188
+
189
+#: public_frontend/templates/public_frontend/listing_detail.html:3
190
+msgid "Listing"
191
+msgstr "リスティング"
192
+
193
+#: public_frontend/templates/public_frontend/listing_detail.html:7
194
+msgid "Status"
195
+msgstr "ステータス"
196
+
197
+#: public_frontend/templates/public_frontend/listing_detail.html:9
198
+msgid "Items"
199
+msgstr "アイテム"
200
+
201
+#: public_frontend/templates/public_frontend/listing_detail.html:14
202
+msgid "No items listed"
203
+msgstr "アイテムはありません"
204
+
205
+#: public_frontend/templates/public_frontend/listings_list.html:13
206
+msgid "No public listings."
207
+msgstr "公開中のリスティングはありません。"
208
+
209
+#: public_frontend/templates/public_frontend/materials_list.html:10
210
+msgid "Category"
211
+msgstr "カテゴリ"
212
+
213
+#: public_frontend/templates/public_frontend/materials_list.html:11
214
+msgid "Default Unit"
215
+msgstr "既定の単位"
216
+
217
+#: public_frontend/templates/public_frontend/pickup_request.html:36
218
+msgid "Review Your Pickup Request"
219
+msgstr "回収依頼内容の確認"
220
+
221
+#: public_frontend/templates/public_frontend/pickup_request.html:43
222
+msgid "Pickup Address"
223
+msgstr "回収住所"
224
+
225
+#: public_frontend/templates/public_frontend/pickup_request.html:45
226
+msgid "Preferred Date/Time"
227
+msgstr "希望日時"
228
+
229
+#: public_frontend/templates/public_frontend/pickup_request.html:47
230
+msgid "Materials / Notes"
231
+msgstr "素材/メモ"
232
+
233
+#: public_frontend/templates/public_frontend/pickup_request.html:49
234
+msgid "Photos"
235
+msgstr "写真"
236
+
237
+#: public_frontend/templates/public_frontend/pickup_request.html:51
238
+msgid "No files attached."
239
+msgstr "添付ファイルはありません。"
240
+
241
+#: public_frontend/templates/public_frontend/pickup_request.html:57
242
+msgid "Edit"
243
+msgstr "編集"
244
+
245
+#: public_frontend/templates/public_frontend/pickup_request.html:58
246
+msgid "Confirm & Send"
247
+msgstr "確認して送信"
248
+
249
+#: public_frontend/templates/public_frontend/pickup_request.html:62
250
+msgid "close"
251
+msgstr "閉じる"
252
+
253
+#: public_frontend/templates/public_frontend/service_detail.html:29
254
+msgid "Back to Home"
255
+msgstr "ホームに戻る"
256
+
257
+#: translations/menu_labels.py:4
258
+msgid "Dashboard"
259
+msgstr "ダッシュボード"
260
+
261
+#: translations/menu_labels.py:5
262
+msgid "Profiles"
263
+msgstr "プロフィール"
264
+
265
+#: translations/menu_labels.py:6
266
+msgid "Opportunities"
267
+msgstr "案件"
268
+
269
+#: translations/menu_labels.py:7
270
+msgid "Intro Requests"
271
+msgstr "紹介リクエスト"
272
+
273
+#: translations/menu_labels.py:8
274
+msgid "Leaderboard"
275
+msgstr "ランキング"
276
+
277
+#: translations/menu_labels.py:9
278
+msgid "Leads"
279
+msgstr "リード"
280
+
281
+#: translations/menu_labels.py:10
282
+msgid "CMS Posts"
283
+msgstr "CMS投稿"
284
+
285
+#: translations/menu_labels.py:11
286
+msgid "CMS Categories"
287
+msgstr "CMSカテゴリ"
288
+
289
+#: translations/menu_labels.py:13
290
+msgid "Advanced Settings"
291
+msgstr "高度な設定"
292
+
293
+#: translations/menu_labels.py:14
294
+msgid "Organization Settings"
295
+msgstr "組織設定"
296
+
297
+#: translations/menu_labels.py:15
298
+msgid "Organization Sites"
299
+msgstr "組織サイト"
300
+
301
+#: translations/menu_labels.py:16
302
+msgid "Permissions"
303
+msgstr "権限"
304
+
305
+#: translations/menu_labels.py:17
306
+msgid "Users"
307
+msgstr "ユーザー"
308
+
309
+#: translations/menu_labels.py:19
310
+msgid "Customers"
311
+msgstr "顧客"
312
+
313
+#: translations/menu_labels.py:20
314
+msgid "Pickups"
315
+msgstr "回収"
316
+
317
+#: translations/menu_labels.py:21
318
+msgid "Weigh Tickets"
319
+msgstr "計量票"
320
+
321
+#: translations/menu_labels.py:22
322
+msgid "Documents"
323
+msgstr "ドキュメント"
324
+
325
+#: translations/menu_labels.py:23
326
+msgid "Provided Services"
327
+msgstr "提供サービス"
328
+
329
+#: translations/menu_labels.py:24
330
+msgid "Scrap Listings"
331
+msgstr "スクラップ出品"
332
+
333
+#: translations/menu_labels.py:25
334
+msgid "Invoices"
335
+msgstr "請求書"
336
+
337
+#: translations/menu_labels.py:26
338
+msgid "Payouts"
339
+msgstr "支払い"
340
+
341
+#: recycle_core/models.py
342
+msgid "Pickup & Logistics"
343
+msgstr "回収と物流"
344
+
345
+#: recycle_core/models.py
346
+msgid "Material Sorting"
347
+msgstr "素材の仕分け"
348
+
349
+#: recycle_core/models.py
350
+msgid "Weighing & Ticketing"
351
+msgstr "計量とチケット処理"
352
+
353
+#: recycle_core/models.py
354
+msgid "Invoicing & Payouts"
355
+msgstr "請求と支払い"
356
+
357
+#: recycle_core/models.py
358
+msgid "Reporting & Analytics"
359
+msgstr "レポートと分析"
360
+
361
+#: recycle_core/models.py
362
+msgid "Marketplace & Bidding"
363
+msgstr "マーケットプレイスと入札"
364
+
365
+#: recycle_core/models.py
366
+msgid "Compliance & Audits"
367
+msgstr "コンプライアンスと監査"
368
+
369
+#: recycle_core/models.py
370
+msgid "Consulting & Training"
371
+msgstr "コンサルティングとトレーニング"

BIN
locale/th/LC_MESSAGES/django.mo


+ 370 - 0
locale/th/LC_MESSAGES/django.po

1
+# SOME DESCRIPTIVE TITLE.
2
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+# This file is distributed under the same license as the PACKAGE package.
4
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+#
6
+msgid ""
7
+msgstr ""
8
+"Project-Id-Version: PACKAGE VERSION\n"
9
+"Report-Msgid-Bugs-To: \n"
10
+"POT-Creation-Date: 2025-09-25 22:51+0700\n"
11
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+"Language-Team: LANGUAGE <LL@li.org>\n"
14
+"Language: th\n"
15
+"MIME-Version: 1.0\n"
16
+"Content-Type: text/plain; charset=UTF-8\n"
17
+"Content-Transfer-Encoding: 8bit\n"
18
+"Plural-Forms: nplurals=1; plural=0;\n"
19
+
20
+#: admin_frontend/templates/admin_frontend/base.html:76
21
+#: translations/menu_labels.py:12
22
+msgid "Settings"
23
+msgstr "การตั้งค่า"
24
+
25
+#: admin_frontend/templates/admin_frontend/base.html:96
26
+msgid "CMS"
27
+msgstr "CMS"
28
+
29
+#: admin_frontend/templates/admin_frontend/base.html:117
30
+msgid "Frontend"
31
+msgstr "ส่วนหน้า"
32
+
33
+#: admin_frontend/templates/admin_frontend/base.html:137
34
+msgid "Organization"
35
+msgstr "องค์กร"
36
+
37
+#: admin_frontend/templates/admin_frontend/base.html:157
38
+msgid "Billing"
39
+msgstr "การเรียกเก็บเงิน"
40
+
41
+#: admin_frontend/templates/admin_frontend/base.html:177
42
+msgid "Recycle Ops"
43
+msgstr "การดำเนินงานรีไซเคิล"
44
+
45
+#: public_frontend/templates/public_frontend/base.html:17
46
+#: public_frontend/templates/public_frontend/materials_list.html:3
47
+#: translations/menu_labels.py:18
48
+msgid "Materials"
49
+msgstr "วัสดุ"
50
+
51
+#: public_frontend/templates/public_frontend/base.html:18
52
+#: public_frontend/templates/public_frontend/listings_list.html:3
53
+msgid "Listings"
54
+msgstr "รายการ"
55
+
56
+#: public_frontend/templates/public_frontend/base.html:19
57
+#: public_frontend/templates/public_frontend/pickup_request.html:3
58
+msgid "Request Pickup"
59
+msgstr "ขอรับของ"
60
+
61
+#: public_frontend/templates/public_frontend/base.html:20
62
+#: public_frontend/templates/public_frontend/blog_list.html:3
63
+msgid "Blog"
64
+msgstr "บล็อก"
65
+
66
+#: public_frontend/templates/public_frontend/base.html:21
67
+#: public_frontend/templates/public_frontend/contact.html:3
68
+msgid "Contact"
69
+msgstr "ติดต่อเรา"
70
+
71
+#: public_frontend/templates/public_frontend/base.html:53
72
+msgid "Staff Login"
73
+msgstr "เข้าสู่ระบบเจ้าหน้าที่"
74
+
75
+#: public_frontend/templates/public_frontend/blog_list.html:14
76
+msgid "No image"
77
+msgstr "ไม่มีรูปภาพ"
78
+
79
+#: public_frontend/templates/public_frontend/blog_list.html:24
80
+#: public_frontend/templates/public_frontend/home.html:92
81
+msgid "No posts yet."
82
+msgstr "ยังไม่มีโพสต์"
83
+
84
+#: public_frontend/templates/public_frontend/contact.html:5
85
+#: public_frontend/templates/public_frontend/home.html:99
86
+msgid "Contact Us"
87
+msgstr "ติดต่อเรา"
88
+
89
+#: public_frontend/templates/public_frontend/contact.html:10
90
+#: public_frontend/templates/public_frontend/home.html:104
91
+#: public_frontend/templates/public_frontend/materials_list.html:9
92
+#: public_frontend/templates/public_frontend/pickup_request.html:38
93
+msgid "Name"
94
+msgstr "ชื่อ"
95
+
96
+#: public_frontend/templates/public_frontend/contact.html:14
97
+#: public_frontend/templates/public_frontend/home.html:108
98
+#: public_frontend/templates/public_frontend/pickup_request.html:40
99
+msgid "Email"
100
+msgstr "อีเมล"
101
+
102
+#: public_frontend/templates/public_frontend/contact.html:20
103
+#: public_frontend/templates/public_frontend/pickup_request.html:41
104
+msgid "Phone"
105
+msgstr "โทรศัพท์"
106
+
107
+#: public_frontend/templates/public_frontend/contact.html:24
108
+#: public_frontend/templates/public_frontend/home.html:113
109
+msgid "Subject"
110
+msgstr "หัวข้อ"
111
+
112
+#: public_frontend/templates/public_frontend/contact.html:29
113
+#: public_frontend/templates/public_frontend/home.html:117
114
+msgid "Message"
115
+msgstr "ข้อความ"
116
+
117
+#: public_frontend/templates/public_frontend/contact.html:33
118
+msgid "Send"
119
+msgstr "ส่ง"
120
+
121
+#: public_frontend/templates/public_frontend/home.html:3
122
+msgid "Home"
123
+msgstr "หน้าแรก"
124
+
125
+#: public_frontend/templates/public_frontend/home.html:9
126
+msgid "Streamline Your Factory's Recycling"
127
+msgstr "ปรับปรุงการรีไซเคิลของโรงงานให้มีประสิทธิภาพ"
128
+
129
+#: public_frontend/templates/public_frontend/home.html:10
130
+msgid ""
131
+"We partner with businesses like yours to manage scrap materials efficiently. "
132
+"Browse listings, request a pickup, and turn your waste into a resource."
133
+msgstr "เราร่วมมือกับธุรกิจเช่นคุณเพื่อจัดการเศษวัสดุอย่างมีประสิทธิภาพ เรียกดูรายการ ขอรับของ และเปลี่ยนของเสียให้เป็นทรัพยากร"
134
+
135
+#: public_frontend/templates/public_frontend/home.html:12
136
+#: public_frontend/templates/public_frontend/pickup_request.html:6
137
+msgid "Request a Pickup"
138
+msgstr "ขอรับของ"
139
+
140
+#: public_frontend/templates/public_frontend/home.html:13
141
+msgid "Browse Listings"
142
+msgstr "ดูรายการ"
143
+
144
+#: public_frontend/templates/public_frontend/home.html:21
145
+msgid "Our Services"
146
+msgstr "บริการของเรา"
147
+
148
+#: public_frontend/templates/public_frontend/home.html:40
149
+msgid "Materials We Accept"
150
+msgstr "วัสดุที่รับ"
151
+
152
+#: public_frontend/templates/public_frontend/home.html:41
153
+#: public_frontend/templates/public_frontend/home.html:82
154
+msgid "View all"
155
+msgstr "ดูทั้งหมด"
156
+
157
+#: public_frontend/templates/public_frontend/home.html:51
158
+#: public_frontend/templates/public_frontend/materials_list.html:22
159
+msgid "No materials published."
160
+msgstr "ยังไม่มีวัสดุเผยแพร่"
161
+
162
+#: public_frontend/templates/public_frontend/home.html:60
163
+msgid "Open Listings"
164
+msgstr "รายการที่เปิดอยู่"
165
+
166
+#: public_frontend/templates/public_frontend/home.html:61
167
+msgid "Browse all"
168
+msgstr "ดูทั้งหมด"
169
+
170
+#: public_frontend/templates/public_frontend/home.html:66
171
+#: public_frontend/templates/public_frontend/listing_detail.html:7
172
+#: public_frontend/templates/public_frontend/listings_list.html:8
173
+msgid "Ends"
174
+msgstr "สิ้นสุด"
175
+
176
+#: public_frontend/templates/public_frontend/home.html:71
177
+msgid "No public listings at the moment."
178
+msgstr "ขณะนี้ยังไม่มีรายการสาธารณะ"
179
+
180
+#: public_frontend/templates/public_frontend/home.html:81
181
+msgid "Industry Insights"
182
+msgstr "ข้อมูลเชิงลึกอุตสาหกรรม"
183
+
184
+#: public_frontend/templates/public_frontend/home.html:121
185
+msgid "Send Message"
186
+msgstr "ส่งข้อความ"
187
+
188
+#: public_frontend/templates/public_frontend/listing_detail.html:3
189
+msgid "Listing"
190
+msgstr "รายการ"
191
+
192
+#: public_frontend/templates/public_frontend/listing_detail.html:7
193
+msgid "Status"
194
+msgstr "สถานะ"
195
+
196
+#: public_frontend/templates/public_frontend/listing_detail.html:9
197
+msgid "Items"
198
+msgstr "รายการ"
199
+
200
+#: public_frontend/templates/public_frontend/listing_detail.html:14
201
+msgid "No items listed"
202
+msgstr "ยังไม่มีรายการ"
203
+
204
+#: public_frontend/templates/public_frontend/listings_list.html:13
205
+msgid "No public listings."
206
+msgstr "ยังไม่มีรายการสาธารณะ"
207
+
208
+#: public_frontend/templates/public_frontend/materials_list.html:10
209
+msgid "Category"
210
+msgstr "หมวดหมู่"
211
+
212
+#: public_frontend/templates/public_frontend/materials_list.html:11
213
+msgid "Default Unit"
214
+msgstr "หน่วยเริ่มต้น"
215
+
216
+#: public_frontend/templates/public_frontend/pickup_request.html:36
217
+msgid "Review Your Pickup Request"
218
+msgstr "ตรวจสอบคำขอรับของของคุณ"
219
+
220
+#: public_frontend/templates/public_frontend/pickup_request.html:43
221
+msgid "Pickup Address"
222
+msgstr "ที่อยู่รับของ"
223
+
224
+#: public_frontend/templates/public_frontend/pickup_request.html:45
225
+msgid "Preferred Date/Time"
226
+msgstr "วันที่/เวลา ที่ต้องการ"
227
+
228
+#: public_frontend/templates/public_frontend/pickup_request.html:47
229
+msgid "Materials / Notes"
230
+msgstr "วัสดุ / หมายเหตุ"
231
+
232
+#: public_frontend/templates/public_frontend/pickup_request.html:49
233
+msgid "Photos"
234
+msgstr "รูปภาพ"
235
+
236
+#: public_frontend/templates/public_frontend/pickup_request.html:51
237
+msgid "No files attached."
238
+msgstr "ไม่มีไฟล์แนบ"
239
+
240
+#: public_frontend/templates/public_frontend/pickup_request.html:57
241
+msgid "Edit"
242
+msgstr "แก้ไข"
243
+
244
+#: public_frontend/templates/public_frontend/pickup_request.html:58
245
+msgid "Confirm & Send"
246
+msgstr "ยืนยันและส่ง"
247
+
248
+#: public_frontend/templates/public_frontend/pickup_request.html:62
249
+msgid "close"
250
+msgstr "ปิด"
251
+
252
+#: public_frontend/templates/public_frontend/service_detail.html:29
253
+msgid "Back to Home"
254
+msgstr "กลับสู่หน้าแรก"
255
+
256
+#: translations/menu_labels.py:4
257
+msgid "Dashboard"
258
+msgstr "แดชบอร์ด"
259
+
260
+#: translations/menu_labels.py:5
261
+msgid "Profiles"
262
+msgstr "โปรไฟล์"
263
+
264
+#: translations/menu_labels.py:6
265
+msgid "Opportunities"
266
+msgstr "โอกาส"
267
+
268
+#: translations/menu_labels.py:7
269
+msgid "Intro Requests"
270
+msgstr "คำขอแนะนำ"
271
+
272
+#: translations/menu_labels.py:8
273
+msgid "Leaderboard"
274
+msgstr "ตารางจัดอันดับ"
275
+
276
+#: translations/menu_labels.py:9
277
+msgid "Leads"
278
+msgstr "ลีด"
279
+
280
+#: translations/menu_labels.py:10
281
+msgid "CMS Posts"
282
+msgstr "โพสต์ CMS"
283
+
284
+#: translations/menu_labels.py:11
285
+msgid "CMS Categories"
286
+msgstr "หมวดหมู่ CMS"
287
+
288
+#: translations/menu_labels.py:13
289
+msgid "Advanced Settings"
290
+msgstr "การตั้งค่าขั้นสูง"
291
+
292
+#: translations/menu_labels.py:14
293
+msgid "Organization Settings"
294
+msgstr "การตั้งค่าองค์กร"
295
+
296
+#: translations/menu_labels.py:15
297
+msgid "Organization Sites"
298
+msgstr "ไซต์ขององค์กร"
299
+
300
+#: translations/menu_labels.py:16
301
+msgid "Permissions"
302
+msgstr "สิทธิ์การใช้งาน"
303
+
304
+#: translations/menu_labels.py:17
305
+msgid "Users"
306
+msgstr "ผู้ใช้"
307
+
308
+#: translations/menu_labels.py:19
309
+msgid "Customers"
310
+msgstr "ลูกค้า"
311
+
312
+#: translations/menu_labels.py:20
313
+msgid "Pickups"
314
+msgstr "การรับของ"
315
+
316
+#: translations/menu_labels.py:21
317
+msgid "Weigh Tickets"
318
+msgstr "ใบชั่งน้ำหนัก"
319
+
320
+#: translations/menu_labels.py:22
321
+msgid "Documents"
322
+msgstr "เอกสาร"
323
+
324
+#: translations/menu_labels.py:23
325
+msgid "Provided Services"
326
+msgstr "บริการที่ให้"
327
+
328
+#: translations/menu_labels.py:24
329
+msgid "Scrap Listings"
330
+msgstr "รายการเศษวัสดุ"
331
+
332
+#: translations/menu_labels.py:25
333
+msgid "Invoices"
334
+msgstr "ใบแจ้งหนี้"
335
+
336
+#: translations/menu_labels.py:26
337
+msgid "Payouts"
338
+msgstr "การจ่ายเงิน"
339
+
340
+#: recycle_core/models.py
341
+msgid "Pickup & Logistics"
342
+msgstr "การรับของและโลจิสติกส์"
343
+
344
+#: recycle_core/models.py
345
+msgid "Material Sorting"
346
+msgstr "การคัดแยกวัสดุ"
347
+
348
+#: recycle_core/models.py
349
+msgid "Weighing & Ticketing"
350
+msgstr "การชั่งน้ำหนักและออกบัตรชั่ง"
351
+
352
+#: recycle_core/models.py
353
+msgid "Invoicing & Payouts"
354
+msgstr "การวางบิลและการจ่ายเงิน"
355
+
356
+#: recycle_core/models.py
357
+msgid "Reporting & Analytics"
358
+msgstr "การรายงานและการวิเคราะห์"
359
+
360
+#: recycle_core/models.py
361
+msgid "Marketplace & Bidding"
362
+msgstr "มาร์เก็ตเพลสและการประมูล"
363
+
364
+#: recycle_core/models.py
365
+msgid "Compliance & Audits"
366
+msgstr "การปฏิบัติตามและการตรวจสอบ"
367
+
368
+#: recycle_core/models.py
369
+msgid "Consulting & Training"
370
+msgstr "ที่ปรึกษาและการฝึกอบรม"

+ 18 - 0
orgs/apps.py

14
         except Exception:
14
         except Exception:
15
             # Avoid startup hard-fail if migrations/apps not ready; re-raise only in debug if needed
15
             # Avoid startup hard-fail if migrations/apps not ready; re-raise only in debug if needed
16
             raise
16
             raise
17
+
18
+        # Monkey-patch User.__str__ to show "<username>, <fullname>"
19
+        # This affects labels in forms and general stringification.
20
+        try:
21
+            from django.contrib.auth import get_user_model
22
+
23
+            User = get_user_model()
24
+
25
+            def _user_str(self):  # type: ignore[override]
26
+                full = self.get_full_name().strip()
27
+                return f"{self.username}, {full}" if full else f"{self.username}"
28
+
29
+            # Assign only if not already customized
30
+            if getattr(User.__str__, "__name__", None) != "_user_str":  # type: ignore[attr-defined]
31
+                User.__str__ = _user_str  # type: ignore[assignment]
32
+        except Exception:
33
+            # Do not block app startup if something goes wrong
34
+            pass
17
         return super().ready()
35
         return super().ready()

+ 2 - 1
orgs/views_admin.py

165
         final_ids = [pid for pid in sel_ids if pid in allowed_ids]
165
         final_ids = [pid for pid in sel_ids if pid in allowed_ids]
166
         group.permissions.set(Permission.objects.filter(id__in=final_ids))
166
         group.permissions.set(Permission.objects.filter(id__in=final_ids))
167
         messages.success(request, f"Permissions updated for group '{group.name}'.")
167
         messages.success(request, f"Permissions updated for group '{group.name}'.")
168
-        return redirect("orgs_admin:permissions_overview")
168
+        # Redirect back to the current URL (stay on the edit page)
169
+        return redirect(request.get_full_path())
169
 
170
 
170
     current_ids = set(group.permissions.values_list("id", flat=True))
171
     current_ids = set(group.permissions.values_list("id", flat=True))
171
 
172
 

+ 22 - 8
public_frontend/templates/public_frontend/base.html

1
-{% load tailwind_tags static %}
1
+{% load tailwind_tags static i18n %}
2
 <!DOCTYPE html>
2
 <!DOCTYPE html>
3
-<html lang="en">
3
+{% get_current_language as LANGUAGE_CODE %}
4
+<html lang="{{ LANGUAGE_CODE }}">
4
 <head>
5
 <head>
5
   <meta charset="utf-8" />
6
   <meta charset="utf-8" />
6
   <meta name="viewport" content="width=device-width, initial-scale=1" />
7
   <meta name="viewport" content="width=device-width, initial-scale=1" />
13
     <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 flex items-center justify-between h-16">
14
     <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 flex items-center justify-between h-16">
14
       <a href="/" class="text-lg font-semibold">{% if current_site %}{{ current_site.name }}{% else %}Ecoloop{% endif %}</a>
15
       <a href="/" class="text-lg font-semibold">{% if current_site %}{{ current_site.name }}{% else %}Ecoloop{% endif %}</a>
15
       <nav class="hidden md:flex items-center gap-6">
16
       <nav class="hidden md:flex items-center gap-6">
16
-        <a href="/#materials" class="hover:text-black/70">Materials</a>
17
-        <a href="/#listings" class="hover:text-black/70">Listings</a>
18
-        <a href="/#pickup-request" class="hover:text-black/70">Request Pickup</a>
19
-        <a href="/#blog" class="hover:text-black/70">Blog</a>
20
-        <a href="/#contact" class="hover:text-black/70">Contact</a>
17
+        <a href="/#materials" class="hover:text-black/70">{% trans "Materials" %}</a>
18
+        <a href="/#listings" class="hover:text-black/70">{% trans "Listings" %}</a>
19
+        <a href="/#pickup-request" class="hover:text-black/70">{% trans "Request Pickup" %}</a>
20
+        <a href="/#blog" class="hover:text-black/70">{% trans "Blog" %}</a>
21
+        <a href="/#contact" class="hover:text-black/70">{% trans "Contact" %}</a>
21
       </nav>
22
       </nav>
23
+      <form method="post" action="{% url 'set_language' %}" class="hidden md:block ml-4">
24
+        {% csrf_token %}
25
+        <input type="hidden" name="next" value="{{ request.get_full_path }}" />
26
+        {% get_available_languages as langs %}
27
+        {% get_language_info_list for langs as languages %}
28
+        <label for="lang" class="sr-only">Language</label>
29
+        <select id="lang" name="language" class="text-sm border border-gray-300 rounded px-2 py-1 bg-white"
30
+                onchange="this.form.submit()">
31
+          {% for lang in languages %}
32
+            <option value="{{ lang.code }}" {% if lang.code == LANGUAGE_CODE %}selected{% endif %}>{{ lang.name_local }}</option>
33
+          {% endfor %}
34
+        </select>
35
+      </form>
22
     </div>
36
     </div>
23
   </header>
37
   </header>
24
   <main class="mx-auto max-w-7xl p-4 sm:p-6 lg:p-8 min-h-[80vh]">
38
   <main class="mx-auto max-w-7xl p-4 sm:p-6 lg:p-8 min-h-[80vh]">
36
   <footer class="bg-white border-t">
50
   <footer class="bg-white border-t">
37
     <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between text-sm text-gray-600">
51
     <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between text-sm text-gray-600">
38
       <span>&copy; {% now "Y" %} {% if current_site %}{{ current_site.name }}{% else %}Ecoloop{% endif %}</span>
52
       <span>&copy; {% now "Y" %} {% if current_site %}{{ current_site.name }}{% else %}Ecoloop{% endif %}</span>
39
-      <a href="/webadmin/" class="hover:underline">Staff Login</a>
53
+      <a href="/webadmin/" class="hover:underline">{% trans "Staff Login" %}</a>
40
     </div>
54
     </div>
41
   </footer>
55
   </footer>
42
 </body>
56
 </body>

+ 1 - 0
public_frontend/templates/public_frontend/blog_detail.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
+{% load i18n %}
2
 {% block title %}{{ post.title }}{% endblock %}
3
 {% block title %}{{ post.title }}{% endblock %}
3
 {% block content %}
4
 {% block content %}
4
 <article class="blog-article max-w-none bg-white rounded shadow p-6">
5
 <article class="blog-article max-w-none bg-white rounded shadow p-6">

+ 4 - 3
public_frontend/templates/public_frontend/blog_list.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
-{% block title %}Blog{% endblock %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Blog" %}{% endblock %}
3
 {% block content %}
4
 {% block content %}
4
 <div class="grid gap-4">
5
 <div class="grid gap-4">
5
   {% for p in posts %}
6
   {% for p in posts %}
10
             <img src="{{ p.feature_image.url }}" alt="{{ p.title }}" loading="lazy">
11
             <img src="{{ p.feature_image.url }}" alt="{{ p.title }}" loading="lazy">
11
           </div>
12
           </div>
12
         {% else %}
13
         {% else %}
13
-          <div class="thumb placeholder">No image</div>
14
+          <div class="thumb placeholder">{% trans "No image" %}</div>
14
         {% endif %}
15
         {% endif %}
15
         <div class="min-w-0">
16
         <div class="min-w-0">
16
           <div class="text-sm text-gray-500">{{ p.published_at|date:'M j, Y' }}</div>
17
           <div class="text-sm text-gray-500">{{ p.published_at|date:'M j, Y' }}</div>
20
       </div>
21
       </div>
21
     </a>
22
     </a>
22
   {% empty %}
23
   {% empty %}
23
-    <div class="text-gray-500">No posts yet.</div>
24
+    <div class="text-gray-500">{% trans "No posts yet." %}</div>
24
   {% endfor %}
25
   {% endfor %}
25
   </div>
26
   </div>
26
 <div class="mt-4">{% include 'admin_frontend/_pagination.html' %}</div>
27
 <div class="mt-4">{% include 'admin_frontend/_pagination.html' %}</div>

+ 9 - 8
public_frontend/templates/public_frontend/contact.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
-{% block title %}Contact{% endblock %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Contact" %}{% endblock %}
3
 {% block content %}
4
 {% block content %}
4
-<h1 class="text-xl font-semibold mb-4">Contact Us{% if org %} — {{ org.name }}{% endif %}</h1>
5
+<h1 class="text-xl font-semibold mb-4">{% trans "Contact Us" %}{% if org %} — {{ org.name }}{% endif %}</h1>
5
 <form method="post" class="bg-white rounded shadow p-4 grid gap-4">
6
 <form method="post" class="bg-white rounded shadow p-4 grid gap-4">
6
   {% csrf_token %}
7
   {% csrf_token %}
7
   <div class="grid md:grid-cols-2 gap-4">
8
   <div class="grid md:grid-cols-2 gap-4">
8
     <div>
9
     <div>
9
-      <label class="block text-sm font-medium mb-1">Name</label>
10
+      <label class="block text-sm font-medium mb-1">{% trans "Name" %}</label>
10
       <input type="text" name="name" value="{{ form.name.value|default:'' }}" required class="w-full border rounded px-3 py-2">
11
       <input type="text" name="name" value="{{ form.name.value|default:'' }}" required class="w-full border rounded px-3 py-2">
11
     </div>
12
     </div>
12
     <div>
13
     <div>
13
-      <label class="block text-sm font-medium mb-1">Email</label>
14
+      <label class="block text-sm font-medium mb-1">{% trans "Email" %}</label>
14
       <input type="email" name="email" value="{{ form.email.value|default:'' }}" required class="w-full border rounded px-3 py-2">
15
       <input type="email" name="email" value="{{ form.email.value|default:'' }}" required class="w-full border rounded px-3 py-2">
15
     </div>
16
     </div>
16
   </div>
17
   </div>
17
   <div class="grid md:grid-cols-2 gap-4">
18
   <div class="grid md:grid-cols-2 gap-4">
18
     <div>
19
     <div>
19
-      <label class="block text-sm font-medium mb-1">Phone</label>
20
+      <label class="block text-sm font-medium mb-1">{% trans "Phone" %}</label>
20
       <input type="text" name="phone" value="{{ form.phone.value|default:'' }}" class="w-full border rounded px-3 py-2">
21
       <input type="text" name="phone" value="{{ form.phone.value|default:'' }}" class="w-full border rounded px-3 py-2">
21
     </div>
22
     </div>
22
     <div>
23
     <div>
23
-      <label class="block text-sm font-medium mb-1">Subject</label>
24
+      <label class="block text-sm font-medium mb-1">{% trans "Subject" %}</label>
24
       <input type="text" name="subject" value="{{ form.subject.value|default:'' }}" class="w-full border rounded px-3 py-2">
25
       <input type="text" name="subject" value="{{ form.subject.value|default:'' }}" class="w-full border rounded px-3 py-2">
25
     </div>
26
     </div>
26
   </div>
27
   </div>
27
   <div>
28
   <div>
28
-    <label class="block text-sm font-medium mb-1">Message</label>
29
+    <label class="block text-sm font-medium mb-1">{% trans "Message" %}</label>
29
     <textarea name="message" rows="5" class="w-full border rounded px-3 py-2">{{ form.message.value|default:'' }}</textarea>
30
     <textarea name="message" rows="5" class="w-full border rounded px-3 py-2">{{ form.message.value|default:'' }}</textarea>
30
   </div>
31
   </div>
31
   <div>
32
   <div>
32
-    <button class="btn-primary" type="submit">Send</button>
33
+    <button class="btn-primary" type="submit">{% trans "Send" %}</button>
33
   </div>
34
   </div>
34
 </form>
35
 </form>
35
 <style>.btn-primary{background:#1d4ed8;color:white;padding:.5rem .75rem;border-radius:.375rem}</style>
36
 <style>.btn-primary{background:#1d4ed8;color:white;padding:.5rem .75rem;border-radius:.375rem}</style>

+ 24 - 24
public_frontend/templates/public_frontend/home.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
-{% load static %}
3
-{% block title %}Home{% endblock %}
2
+{% load static i18n %}
3
+{% block title %}{% trans "Home" %}{% endblock %}
4
 
4
 
5
 {% block content %}
5
 {% block content %}
6
 <div class="grid gap-12 md:gap-16">
6
 <div class="grid gap-12 md:gap-16">
7
   {# Hero Section #}
7
   {# Hero Section #}
8
   <section id="hero" class="bg-white rounded-lg shadow-md p-6 md:p-8 text-center">
8
   <section id="hero" class="bg-white rounded-lg shadow-md p-6 md:p-8 text-center">
9
-    <h1 class="text-3xl md:text-4xl font-bold mb-2">Streamline Your Factory's Recycling</h1>
10
-    <p class="text-gray-600 max-w-2xl mx-auto mb-6">We partner with businesses like yours to manage scrap materials efficiently. Browse listings, request a pickup, and turn your waste into a resource.</p>
9
+    <h1 class="text-3xl md:text-4xl font-bold mb-2">{% trans "Streamline Your Factory's Recycling" %}</h1>
10
+    <p class="text-gray-600 max-w-2xl mx-auto mb-6">{% trans "We partner with businesses like yours to manage scrap materials efficiently. Browse listings, request a pickup, and turn your waste into a resource." %}</p>
11
     <div class="flex gap-3 justify-center">
11
     <div class="flex gap-3 justify-center">
12
-      <a href="{% url 'public_frontend:pickup_request' %}" class="btn-primary">Request a Pickup</a>
13
-      <a href="#listings" class="btn-outline">Browse Listings</a>
12
+      <a href="{% url 'public_frontend:pickup_request' %}" class="btn-primary">{% trans "Request a Pickup" %}</a>
13
+      <a href="#listings" class="btn-outline">{% trans "Browse Listings" %}</a>
14
     </div>
14
     </div>
15
   </section>
15
   </section>
16
 
16
 
18
   {% if services %}
18
   {% if services %}
19
   <section id="services">
19
   <section id="services">
20
     <div class="flex items-center justify-between mb-3">
20
     <div class="flex items-center justify-between mb-3">
21
-      <h2 class="text-2xl font-semibold">Our Services</h2>
21
+      <h2 class="text-2xl font-semibold">{% trans "Our Services" %}</h2>
22
     </div>
22
     </div>
23
     <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
23
     <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
24
       {% for s in services %}
24
       {% for s in services %}
25
         <a href="{% url 'public_frontend:service_detail' s.pk %}" class="block p-4 bg-white rounded-lg shadow-md hover:bg-gray-50 transition flex items-start gap-4">
25
         <a href="{% url 'public_frontend:service_detail' s.pk %}" class="block p-4 bg-white rounded-lg shadow-md hover:bg-gray-50 transition flex items-start gap-4">
26
           <img src="{% static 'service_icons/' %}{{ s.image_name }}" alt="{{ s.title }} icon" class="w-12 h-12 object-contain"/>
26
           <img src="{% static 'service_icons/' %}{{ s.image_name }}" alt="{{ s.title }} icon" class="w-12 h-12 object-contain"/>
27
           <div>
27
           <div>
28
-            <div class="font-medium">{{ s.title }}</div>
28
+            <div class="font-medium">{{ s.get_title_display }}</div>
29
             <div class="text-sm text-gray-600">{{ s.description }}</div>
29
             <div class="text-sm text-gray-600">{{ s.description }}</div>
30
           </div>
30
           </div>
31
         </a>
31
         </a>
37
   {# Materials Section #}
37
   {# Materials Section #}
38
   <section id="materials">
38
   <section id="materials">
39
     <div class="flex items-center justify-between mb-3">
39
     <div class="flex items-center justify-between mb-3">
40
-      <h2 class="text-2xl font-semibold">Materials We Accept</h2>
41
-      <a href="{% url 'public_frontend:materials_list' %}" class="text-blue-700 hover:underline">View all</a>
40
+      <h2 class="text-2xl font-semibold">{% trans "Materials We Accept" %}</h2>
41
+      <a href="{% url 'public_frontend:materials_list' %}" class="text-blue-700 hover:underline">{% trans "View all" %}</a>
42
     </div>
42
     </div>
43
     <div class="bg-white rounded-lg shadow-md p-4">
43
     <div class="bg-white rounded-lg shadow-md p-4">
44
         <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
44
         <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
48
                     <p class="text-sm text-gray-500">{{ m.category.name }}</p>
48
                     <p class="text-sm text-gray-500">{{ m.category.name }}</p>
49
                 </div>
49
                 </div>
50
             {% empty %}
50
             {% empty %}
51
-                <p class="text-gray-500 col-span-full">No materials published.</p>
51
+                <p class="text-gray-500 col-span-full">{% trans "No materials published." %}</p>
52
             {% endfor %}
52
             {% endfor %}
53
         </div>
53
         </div>
54
     </div>
54
     </div>
57
   {# Open Listings Section #}
57
   {# Open Listings Section #}
58
   <section id="listings">
58
   <section id="listings">
59
     <div class="flex items-center justify-between mb-3">
59
     <div class="flex items-center justify-between mb-3">
60
-      <h2 class="text-2xl font-semibold">Open Listings</h2>
61
-      <a href="{% url 'public_frontend:listings_list' %}" class="text-blue-700 hover:underline">Browse all</a>
60
+      <h2 class="text-2xl font-semibold">{% trans "Open Listings" %}</h2>
61
+      <a href="{% url 'public_frontend:listings_list' %}" class="text-blue-700 hover:underline">{% trans "Browse all" %}</a>
62
     </div>
62
     </div>
63
     <div class="grid md:grid-cols-3 gap-4">
63
     <div class="grid md:grid-cols-3 gap-4">
64
       {% for l in listings %}
64
       {% for l in listings %}
65
         <a href="{% url 'public_frontend:listing_detail' l.id %}" class="block p-4 border rounded bg-white hover:bg-gray-50 transition">
65
         <a href="{% url 'public_frontend:listing_detail' l.id %}" class="block p-4 border rounded bg-white hover:bg-gray-50 transition">
66
-          <div class="text-sm text-gray-500">Ends {{ l.ends_at|default:'—' }}</div>
66
+          <div class="text-sm text-gray-500">{% trans "Ends" %} {{ l.ends_at|default:'—' }}</div>
67
           <div class="font-medium">{{ l.title }}</div>
67
           <div class="font-medium">{{ l.title }}</div>
68
           <div class="text-sm text-gray-600 line-clamp-2">{{ l.description|truncatechars:120 }}</div>
68
           <div class="text-sm text-gray-600 line-clamp-2">{{ l.description|truncatechars:120 }}</div>
69
         </a>
69
         </a>
70
       {% empty %}
70
       {% empty %}
71
-        <div class="text-gray-500 p-4 bg-white rounded-lg shadow-md col-span-full">No public listings at the moment.</div>
71
+        <div class="text-gray-500 p-4 bg-white rounded-lg shadow-md col-span-full">{% trans "No public listings at the moment." %}</div>
72
       {% endfor %}
72
       {% endfor %}
73
     </div>
73
     </div>
74
   </section>
74
   </section>
78
   {# Blog Section #}
78
   {# Blog Section #}
79
   <section id="blog">
79
   <section id="blog">
80
     <div class="flex items-center justify-between mb-3">
80
     <div class="flex items-center justify-between mb-3">
81
-      <h2 class="text-2xl font-semibold">Industry Insights</h2>
82
-      <a href="{% url 'public_frontend:blog_list' %}" class="text-blue-700 hover:underline">View all</a>
81
+      <h2 class="text-2xl font-semibold">{% trans "Industry Insights" %}</h2>
82
+      <a href="{% url 'public_frontend:blog_list' %}" class="text-blue-700 hover:underline">{% trans "View all" %}</a>
83
     </div>
83
     </div>
84
     <div class="grid md:grid-cols-3 gap-4">
84
     <div class="grid md:grid-cols-3 gap-4">
85
       {% for p in posts %}
85
       {% for p in posts %}
89
           <div class="text-sm text-gray-600 line-clamp-2">{{ p.excerpt|default:p.content|striptags|truncatechars:120 }}</div>
89
           <div class="text-sm text-gray-600 line-clamp-2">{{ p.excerpt|default:p.content|striptags|truncatechars:120 }}</div>
90
         </a>
90
         </a>
91
       {% empty %}
91
       {% empty %}
92
-        <div class="text-gray-500 p-4 bg-white rounded-lg shadow-md col-span-full">No posts yet.</div>
92
+        <div class="text-gray-500 p-4 bg-white rounded-lg shadow-md col-span-full">{% trans "No posts yet." %}</div>
93
       {% endfor %}
93
       {% endfor %}
94
     </div>
94
     </div>
95
   </section>
95
   </section>
96
 
96
 
97
   {# Contact Section #}
97
   {# Contact Section #}
98
   <section id="contact">
98
   <section id="contact">
99
-    <h2 class="text-2xl font-semibold mb-3">Contact Us</h2>
99
+    <h2 class="text-2xl font-semibold mb-3">{% trans "Contact Us" %}</h2>
100
     <form method="post" action="{% url 'public_frontend:contact' %}" class="bg-white rounded-lg shadow-md p-4 grid gap-4">
100
     <form method="post" action="{% url 'public_frontend:contact' %}" class="bg-white rounded-lg shadow-md p-4 grid gap-4">
101
       {% csrf_token %}
101
       {% csrf_token %}
102
       <div class="grid md:grid-cols-2 gap-4">
102
       <div class="grid md:grid-cols-2 gap-4">
103
         <div>
103
         <div>
104
-          <label class="block text-sm font-medium mb-1">Name</label>
104
+          <label class="block text-sm font-medium mb-1">{% trans "Name" %}</label>
105
           <input type="text" name="name" value="{{ contact_form.name.value|default:'' }}" required class="w-full border rounded px-3 py-2">
105
           <input type="text" name="name" value="{{ contact_form.name.value|default:'' }}" required class="w-full border rounded px-3 py-2">
106
         </div>
106
         </div>
107
         <div>
107
         <div>
108
-          <label class="block text-sm font-medium mb-1">Email</label>
108
+          <label class="block text-sm font-medium mb-1">{% trans "Email" %}</label>
109
           <input type="email" name="email" value="{{ contact_form.email.value|default:'' }}" required class="w-full border rounded px-3 py-2">
109
           <input type="email" name="email" value="{{ contact_form.email.value|default:'' }}" required class="w-full border rounded px-3 py-2">
110
         </div>
110
         </div>
111
       </div>
111
       </div>
112
       <div>
112
       <div>
113
-        <label class="block text-sm font-medium mb-1">Subject</label>
113
+        <label class="block text-sm font-medium mb-1">{% trans "Subject" %}</label>
114
         <input type="text" name="subject" value="{{ contact_form.subject.value|default:'' }}" class="w-full border rounded px-3 py-2">
114
         <input type="text" name="subject" value="{{ contact_form.subject.value|default:'' }}" class="w-full border rounded px-3 py-2">
115
       </div>
115
       </div>
116
       <div>
116
       <div>
117
-        <label class="block text-sm font-medium mb-1">Message</label>
117
+        <label class="block text-sm font-medium mb-1">{% trans "Message" %}</label>
118
         <textarea name="message" rows="5" class="w-full border rounded px-3 py-2">{{ contact_form.message.value|default:'' }}</textarea>
118
         <textarea name="message" rows="5" class="w-full border rounded px-3 py-2">{{ contact_form.message.value|default:'' }}</textarea>
119
       </div>
119
       </div>
120
       <div>
120
       <div>
121
-        <button class="btn-primary" type="submit">Send Message</button>
121
+        <button class="btn-primary" type="submit">{% trans "Send Message" %}</button>
122
       </div>
122
       </div>
123
     </form>
123
     </form>
124
   </section>
124
   </section>

+ 5 - 4
public_frontend/templates/public_frontend/listing_detail.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
-{% block title %}Listing {{ listing.id }}{% endblock %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Listing" %} {{ listing.id }}{% endblock %}
3
 {% block content %}
4
 {% block content %}
4
 <article class="bg-white rounded shadow p-6">
5
 <article class="bg-white rounded shadow p-6">
5
   <h1 class="text-2xl font-semibold mb-1">{{ listing.title }}</h1>
6
   <h1 class="text-2xl font-semibold mb-1">{{ listing.title }}</h1>
6
-  <div class="text-sm text-gray-500 mb-4">Status: {{ listing.status }}{% if listing.ends_at %} • Ends {{ listing.ends_at }}{% endif %}</div>
7
+  <div class="text-sm text-gray-500 mb-4">{% trans "Status" %}: {{ listing.status }}{% if listing.ends_at %} • {% trans "Ends" %} {{ listing.ends_at }}{% endif %}</div>
7
   <p class="prose max-w-none">{{ listing.description|linebreaks }}</p>
8
   <p class="prose max-w-none">{{ listing.description|linebreaks }}</p>
8
-  <h2 class="font-semibold mt-6 mb-2">Items</h2>
9
+  <h2 class="font-semibold mt-6 mb-2">{% trans "Items" %}</h2>
9
   <ul class="list-disc ml-6 text-sm text-gray-700">
10
   <ul class="list-disc ml-6 text-sm text-gray-700">
10
   {% for it in listing.items.all %}
11
   {% for it in listing.items.all %}
11
     <li>{{ it.material.name }} — {{ it.quantity_estimate }} {{ it.get_unit_display }}</li>
12
     <li>{{ it.material.name }} — {{ it.quantity_estimate }} {{ it.get_unit_display }}</li>
12
   {% empty %}
13
   {% empty %}
13
-    <li>No items listed</li>
14
+    <li>{% trans "No items listed" %}</li>
14
   {% endfor %}
15
   {% endfor %}
15
   </ul>
16
   </ul>
16
 </article>
17
 </article>

+ 4 - 4
public_frontend/templates/public_frontend/listings_list.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
-{% block title %}Listings{% endblock %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Listings" %}{% endblock %}
3
 {% block content %}
4
 {% block content %}
4
 <div class="grid md:grid-cols-3 gap-4">
5
 <div class="grid md:grid-cols-3 gap-4">
5
   {% for l in listings %}
6
   {% for l in listings %}
6
     <a href="{% url 'public_frontend:listing_detail' l.id %}" class="block p-4 border rounded bg-white hover:bg-gray-50">
7
     <a href="{% url 'public_frontend:listing_detail' l.id %}" class="block p-4 border rounded bg-white hover:bg-gray-50">
7
-      <div class="text-sm text-gray-500">Ends {{ l.ends_at|default:'—' }}</div>
8
+      <div class="text-sm text-gray-500">{% trans "Ends" %} {{ l.ends_at|default:'—' }}</div>
8
       <div class="font-medium">{{ l.title }}</div>
9
       <div class="font-medium">{{ l.title }}</div>
9
       <div class="text-sm text-gray-600 line-clamp-2">{{ l.description|truncatechars:140 }}</div>
10
       <div class="text-sm text-gray-600 line-clamp-2">{{ l.description|truncatechars:140 }}</div>
10
     </a>
11
     </a>
11
   {% empty %}
12
   {% empty %}
12
-    <div class="text-gray-500">No public listings.</div>
13
+    <div class="text-gray-500">{% trans "No public listings." %}</div>
13
   {% endfor %}
14
   {% endfor %}
14
 </div>
15
 </div>
15
 <div class="mt-4">
16
 <div class="mt-4">
16
   {% include 'admin_frontend/_pagination.html' %}
17
   {% include 'admin_frontend/_pagination.html' %}
17
   </div>
18
   </div>
18
 {% endblock %}
19
 {% endblock %}
19
-

+ 6 - 5
public_frontend/templates/public_frontend/materials_list.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
-{% block title %}Materials{% endblock %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Materials" %}{% endblock %}
3
 {% block content %}
4
 {% block content %}
4
 <div class="bg-white rounded shadow overflow-hidden">
5
 <div class="bg-white rounded shadow overflow-hidden">
5
   <table class="min-w-full text-sm">
6
   <table class="min-w-full text-sm">
6
     <thead class="bg-gray-50 text-left">
7
     <thead class="bg-gray-50 text-left">
7
       <tr>
8
       <tr>
8
-        <th class="px-4 py-2">Name</th>
9
-        <th class="px-4 py-2">Category</th>
10
-        <th class="px-4 py-2">Default Unit</th>
9
+        <th class="px-4 py-2">{% trans "Name" %}</th>
10
+        <th class="px-4 py-2">{% trans "Category" %}</th>
11
+        <th class="px-4 py-2">{% trans "Default Unit" %}</th>
11
       </tr>
12
       </tr>
12
     </thead>
13
     </thead>
13
     <tbody>
14
     <tbody>
18
           <td class="px-4 py-2">{{ m.get_default_unit_display }}</td>
19
           <td class="px-4 py-2">{{ m.get_default_unit_display }}</td>
19
         </tr>
20
         </tr>
20
       {% empty %}
21
       {% empty %}
21
-        <tr><td colspan="3" class="px-4 py-6 text-center text-gray-500">No materials published.</td></tr>
22
+        <tr><td colspan="3" class="px-4 py-6 text-center text-gray-500">{% trans "No materials published." %}</td></tr>
22
       {% endfor %}
23
       {% endfor %}
23
     </tbody>
24
     </tbody>
24
   </table>
25
   </table>

+ 15 - 14
public_frontend/templates/public_frontend/pickup_request.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
-{% block title %}Request Pickup{% endblock %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Request Pickup" %}{% endblock %}
3
 {% load crispy_forms_tags widget_tweaks %}
4
 {% load crispy_forms_tags widget_tweaks %}
4
 {% block content %}
5
 {% block content %}
5
-<h1 class="text-xl font-semibold mb-4">Request a Pickup</h1>
6
+<h1 class="text-xl font-semibold mb-4">{% trans "Request a Pickup" %}</h1>
6
 <div x-data="pickupForm()">
7
 <div x-data="pickupForm()">
7
   <div class="bg-white rounded shadow p-4" >
8
   <div class="bg-white rounded shadow p-4" >
8
     <form x-ref="form" id="pickup-form" method="post" enctype="multipart/form-data" class="space-y-4">
9
     <form x-ref="form" id="pickup-form" method="post" enctype="multipart/form-data" class="space-y-4">
32
 
33
 
33
   <dialog x-ref="dialog" id="review_modal" class="modal dark:bg-black/40">
34
   <dialog x-ref="dialog" id="review_modal" class="modal dark:bg-black/40">
34
     <div class="modal-box dark:bg-gray-900 dark:text-gray-100 dark:border dark:border-gray-700">
35
     <div class="modal-box dark:bg-gray-900 dark:text-gray-100 dark:border dark:border-gray-700">
35
-      <h3 class="font-bold text-lg">Review Your Pickup Request</h3>
36
+      <h3 class="font-bold text-lg">{% trans "Review Your Pickup Request" %}</h3>
36
       <div class="py-4 space-y-3">
37
       <div class="py-4 space-y-3">
37
-        <div><div class="text-sm text-gray-600 dark:text-gray-300">Name</div><div class="font-medium" x-text="rv.name || '—'"></div></div>
38
+        <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Name" %}</div><div class="font-medium" x-text="rv.name || '—'"></div></div>
38
         <div class="grid md:grid-cols-2 gap-4">
39
         <div class="grid md:grid-cols-2 gap-4">
39
-          <div><div class="text-sm text-gray-600 dark:text-gray-300">Email</div><div class="font-medium" x-text="rv.email || '—'"></div></div>
40
-          <div><div class="text-sm text-gray-600 dark:text-gray-300">Phone</div><div class="font-medium" x-text="rv.phone || '—'"></div></div>
40
+          <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Email" %}</div><div class="font-medium" x-text="rv.email || '—'"></div></div>
41
+          <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Phone" %}</div><div class="font-medium" x-text="rv.phone || '—'"></div></div>
41
         </div>
42
         </div>
42
-        <div><div class="text-sm text-gray-600 dark:text-gray-300">Pickup Address</div><div class="whitespace-pre-line" x-text="rv.address || '—'"></div></div>
43
+        <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Pickup Address" %}</div><div class="whitespace-pre-line" x-text="rv.address || '—'"></div></div>
43
         <div class="grid md:grid-cols-2 gap-4">
44
         <div class="grid md:grid-cols-2 gap-4">
44
-          <div><div class="text-sm text-gray-600 dark:text-gray-300">Preferred Date/Time</div><div class="font-medium" x-text="rv.preferred_at || '—'"></div></div>
45
+          <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Preferred Date/Time" %}</div><div class="font-medium" x-text="rv.preferred_at || '—'"></div></div>
45
         </div>
46
         </div>
46
-        <div><div class="text-sm text-gray-600 dark:text-gray-300">Materials / Notes</div><div class="whitespace-pre-line" x-text="rv.materials || '—'"></div></div>
47
+        <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Materials / Notes" %}</div><div class="whitespace-pre-line" x-text="rv.materials || '—'"></div></div>
47
         <div>
48
         <div>
48
-          <div class="text-sm text-gray-600 dark:text-gray-300">Photos</div>
49
+          <div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Photos" %}</div>
49
           <ul class="list-disc list-inside text-sm text-gray-700 dark:text-gray-300">
50
           <ul class="list-disc list-inside text-sm text-gray-700 dark:text-gray-300">
50
-            <template x-if="(rv.photos||[]).length === 0"><li>No files attached.</li></template>
51
+            <template x-if="(rv.photos||[]).length === 0"><li>{% trans "No files attached." %}</li></template>
51
             <template x-for="fname in rv.photos" :key="fname"><li x-text="fname"></li></template>
52
             <template x-for="fname in rv.photos" :key="fname"><li x-text="fname"></li></template>
52
           </ul>
53
           </ul>
53
         </div>
54
         </div>
54
       </div>
55
       </div>
55
       <div class="modal-action">
56
       <div class="modal-action">
56
-        <button class="btn" @click="$refs.dialog.close()">Edit</button>
57
-        <button class="btn btn-primary" @click.prevent="confirm">Confirm &amp; Send</button>
57
+        <button class="btn" @click="$refs.dialog.close()">{% trans "Edit" %}</button>
58
+        <button class="btn btn-primary" @click.prevent="confirm">{% trans "Confirm & Send" %}</button>
58
       </div>
59
       </div>
59
     </div>
60
     </div>
60
     <form method="dialog" class="modal-backdrop dark:bg-black/60">
61
     <form method="dialog" class="modal-backdrop dark:bg-black/60">
61
-      <button class="dark:text-gray-200">close</button>
62
+      <button class="dark:text-gray-200">{% trans "close" %}</button>
62
     </form>
63
     </form>
63
   </dialog>
64
   </dialog>
64
 </div>
65
 </div>

+ 2 - 2
public_frontend/templates/public_frontend/service_detail.html

1
 {% extends 'public_frontend/base.html' %}
1
 {% extends 'public_frontend/base.html' %}
2
+{% load i18n %}
2
 {% load static %}
3
 {% load static %}
3
 {% block title %}{{ service.title }}{% endblock %}
4
 {% block title %}{{ service.title }}{% endblock %}
4
 
5
 
25
   {% endif %}
26
   {% endif %}
26
 
27
 
27
   <div>
28
   <div>
28
-    <a href="/" class="text-blue-700 hover:underline">← Back to Home</a>
29
+    <a href="/" class="text-blue-700 hover:underline">← {% trans "Back to Home" %}</a>
29
   </div>
30
   </div>
30
 </article>
31
 </article>
31
 {% endblock %}
32
 {% endblock %}
32
-

+ 9 - 8
recycle_core/models.py

6
 from django.contrib.contenttypes.models import ContentType
6
 from django.contrib.contenttypes.models import ContentType
7
 from django.db import models
7
 from django.db import models
8
 from django.utils import timezone
8
 from django.utils import timezone
9
+from django.utils.translation import gettext_lazy as _
9
 from orgs.models import Organization
10
 from orgs.models import Organization
10
 from markdownfield.models import MarkdownField, RenderedMarkdownField
11
 from markdownfield.models import MarkdownField, RenderedMarkdownField
11
 from markdownfield.validators import VALIDATOR_STANDARD
12
 from markdownfield.validators import VALIDATOR_STANDARD
52
 class ProvidedService(TimestampedModel):
53
 class ProvidedService(TimestampedModel):
53
     organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="services")
54
     organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="services")
54
     TITLE_CHOICES = (
55
     TITLE_CHOICES = (
55
-        ("Pickup & Logistics", "Pickup & Logistics"),
56
-        ("Material Sorting", "Material Sorting"),
57
-        ("Weighing & Ticketing", "Weighing & Ticketing"),
58
-        ("Invoicing & Payouts", "Invoicing & Payouts"),
59
-        ("Reporting & Analytics", "Reporting & Analytics"),
60
-        ("Marketplace & Bidding", "Marketplace & Bidding"),
61
-        ("Compliance & Audits", "Compliance & Audits"),
62
-        ("Consulting & Training", "Consulting & Training"),
56
+        ("Pickup & Logistics", _("Pickup & Logistics")),
57
+        ("Material Sorting", _("Material Sorting")),
58
+        ("Weighing & Ticketing", _("Weighing & Ticketing")),
59
+        ("Invoicing & Payouts", _("Invoicing & Payouts")),
60
+        ("Reporting & Analytics", _("Reporting & Analytics")),
61
+        ("Marketplace & Bidding", _("Marketplace & Bidding")),
62
+        ("Compliance & Audits", _("Compliance & Audits")),
63
+        ("Consulting & Training", _("Consulting & Training")),
63
     )
64
     )
64
     title = models.CharField(max_length=100, choices=TITLE_CHOICES)
65
     title = models.CharField(max_length=100, choices=TITLE_CHOICES)
65
     # Short summary text
66
     # Short summary text

+ 10 - 8
recycle_core/templates/recycle_core/pickups_list.html

8
 </div>
8
 </div>
9
 
9
 
10
 <div class="bg-white rounded shadow p-4 mb-4">
10
 <div class="bg-white rounded shadow p-4 mb-4">
11
-  <form method="get" class="space-y-3 flex gap-2">
11
+  <form method="get" class="flex flex-wrap gap-3 items-end">
12
     {{ filter.form|crispy }}
12
     {{ filter.form|crispy }}
13
-    <div class="flex items-center gap-2">
13
+    <div class="flex items-center gap-2 w-full">
14
       <button class="px-3 py-2 bg-blue-600 text-white rounded">Filter</button>
14
       <button class="px-3 py-2 bg-blue-600 text-white rounded">Filter</button>
15
       <a href="{% url 'recycle_core:pickups_list' %}" class="btn-outline">Reset</a>
15
       <a href="{% url 'recycle_core:pickups_list' %}" class="btn-outline">Reset</a>
16
     </div>
16
     </div>
20
   {% endif %}
20
   {% endif %}
21
 </div>
21
 </div>
22
 
22
 
23
-<div class="bg-white rounded shadow overflow-hidden">
24
-  <table class="min-w-full divide-y divide-gray-200">
23
+<div class="bg-white rounded shadow">
24
+  <div class="overflow-x-auto">
25
+  <table class="min-w-[1100px] divide-y divide-gray-200">
25
     <thead class="bg-gray-50">
26
     <thead class="bg-gray-50">
26
       <tr>
27
       <tr>
27
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Customer</th>
28
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Customer</th>
28
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Site</th>
29
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Site</th>
29
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Status</th>
30
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Status</th>
30
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Scheduled</th>
31
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Scheduled</th>
31
-        <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Driver</th>
32
+        <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 w-56 min-w-[220px]">Driver</th>
32
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Actions</th>
33
         <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Actions</th>
33
       </tr>
34
       </tr>
34
     </thead>
35
     </thead>
38
         <td class="px-4 py-2">{{ p.customer.name }}</td>
39
         <td class="px-4 py-2">{{ p.customer.name }}</td>
39
         <td class="px-4 py-2">{{ p.site.name }}</td>
40
         <td class="px-4 py-2">{{ p.site.name }}</td>
40
         <td class="px-4 py-2"><span class="px-2 py-1 rounded bg-gray-100 text-gray-700">{{ p.status }}</span></td>
41
         <td class="px-4 py-2"><span class="px-2 py-1 rounded bg-gray-100 text-gray-700">{{ p.status }}</span></td>
41
-        <td class="px-4 py-2 text-gray-500">{{ p.scheduled_at|default:"-" }}</td>
42
-        <td class="px-4 py-2 text-gray-600">{{ p.assigned_driver.username|default:"-" }}</td>
42
+        <td class="px-4 py-2 text-gray-500">{{ p.scheduled_at|date:"SHORT_DATETIME_FORMAT"|default:"-" }}</td>
43
+        <td class="px-4 py-2 text-gray-600 w-56 min-w-[220px] whitespace-nowrap">{{ p.assigned_driver.username|default:"-" }}</td>
43
         <td class="px-4 py-2">
44
         <td class="px-4 py-2">
44
           <div class="flex flex-wrap gap-2 justify-end">
45
           <div class="flex flex-wrap gap-2 justify-end">
45
-            <a class="btn-outline btn-xs" href="{% url 'recycle_core:pickup_detail' p.id %}">View</a>
46
+            <a class="text-blue-600 hover:text-blue-700 hover:underline text-xs font-medium mr-3" href="{% url 'recycle_core:pickup_detail' p.id %}">View</a>
46
             {% if perms.recycle_core.assign_driver %}
47
             {% if perms.recycle_core.assign_driver %}
47
             <form method="post" action="{% url 'recycle_core:pickup_assign' p.id %}" class="flex items-center gap-2">
48
             <form method="post" action="{% url 'recycle_core:pickup_assign' p.id %}" class="flex items-center gap-2">
48
               {% csrf_token %}
49
               {% csrf_token %}
78
       {% endfor %}
79
       {% endfor %}
79
     </tbody>
80
     </tbody>
80
   </table>
81
   </table>
82
+  </div>
81
   {% include 'admin_frontend/_pagination.html' %}
83
   {% include 'admin_frontend/_pagination.html' %}
82
 </div>
84
 </div>
83
 {% endblock %}
85
 {% endblock %}

+ 22 - 1
recycle_core/views.py

7
 import django_filters as filters
7
 import django_filters as filters
8
 
8
 
9
 from admin_frontend.templatetags.public_urls import public_route
9
 from admin_frontend.templatetags.public_urls import public_route
10
+from django import forms
10
 from admin_frontend.nav import _nav_items
11
 from admin_frontend.nav import _nav_items
11
 from cms.views import breadcrumbs
12
 from cms.views import breadcrumbs
12
 from orgs.decorators import permissions_required
13
 from orgs.decorators import permissions_required
421
         site = filters.ModelChoiceFilter(queryset=CustomerSite.objects.all())
422
         site = filters.ModelChoiceFilter(queryset=CustomerSite.objects.all())
422
         assigned_driver = filters.CharFilter(field_name="assigned_driver__username", lookup_expr="icontains", label="Driver")
423
         assigned_driver = filters.CharFilter(field_name="assigned_driver__username", lookup_expr="icontains", label="Driver")
423
         status = filters.ChoiceFilter(field_name="status", choices=PickupOrder.STATUS_CHOICES)
424
         status = filters.ChoiceFilter(field_name="status", choices=PickupOrder.STATUS_CHOICES)
424
-        scheduled_at = filters.DateFromToRangeFilter(field_name="scheduled_at", label="Scheduled between")
425
+        scheduled_at = filters.DateTimeFromToRangeFilter(
426
+            field_name="scheduled_at",
427
+            label="Scheduled between",
428
+            widget=filters.widgets.RangeWidget(
429
+                attrs={
430
+                    "type": "datetime-local",
431
+                    "class": "border border-gray-300 rounded px-2 py-1"
432
+                }
433
+            ),
434
+        )
425
 
435
 
426
         class Meta:
436
         class Meta:
427
             model = PickupOrder
437
             model = PickupOrder
448
 
458
 
449
     # empty forms used in row actions
459
     # empty forms used in row actions
450
     assign_form = PickupAssignForm()
460
     assign_form = PickupAssignForm()
461
+    # Limit driver choices to users with driver role, scoped to org if present
462
+    drivers_qs = get_user_model().objects.filter(recycle_profile__role="driver")
463
+    if org is not None:
464
+        drivers_qs = drivers_qs.filter(recycle_profile__organization=org)
465
+    assign_form.fields["driver"].queryset = drivers_qs.order_by("username")
451
     status_form = PickupStatusForm()
466
     status_form = PickupStatusForm()
452
 
467
 
453
     context = {
468
     context = {
467
 def pickup_assign(request, pk: int):
482
 def pickup_assign(request, pk: int):
468
     pickup = get_object_or_404(PickupOrder, pk=pk)
483
     pickup = get_object_or_404(PickupOrder, pk=pk)
469
     form = PickupAssignForm(request.POST)
484
     form = PickupAssignForm(request.POST)
485
+    # Enforce driver role (and org, if present) on POST validation
486
+    org = getattr(request, "org", None)
487
+    drivers_qs = get_user_model().objects.filter(recycle_profile__role="driver")
488
+    if org is not None:
489
+        drivers_qs = drivers_qs.filter(recycle_profile__organization=org)
490
+    form.fields["driver"].queryset = drivers_qs
470
     if form.is_valid():
491
     if form.is_valid():
471
         pickup.assigned_driver = form.cleaned_data["driver"]
492
         pickup.assigned_driver = form.cleaned_data["driver"]
472
         pickup.status = PickupOrder.STATUS_SCHEDULED
493
         pickup.status = PickupOrder.STATUS_SCHEDULED

+ 27 - 0
translations/menu_labels.py

1
+from django.utils.translation import gettext_lazy as _
2
+
3
+# Mark app menu labels for translation extraction
4
+_("Dashboard")
5
+_("Profiles")
6
+_("Opportunities")
7
+_("Intro Requests")
8
+_("Leaderboard")
9
+_("Leads")
10
+_("CMS Posts")
11
+_("CMS Categories")
12
+_("Settings")
13
+_("Advanced Settings")
14
+_("Organization Settings")
15
+_("Organization Sites")
16
+_("Permissions")
17
+_("Users")
18
+_("Materials")
19
+_("Customers")
20
+_("Pickups")
21
+_("Weigh Tickets")
22
+_("Documents")
23
+_("Provided Services")
24
+_("Scrap Listings")
25
+_("Invoices")
26
+_("Payouts")
27
+