浏览代码

refactor(更新)
- 多语言选择和目标地区

周文健 2 月之前
父节点
当前提交
8625f7f4a0

+ 1 - 0
app/build.gradle.kts

@@ -59,6 +59,7 @@ android {
     }
     buildFeatures {
         dataBinding = true
+        buildConfig = true
     }
     packaging {
         resources {

+ 676 - 0
app/src/main/assets/i18n/en-US.csv

@@ -0,0 +1,676 @@
+key,type,comment,value
+i18n.language_name,text,语言自称,English
+abnormal,text,添加修改数据界面的状态的显示文本,异常
+account_login,text,登录界面的账号密码登录的文本,用户名登录
+action_confirm,text,提示弹窗的标题,操作确认
+action_confirm_content,text,点击步骤确认时的消息,确定要执行{0}吗?
+action_failed,text,提示弹窗的标题,操作失败
+action_hint,text,提示弹窗的标题,操作提醒
+action_succeed,text,提示弹窗的标题,操作成功
+add_card_failed,text,提示弹窗的内容,添加卡片失败
+add_card_succeed,text,提示弹窗的内容,添加卡片成功
+add_colocker,text,流程模式设置,步骤功能显示,添加共锁人({0})
+add_group,text,选择点位的时候的分组添加的按钮,添加分组
+add_key_failed,text,添加钥匙失败的弹窗的内容,新增钥匙失败
+add_key_succeed,text,添加钥匙成功的弹窗的内容,新增钥匙成功
+add_lock_failed,text,添加挂锁失败的弹窗的内容,添加挂锁失败
+add_lock_succeed,text,添加挂锁成功的弹窗的内容,添加挂锁成功
+add_point_failed,text,添加隔离点失败的弹窗的内容,新增隔离点失败
+add_point_succeed,text,添加隔离点成功的弹窗的内容,新增隔离点成功
+add_rfid_token_failed,text,添加RFID失败的弹窗的内容,添加RFID标签失败
+add_rfid_token_succeed,text,添加RFID成功的弹窗的内容,添加RFID标签成功
+add_role_failed,text,添加角色失败的弹窗的内容,新增角色失败
+add_role_succeed,text,添加角色成功的弹窗的内容,新增角色成功
+add_user_succeed,text,添加用户成功的弹窗的内容,新增用户成功
+add_workstation_failed,text,添加区域失败的弹窗的内容,新增区域失败
+add_workstation_succeed,text,添加区域成功的弹窗的内容,新增区域成功
+admin_role_can_not_edit,text,管理员信息修改的提示内容,管理员角色无法编辑
+admin_username,text,初始化超级管理员时的账号的标题,管理员账号:(数字、字母、6-20位)
+all,text,下拉框的全部文本,全部
+all_hardware_tv,text,首页的全部硬件文本,全部\n硬件
+all_job_tv,text,首页的全部作业文本,全部\n作业
+all_points_tv,text,首页的全部点位文本,全部\n点位
+all_quick_entrance,text,快捷入口的配置弹窗的备选快捷入口的文本,所有快捷入口
+all_select_not_all_select,text,角色添加/修改弹窗中权限菜单选矿的文本,全选/全不选
+already_colock,text,作业执行界面的共锁人员界面的标题文本,已共锁({0})
+already_uncolock,text,作业执行界面的共锁人员界面的标题文本,已解除共锁({0})
+back,text,通用返回文本,返回
+base_info_title,text,作业创建/修改,SOP创建/修改的基本信息标题文本,基本信息
+ble_connect_fail,text,蓝牙连接失败的加载弹窗提示文本,连接失败,请重试!
+ble_connecting,text,蓝牙连接中的加载弹窗的提示文本,连接中,请稍后...
+can_not_remove_current_colocker,text,选择共锁人时只有一个共锁人时移除触发的提示文本,无法移除当前共锁人
+can_not_remove_current_locker,text,选择上锁人时,非选择人员步骤不允许移除上锁人提示文本,无法移除当前上锁人
+cancel,text,通用取消文本,取消
+cancel_countdown,text,提示框的取消倒计时文本,取消({0}秒)
+cancel_exception,text,异常管理详情/异常作业详情界面的按钮文本,取消异常
+cancel_exception_failed,text,取消异常完成之后提示弹窗文本,取消异常失败
+cancel_exception_success,text,取消异常完成之后提示弹窗文本,异常取消成功
+cancel_job,text,作业执行界面的按钮文本,取消作业
+cancel_job_tip,text,取消作业的二次确认弹窗文本,是否确认取消当前作业
+capture_tip_content,text,人脸录入时的提示文本,"1. 系统将自动拍摄照片,在拍摄过程中请确保:
+     \n · 脸部正对摄像头
+     \n · 保持适当距离,让整个脸部出现在左侧框中
+     \n · 光线充足
+     \n · 表情自然
+\n2. 拍摄完成后,您可以点击确认按钮进行提交,也可以点击重拍按钮重新进行拍摄。
+\n3. 取消录入,请点击取消按钮"
+capture_tip_title,text,录入人脸时的标题文本,录入提示
+card_already_registration,text,卡片录入时已存在卡片保存是的错误文本,卡片已录入
+card_code,text,卡片管理表头文本,卡片名称
+card_login,text,登录界面的刷卡登录文本,刷卡登录
+card_manage_card_detail_title,text,卡片管理表头文本,卡片详情
+card_manage_delete_failed,text,卡片管理删除卡片失败时的提示弹窗文本,卡片删除失败
+card_manage_delete_succeed,text,卡片管理删除卡片成功时的提示弹窗文本,卡片删除成功
+card_manage_new_card_title,text,添加卡片弹窗的标题文本,新增卡片
+card_manage_title,text,卡片管理界面的标题文本,卡片管理
+card_nfc,text,卡片管理表头文本,卡片 NFC
+change_to_standard,text,设置远程服务地址时的切换版本按钮文本,切换标准版
+check_before_unlocking,text,八大步骤(玛氏)的步骤流程名称,检查使用,取锁前检查
+check_delete_card,text,删除卡片时的二次确认弹窗文本,确定要删除选中的卡片吗?
+check_delete_job,text,删除作业时的二次确认弹窗文本,您确认要删除作业吗
+check_delete_key,text,删除钥匙时的二次确认弹窗文本,您确定要删除选中的钥匙吗
+check_delete_lock,text,删除挂锁时的二次确认弹窗文本,确定要删除选中的挂锁吗?
+check_delete_point,text,删除隔离点时的二次确认弹窗文本,您确定要删除隔离点吗
+check_delete_rfid_token,text,删除RFID标签时的二次确认弹窗文本,确定要删除选中的RFID标签吗?
+check_delete_role,text,删除角色时的二次确认弹窗文本,您确认要删除角色吗
+check_delete_sop,text,删除SOP时的二次确认弹窗文本,您确定要删除选中的SOP吗
+check_delete_user,text,删除用户时的二次确认弹窗文本,您确认要删除用户吗?
+check_delete_workflow_mode,text,删除流程模式时的二次确认弹窗文本,确定要删除选中的流程模式吗
+check_delete_workstation,text,删除区域时的二次确认弹窗文本,"您确定要删除区域\""{0}\""吗"
+check_device_info,text,初始化界面检测硬件完成之后的检测信息结果文本,"检测到未注册钥匙{0}把,未注册挂锁{1}把"
+check_key_and_lock,text,初始化界面检测硬件时的文本,正在检查钥匙和挂锁
+check_lock_is_new_device,text,仓位管理检查挂锁的加载弹窗的文本,检查挂锁是否为新硬件
+check_new_key_need_register,text,仓位管理检测到新钥匙是否注册的提示弹窗的文本,检测到新钥匙,是否注册
+check_new_lock_need_register,text,仓位管理检测到新挂锁是否注册的提示弹窗的文本,检测到新挂锁,是否注册
+close,text,通用关闭文本,关闭
+colock,text,流程模式设置,步骤功能显示,共锁
+colock_complete,text,作业执行界面添加共锁完成弹窗的文本,添加共锁完成
+colock_failed,text,作业执行界面添加共锁失败弹窗的文本,添加共锁失败
+colocker,text,通用共锁人文本,共锁人
+confirm,text,通用确认文本,确定
+confirm_cancel_exception,text,取消异常时的二次确认弹窗文本,是否确认取消异常
+confirm_create_lock_job,text,解锁-上锁任务,完成解锁步骤之后,处理异常之后的提示弹窗文本,确认是否创建上锁作业
+confirm_create_unlock_job,text,上锁-解锁任务,完成上锁步骤之后,处理异常之后的提示弹窗文本,是否创建解锁作业
+confirm_handle_exception,text,处理异常时的二次确认弹窗文本,是否确认处理异常
+confirm_to_colock,text,共锁人添加共锁时的弹窗确认文本,{0}是否确认共锁?
+confirm_to_uncolock,text,共锁人解除共锁时的弹窗确认文本,{0}是否确认解除共锁?
+continue_the_ticket,text,作业票未完成归还钥匙之后,如果不强制上传数据的提示文本,请继续完成作业票
+create_job_failed,text,处理异常之后新建作业创建时创建失败的弹窗文本,创建作业失败
+create_job_name,text,创建作业/编辑作业的基本信息中的作业名称文本,作业名称
+create_job_title,text,新建作业的标题文本,新建作业
+create_sop_job_sop,text,新建SOP作业选择Sop文本,SOP
+create_sop_job_tip,text,保存并执行作业时的二次确认弹窗,"确定要执行作业\""{0}\""吗?"
+create_sop_job_title,text,新建SOP作业标题,新建SOP作业
+create_sop_name,text,新建SOP的sop名称文本,SOP名称
+create_sop_title,text,新建SOP的标题,新建SOP
+current_job_has_cross_job,text,处理异常时的交叉作业警告弹窗,警告!当前作业存在交叉作业,是否继续处理异常
+current_role_no_user,text,流程模式编辑确认方式角色确认时,如果选择的角色无用户时提示,当前角色暂无用户
+current_slot_has_no_key,text,仓位管理检查钥匙时如果仓内没有钥匙时弹窗提示,当前仓位不存在钥匙
+current_slot_has_no_lock,text,仓位管理检查挂锁时如果仓内没有挂锁时弹窗提示,当前仓位不存在挂锁
+current_sop_has_job_in_progress,text,删除sop作业时如果存在关联作业弹窗提示文本,当前SOP存在进行中的作业
+current_ticket_report_lock_take_exception_tip,text,挂锁取出时,如果作业异常提示文本,当前作业挂锁上报异常,请归还挂锁
+current_user_has_not_face_data,text,选择人员界面,如果头像为空时点击提示,当前用户不存在人脸数据
+current_workflow_mode_error,text,进行流程模式设置界面,如果流程模式数据获取异常时提示,当前流程模式错误
+currently_no_hardware_can_be_report,text,异常上报,类型为硬件异常但是没有硬件数据时提示,当前没有硬件可以上报
+currently_no_job_can_be_report,text,异常上报,类型为作业异常但是没有作业数据时提示,当前没有作业可以上报
+currently_unable_to_lock_together,text,作业执行界面,如果步骤未在共锁步骤,刷卡提示,当前阶段无法共锁
+data_content_error,text,导入流程模式时如果解密之后的内容转换异常时提示,数据内容错误
+data_decrypt_failed,text,导入流程模式时如果数据解密失败时提示,数据解密失败
+data_file_is_corrupted,text,导入流程模式时如果数据文件的校验码错误时提示,数据文件已损坏
+data_file_not_exists,text,导入流程模式时如果数据文件不存在时提示,数据文件不存在
+date,text,时间范围选择弹窗格式,{0}年{1}月{2}日
+delete,text,通用删除文本,删除
+delete_group,text,选择点位的时候的分组删除的按钮,删除分组
+delete_success,text,流程模式和指纹删除成功的弹窗文本,删除成功
+detail,text,通用表头文本,详情
+detect_face_tip,text,人脸录入时检测到人脸的提示文本,检测到人脸,即将拍摄
+detect_port,text,仓位管理进入的加载文本,正在扫描设备......
+detect_slot,text,仓位管理的检测按钮文本,检测仓位
+device_in_detect,text,初始化硬件的时候的识别中提示文本,设备识别中
+do_you_want_to_remove_exception,text,仓位管理长按移除异常的二次确认弹窗文本,是否确认移除该异常
+doing_checking,text,步骤确认验证人脸时的加载提示文本,正在验证......
+doing_login,text,登录时的加载提示文本,正在登录······
+done_header,text,我的待办的tab文本,已处理
+edit,text,通用编辑文本,编辑
+edit_job_title,text,编辑作业的标题文本,作业详情
+edit_sop_job_title,text,编辑SOP作业的标题文本,SOP作业详情
+edit_sop_title,text,编辑SOP的标题文本,SOP详情
+end,text,选择时间范围的结束时间的文本,结束
+end_job,text,作业执行界面的按钮文本,结束作业
+end_time,text,首页的时间范围的文本,结束时间
+ensure_power_isolation,text,八大步骤(玛氏)的步骤流程名称,检查使用,能量隔离证实
+error_date_range_invalid,text,时间范围选择时的检查提示文本,开始时间不能晚于结束时间
+exception_data_not_exists,text,处理异常时,异常数据不存在的提示弹窗文本,异常数据不存在
+exception_description,text,异常管理详情的表头,异常描述:
+exception_description_tv,text,异常上报的内容标题,异常描述
+exception_detail_title,text,异常管理详情的标题,异常详情
+exception_info,text,异常管理详情的的表头,异常信息
+exception_job,text,异常作业的详情的表头,异常作业:
+exception_job_title,text,异常作业的标题,异常作业
+exception_lost,text,异常作业的数据不存在的情况下提示,异常丢失
+exception_manage_title,text,异常管理的标题,异常管理
+exception_occurrence_time,text,异常管理详情的表头,异常发生时间:
+exception_occurrence_time_header,text,异常管理的表头,发生时间
+exception_reason,text,仓位管理异常上报的弹窗信息,异常信息
+exception_release_time,text,异常管理详情的表头,异常解除时间:
+exception_report,text,仓位异常上报和异常上报的标题,异常上报
+exception_report_success,text,异常上报成功之后的弹窗提示,异常上报成功
+exception_reporter,text,异常管理详情的表头,上报人:
+exception_source,text,异常管理详情的表头,异常源:
+exception_source_tv,text,异常上报的内容标题,异常源
+exception_status,text,异常管理详情的表头,状态:
+exception_status_header,text,异常管理的表头,异常状态
+exception_type,text,异常管理详情的表头,异常类型:
+exception_type_header,text,异常管理列表的表头,异常类型
+exception_type_tv,text,异常上报的内容标题,异常类型
+expand_collapse,text,添加/修改角色的权限树的操作文本,展开/折叠
+face_can_not_process,text,虹软初始化失败时的提示文本,人脸引擎激活失败,识别暂不可用
+face_detected_do_login,text,人脸登录的加载窗提示文本,检测到人脸,正在登录······
+face_login,text,登录界面的人脸登录文本,人脸登录
+face_login_failed,text,人脸登录失败时的提示文本,人脸匹配失败,请重试
+face_login_success,text,人脸登录成功时的提示文本,人脸验证通过
+face_not_set_tip,text,用户信息的人脸设置界面未设置人脸的显示文本,您尚未设置人脸数据
+face_set_tip,text,用户信息的人脸设置界面已设置人脸的显示文本,您已设置了人脸数据
+file_not_exists,text,流程模式导入时如果指定文件不存在时提示,文件不存在
+filter,text,通用筛选文本,筛选
+fingerprint_add_success_tip,text,指纹添加成功时的弹窗文本,已成功添加指纹数据
+fingerprint_code,text,指纹列表的表头,指纹编号
+fingerprint_code_str,text,指纹列表的数据前缀,指纹_{0}
+fingerprint_delete_confirm_tip,text,指纹列表指纹项删除指纹的二次确认弹窗文本,确定要删除{0}吗?
+fingerprint_delete_selected_confirm_tip,text,指纹列表删除指纹的二次确认弹窗文本,确定要删除选中的指纹吗?
+fingerprint_login,text,登录界面的指纹登录文本,指纹登录
+fingerprint_login_failed,text,登录界面的指纹登录失败提示文本 ,指纹识别失败,请重试
+fingerprint_login_success,text,登录界面的指纹登录成功提示文本,指纹验证通过
+fingerprint_scan_tip,text,录入指纹时的提示录入提示文本,请连续按压{0}次指纹识别区
+finish_job_tip,text,作业执行界面结束作业的二次确认弹窗文本,是否确认结束当前作业
+finish_the_job,text,作业执行界面的结束作业按钮文本,结束作业
+get_key_info_fail,text,钥匙数据获取失败时的提示文本,获取钥匙信息失败
+go_locking,text,作业执行界面的去上锁按钮文本,去上锁
+go_unlocking,text,作业执行界面的去解锁按钮文本,去解锁
+group_at_least_has_one_point,text,选择点位确认按钮检查分组点位的错误提示文本,每个分组至少需要存在一个点位
+group_job_in_progress,text,分组已经获取钥匙未归还的情况再次点击的提示文本,分组作业进行中
+group_name_must_not_empty,text,选择点位分组名称为空时保存的错误提示文本,分组名称不能为空
+handle,text,我的待办的处理按钮文本,处理
+handle_colock,text,我的待办处理共锁时的二次确认弹窗文本,请确认是否要进行添加共锁
+handle_exception,text,异常详情和异常作业的处理异常的按钮文本,处理异常
+handle_exception_success,text,异常处理成功的弹窗文本,异常处理成功
+handle_exception_will_release_all_colock,text,异常作业的已经共锁的作业结束时的警告弹窗文本,警告!处理异常将移除所有共锁,请确认是否继续
+handle_failed,text,异常处理失败的弹窗提示文本,处理失败
+handle_lock_take_key,text,我的待办上锁取钥匙时的二次确认弹窗文本,确认获取钥匙进行上锁吗?
+handle_release_colock,text,我的待办的解除共锁时的二次确认弹窗文本,请确认是否要进行解除共锁
+handle_step_confirm,text,我的待办的步骤确认时的二次确认弹窗文本,请确认是否完成[{0}]
+handle_time,text,异常管理详情的表头,处理时间:
+handle_time_custom_time_range,text,我的待办的已处理的时间筛选的选择项文本,自定义区间
+handle_time_last_30_days,text,我的待办的已处理的时间筛选的选择项文本,近30天
+handle_time_last_7_days,text,我的待办的已处理的时间筛选的选择项文本,近7天
+handle_unknown,text,我的待办的处理项类型异常的提示文本,当前处理类型未知,无法处理
+handle_unlock_take_key,text,我的待办解锁取钥匙时的二次确认弹窗文本,确认获取钥匙进行解锁吗?
+hardware_in_use_tv,text,首页使用中的硬件文本,使用中\n的硬件
+hardware_info,text,仓位异常上报的信息展示,硬件信息: {0}
+hardware_key,text,仓位异常上报的硬件类型文本,钥匙
+hardware_lock,text,仓位异常上报的硬件类型文本,挂锁
+hardware_unknown,text,仓位异常上报的硬件类型文本,未知
+has_job_in_progress,text,作业管理删除作业时有作业存在时的弹窗文本,存在正在进行中的作业
+has_locked,text,作业执行界面的点位上锁状态文本,已上锁
+has_user_in_progress_job,text,删除用户时如果关联用户的作业有进行中的时候弹窗文本,有用户在进行的作业中
+home_overview_data_title,text,首页总览数据标题,总览数据
+home_realtime_data_title,text,首页试试数据标题,实时数据
+import_str,text,通用导入文本,导入
+import_success,text,通用导入成功文本,导入成功
+in_progress_job_manage_title,text,进行中的作业的标题,进行中的作业
+init_card_registration_step_hint,text,初始化卡片注册时的提示文本,请在读卡器上刷卡
+init_card_registration_step_tip,text,初始化卡片注册时的标题文本,识别并录入卡片
+init_device_registration_key_and_lock_complete_step_hint,text,初始化硬件的时候的扫描完成的提示文本,扫描完成
+init_device_registration_key_and_lock_step_hint,text,初始化硬件的时候的扫描中的提示文本,请等待系统识别钥匙和挂锁
+init_device_registration_key_and_lock_step_tip,text,初始化硬件的时候的标题文本,识别钥匙和挂锁
+init_point_rfid_registration_step_hint,text,初始化点位注册的时候的提示文本,请在读卡器上刷点位RFID标签
+init_point_rfid_registration_step_tip,text,初始化点位注册的时候的标题文本,识别并录入点位RFID标签
+init_set_admin_account_step,text,初始化设置管理员账号时的步骤序号,1
+init_set_admin_account_step_hint,text,初始化设置管理员账号时的提示文本,请设置管理员账号密码
+init_set_admin_account_step_tip,text,初始化设置管理员账号时的标题文本,设置管理员账号
+init_set_remote_server_step_hint,text,设置远程服务器地址的提示文本,请设置服务器的地址和端口
+init_set_remote_server_step_tip,text,设置远程服务器地市的标题文本,配置服务器
+insert,text,通用新增文本,新增
+invalid_card,text,作业执行界面和我的待办界面的共锁人刷卡时卡片无效的提示文本,卡片无效
+invalid_user,text,作业执行界面和我的待办界面的共锁人刷卡时用户不存在的提示文本,用户不存在
+item_my_todo_complete_time_title,text,我的待办信息的头文本,完成时间:
+item_my_todo_current_operation_title,text,我的待办信息的头文本,当前操作:
+item_my_todo_current_step_title,text,我的待办信息的头文本,当前步骤:
+item_my_todo_job_name_title,text,我的待办信息的头文本,相关作业:
+job_already_finished,text,作业执行界面,收到作业被结束的消息的提示文本,该作业已被结束
+job_canceled,text,作业管理已取消的作业点击提示文本,作业已取消
+job_card_login_failed,text,工卡登录失败的提示文本,工卡无效
+job_card_login_success,text,工卡登录成功的提示文本,工卡识别成功
+job_card_not_set_tip,text,设置工卡界面,还没有设置工卡时的文本信息,您尚未设置工卡
+job_card_scan_tip,text,设置工卡界面,设置工卡的提示文本,请在读卡器上读卡
+job_card_set_tip,text,设置工卡界面,已经设置工卡时的文本信息,您已设置了工卡数据
+job_create_and_execute_failed,text,保存并执行作业失败时的弹窗提示文本,作业执行失败
+job_create_and_execute_succeed,text,保存并执行作业成功时的弹窗提示文本,作业开始执行
+job_create_failed,text,保存作业失败时的弹窗提示文本,作业保存失败
+job_create_succeed,text,保存作业成功时的弹窗提示文本,作业保存成功
+job_execute_colocker_colock_status_title,text,作业执行界面和异常作业界面的共锁人标题,共锁人员共锁状态
+job_execute_lock_status_title,text,作业执行界面和异常作业界面的点位标题,隔离点锁定状态
+job_execute_step_description,text,作业执行界面和异常作业界面的操作说明标题,操作说明({0})
+job_execute_tab_title_colock,text,作业执行界面和异常作业界面的共锁人TAB标题,共锁
+job_execute_tab_title_lock,text,作业执行界面和异常作业界面的点位TAB标题,锁定
+job_execute_title,text,作业执行界面标题,作业执行
+job_finished,text,作业管理的已结束作业点击提示文本,作业已结束
+job_lost,text,作业执行界面的作业数据不存在的文本,作业丢失
+job_manage_delete_failed,text,作业管理删除作业失败时的弹窗提示文本,无法删除选中的作业
+job_manage_delete_succeed,text,作业管理删除作业成功时的弹窗提示文本,删除选中的作业成功
+job_manage_title,text,作业管理标题,作业管理
+job_name,text,作业观猎列表表头,作业名称
+job_save_and_execute_tip,text,保存并执行作业时二次确认弹窗文本,"确定要执行作业\""{0}\""吗?"
+job_save_tip,text,保存作业二次确认弹窗文本,"确定要保存作业\""{0}\""吗?"
+job_status,text,锁定中的点位表头,作业状态
+job_workstation,text,创建/修改作业的基本信息的文本,作业区域
+key_exception_tag,text,还钥匙的时候如果钥匙异常提示,该钥匙已被标记异常
+key_in_use,text,钥匙管理列表删除钥匙时如果钥匙使用中提示,钥匙正在使用
+key_info_already_exists,text,仓位管理检查钥匙仓位注册时如果钥匙信息已存在提示,钥匙信息已存在
+key_is_in_failure_mode,text,钥匙状态查询为故障的时候输出到日志,钥匙处于故障模式
+key_mac,text,添加钥匙弹窗的信息文本,钥匙MAC
+key_manage_delete_failed,text,钥匙管理删除失败时弹窗文本,钥匙删除失败
+key_manage_delete_succeed,text,钥匙管理删除成功时弹窗文本,钥匙删除成功
+key_manage_key_detail_title,text,钥匙详情标题,钥匙详情
+key_manage_new_key_title,text,新增钥匙标题,新增钥匙
+key_manage_title,text,钥匙管理标题,钥匙管理
+key_name,text,钥匙管理表头,钥匙名称
+key_nfc,text,钥匙管理表头,钥匙NFC
+key_not_exists,text,切换工作模式之后,没有找到钥匙信息时提示,钥匙不存在
+key_return_success,text,钥匙归还之后信息上报成功之后提示,钥匙归还成功
+key_return_tip,text,上报点位信息异常时提示文本,作业票尚未完成,禁止归还钥匙
+key_take_error_tip,text,获取挂锁之后,获取钥匙信息失败时提示,钥匙分配失败,请检查硬件状态
+loading_data,text,通用数据加载弹窗文本,数据加载中
+loading_device,text,仓位管理加载中文本,正在加载硬件......
+loading_msg_get_ticket_status_start,text,开始读取作业票加载中文本,正在读取钥匙作业票
+loading_msg_return_key_start,text,开始连接钥匙加载中文本,开始连接钥匙,请稍候······
+lock,text,流程模式设置,步骤功能显示,上锁
+lock_already_exists,text,仓位管理检查挂锁和挂锁列表添加挂锁信息存在时提示,挂锁信息已存在
+lock_code,text,挂锁列表表头,挂锁编号
+lock_exception_tag,text,还挂锁时挂锁异常提示,该挂锁已被标记异常
+lock_in_use,text,挂锁管理删除挂锁时,如果挂锁使用中提示,挂锁正在使用中
+lock_is_not_enough,text,去上锁是,挂锁数量不足时提示,锁具数量不足
+lock_key_return_tip,text,还钥匙时,如果作业票未完成提示是否强制上传,作业票尚未完成,是否强制上传数据
+lock_manage_delete_failed,text,挂锁删除失败时提示,挂锁删除失败
+lock_manage_delete_succeed,text,挂锁删除成功时提示,挂锁删除成功
+lock_manage_lock_detail_title,text,挂锁详情标题,挂锁详情
+lock_manage_new_lock_title,text,新增挂锁标题,新增挂锁
+lock_manage_title,text,挂锁管理标题,挂锁管理
+lock_name,text,挂锁管理表头,挂锁名称
+lock_nfc,text,挂锁管理表头,挂锁 NFC
+lock_status,text,作业执行界面挂锁信息表头,上锁状态
+lock_take_report_fail,text,挂锁取出上报异常时提示信息,挂锁取出上报失败
+locked_points_title,text,锁定中的点位标题,锁定中的点位
+locked_points_tv,text,首页锁定中的点位文本,锁定中\n的点位
+locker,text,通用上锁人文本,上锁人
+login,text,通用登录文本,登录
+login_tip,text,登录界面提示文本,请输入用户名和密码或者刷卡进行登录
+loto,text,系统主标题,Intelligent Lock Control System
+loto_en,text,系统副标题,Intelligent Lock Control System
+manage_filter_status,text,添加/修改弹窗状态文本,状态
+manage_role_function_permission,text,角色管理功能权限文本,功能权限
+member_info_title,text,人员信息标题,人员信息
+move_down,text,区域管理下移按钮文本,下移
+move_up,text,区域管理上移按钮文本,上移
+my_todo_title,text,我的待办标题,我的待办
+navigate_to_step,text,流程模式设置,步骤功能显示,跳转到第{0}步
+new_device,text,新设备标题文本,New
+new_group,text,选择点位,默认新增分组名称前缀,新分组{0}
+new_password,text,重置密码界面新密码文本,新密码(数字、字母、特殊符号、6-20位)
+new_password_and_repeat_new_password_not_same,text,重置密码界面重复密码校验提示文本,新密码与重复新密码不一致
+new_password_cannot_be_the_same_as_the_old_password,text,重置密码界面新旧密码校验提示文本,新密码与旧密码不能相同
+next,text,初始化界面下一步按钮文本,下一步
+nickname,text,通用姓名文本,姓名
+no_available_key,text,作业执行界面去上锁没有钥匙时弹窗提示文本,暂无可用钥匙
+no_data,text,列表界面没有数据时默认展示信息文本,暂无数据
+no_goto_step,text,流程模式设置,步骤功能显示,无跳转
+no_permission_to_handle,text,作业执行界面,进行操作时权限不通过提示文本,您暂无权限操作当前作业票
+no_response_board_exists,text,硬件通信时响应超时提示文本,存在未响应的主板
+normal,text,通用正常文本,正常
+not_group_can_lock,text,所有分组上锁完成之后点击去上锁按钮提示,当前无分组可上锁
+not_group_can_unlock,text,所有分组解锁完成之后点击去解锁按钮提示,当前无分组可解锁
+not_in_slot,text,仓位管理长按仓位检查硬件不存在时提示,未在仓位
+not_save_tip,text,创建/修改作业,SOP,SOP作业返回时通用确认弹窗文本,数据还没有保存,您确定要放弃保存,离开当前页面吗?
+number,text,仓位异常上报信息文本,编号: 
+old_password,text,重置密码旧密码文本,旧密码
+old_password_error,text,重置密码旧密码校验失败提示以文本,旧密码错误
+one_key_cancel,text,异常管理列表按钮文本,一键取消
+one_key_handle,text,异常管理列表按钮文本,一键处理
+ongoing_job_tv,text,首页进行中的作业文本,进行中\n的作业
+only_one_person_allowed,text,人脸录入时提示文本,请保持单人入镜
+operation,text,列表表头,操作
+password_and_repeat_password_not_same,text,重置密码和初始化设置管理员账号时密码与重复密码不一致时提示文本,密码与重复密码不一致
+password_regex_tip,text,重置密码和初始化设置管理员账号时密码不符合规范提示文本,密码不符合要求
+phone,text,通用电话文本,电话
+please_do_colock,text,作业执行界面提示文本,请共锁人完成共锁
+please_do_uncolock,text,作业执行界面提示文本,请共锁人解除共锁
+please_done_operation,text,作业执行界面点击未完成的非当前步骤时提示,请先完成{0}
+please_go_locking,text,作业执行界面提示文本,请上锁员执行去上锁操作
+please_go_unlocking,text,作业执行界面提示文本,请上锁员执行去解锁操作
+please_input_account,text,输入框提示文本和数据校验错误提示文本,请输入用户名
+please_input_admin_username,text,输入框提示文本和数据校验错误提示文本,请输入管理员账号
+please_input_area,text,输入框提示文本和数据校验错误提示文本,请输入区域
+please_input_card_code,text,输入框提示文本和数据校验错误提示文本,请输入工卡
+please_input_card_nfc,text,输入框提示文本和数据校验错误提示文本,请输入卡片 NFC
+please_input_correct_phone,text,输入框提示文本和数据校验错误提示文本,请输入正确的手机号
+please_input_exception_reason,text,输入框提示文本和数据校验错误提示文本,请输入异常原因
+please_input_job_name,text,输入框提示文本和数据校验错误提示文本,请输入作业名称
+please_input_key_mac,text,输入框提示文本和数据校验错误提示文本,请输入钥匙MAC
+please_input_key_name,text,输入框提示文本和数据校验错误提示文本,请输入钥匙名称
+please_input_key_nfc,text,输入框提示文本和数据校验错误提示文本,请输入钥匙NFC
+please_input_key_word,text,输入框提示文本和数据校验错误提示文本,请输入关键字
+please_input_lock_code,text,输入框提示文本和数据校验错误提示文本,请输入挂锁编号
+please_input_lock_nfc,text,输入框提示文本和数据校验错误提示文本,请输入挂锁 NFC
+please_input_new_password,text,输入框提示文本和数据校验错误提示文本,请输入新密码
+please_input_nickname,text,输入框提示文本和数据校验错误提示文本,请输入姓名
+please_input_old_password,text,输入框提示文本和数据校验错误提示文本,请输入旧密码
+please_input_password,text,输入框提示文本和数据校验错误提示文本,请输入密码
+please_input_permission_characters,text,输入框提示文本和数据校验错误提示文本,请输入权限字符
+please_input_phone,text,输入框提示文本和数据校验错误提示文本,请输入电话
+please_input_point_function,text,输入框提示文本和数据校验错误提示文本,请输入隔离点作用
+please_input_point_name,text,输入框提示文本和数据校验错误提示文本,请输入隔离点名称
+please_input_remark,text,输入框提示文本和数据校验错误提示文本,请输入备注
+please_input_remote_server_address,text,输入框提示文本和数据校验错误提示文本,请输入服务地址
+please_input_repeat_new_password,text,输入框提示文本和数据校验错误提示文本,请重复新密码
+please_input_repeat_password,text,输入框提示文本和数据校验错误提示文本,请输入重复密码
+please_input_rfid,text,输入框提示文本和数据校验错误提示文本,请输入 RFID 标签
+please_input_rfid_code,text,输入框提示文本和数据校验错误提示文本,请输入 RFID 编号
+please_input_rfid_tag,text,输入框提示文本和数据校验错误提示文本,请输入RFID标签
+please_input_role_name,text,输入框提示文本和数据校验错误提示文本,请输入角色名称
+please_input_sop_name,text,输入框提示文本和数据校验错误提示文本,请输入SOP名称
+please_input_step_description,text,输入框提示文本和数据校验错误提示文本,请输入步骤操作说明
+please_input_step_title,text,输入框提示文本和数据校验错误提示文本,请填写步骤标题
+please_input_step_title_short,text,输入框提示文本和数据校验错误提示文本,请输入步骤标题缩写
+please_input_username,text,输入框提示文本和数据校验错误提示文本,请输入登录名
+please_input_workstation_name,text,输入框提示文本和数据校验错误提示文本,请输入区域名称
+please_must_select_at_least_one_point,text,创建作业,SOP,SOP作业保存时如果分组点位不足时提示,您至少需要添加一个点位
+please_press_fingerprint_again,text,设置指纹时提示,请再次按压指纹
+please_re_press_fingerprint_again,text,设置指纹时如果3次全错误时提示,请重新按压指纹
+please_return_key_after_locking,text,作业执行界面提示文本,请上锁员完成上锁后,归还钥匙
+please_return_key_after_unlocking,text,作业执行界面提示文本,请上锁员完成解锁后,归还钥匙
+please_scan_face,text,登录弹窗提示文本,请刷脸
+please_scan_fingerprint,text,登录弹窗提示文本,请刷指纹
+please_select_area,text,删除区域时未选择区域的提示文本,请选择区域
+please_select_card,text,删除卡片时未选择卡片的提示文本,请选择卡片
+please_select_card_username,text,保存卡片时未选择用户的提示文本,请选择用户名称
+please_select_colocker,text,选择人员界面未选择共锁人时保存提示文本,请选择共锁人
+please_select_exception_description,text,异常上报检查数据时提示文本,请选择异常描述
+please_select_exception_source,text,异常上报检查数据时提示文本,请选择异常源
+please_select_exception_type,text,异常上报检查数据时提示文本,请选择异常类型
+please_select_flow_mode,text,选择提示文本和数据校验错误提示文本,请选择流程模式
+please_select_group,text,去上锁选择标题,请选择分组
+please_select_handle_time,text,我的待办已处理时间选择提示文本,请选择处理时间
+please_select_job,text,作业管理删除作业未选择提示文本,请选择作业
+please_select_job_workstation,text,选择提示文本和数据校验错误提示文本,请选择作业区域
+please_select_key,text,钥匙管理删除钥匙未选择提示文本,请选择钥匙
+please_select_lock,text,挂锁管理删除挂锁未选择提示文本,请选择挂锁
+please_select_locker,text,选择人员确认数据时未选择上锁人提示,请选择上锁人
+please_select_member,text,作业执行界面提示文本,您可以选择添加人员
+please_select_point,text,选择点位确认数据时未选择点位提示,请选择隔离点
+please_select_power_type,text,添加点位时能量源提示文本和未选择提示文本,请选择能量源
+please_select_process_application,text,异常上报时处理申请提示文本和未选择提示文本,请选择处理申请
+please_select_rfid_token,text,添加更新点位数据时rfid未选择提示和提示文本,请选择RFID标签
+please_select_role,text,添加修改用户时未选择角色文本和选择提示文本,请选择角色
+please_select_sop,text,创建修改SOP作业时的提示文本和未选择提示文本,请选择SOP
+please_select_sop_workstation,text,创建修改SOP作业时的提示文本和未选择提示文本,请选择SOP区域
+please_select_start_time,text,首页点击结束时间未选择开始时间时提示文本,请先选择开始时间
+please_select_status,text,添加用户时未选择状态时提示,请选择状态
+please_select_step_confirm_member,text,流程模式设置,确认数据时提示,请选择执行确认人员
+please_select_step_confirm_role,text,流程模式设置,确认数据时提示,请选择执行确认角色
+please_select_step_confirm_type,text,流程模式设置,确认数据时提示,请选择执行确认方式
+please_select_user,text,用户管理删除用户未选择时提示,请选择用户
+please_select_workflow_mode,text,创建作业,SOP时未选择流程模式提示,请选择流程模式
+please_select_workstation,text,创建作业,SOP时未选择区域提示,请选择区域
+please_swipe_card,text,登录弹窗提示文本,请刷卡
+please_take_out_ready_device_first,text,同一个作业去上锁还未取出设备,再次上锁时提示,请先取出已开卡扣的设备
+please_wait_ticket_name_lock_complete,text,交叉点位存在上锁时,有任务要去解锁时提示,请等待[{0}]上锁完成
+point_detail,text,我的待办点位明细按钮文本,点位明细
+point_in_use,text,点位删除时检查,点位使用中提示,点位正在使用无法修改
+point_info_title,text,点位信息标题文本,点位信息
+point_list_title,text,点位选择界面标题,点位清单
+point_manage_add_title,text,点位管理添加点位标题,添加点位
+point_manage_delete_failed,text,点位管理删除失败时提示,无法删除隔离点
+point_manage_delete_succeed,text,点位管理删除成功时提示,隔离点删除成功
+point_manage_point_function,text,点位管理隔离点作用表头,隔离点作用
+point_manage_point_group,text,作业执行界面点位表头,分组名称
+point_manage_point_name,text,添加更新筛选点位显示文本,隔离点名称
+point_manage_point_power_type,text,添加更新筛选点位显示文本,能量源
+point_manage_rfid,text,添加更新筛选点位显示文本,RFID
+point_manage_rfid_tag,text,添加更新筛选点位显示文本,RFID 标签
+point_manage_title,text,点位管理标题,点位管理
+point_manage_update_title,text,修改点位标题,修改点位
+point_manage_workstation,text,添加更新筛选点位显示文本,区域
+point_name_tv,text,添加更新筛选点位显示文本,隔离点
+power_isolation_way,text,添加更新筛选点位显示文本,确认隔离方式
+preset_workflow_can_not_delete,text,删除流程模式时如果流程模式时预设弹窗提示文本,预设流程模式无法删除
+previous,text,初始化界面上一步按钮我文本,上一步
+process_application_tv,text,异常上报显示文本,处理申请
+quick_entrance_most_set_tip,text,快捷入口配置提示文本,快捷入口最多设置8个
+quick_entrance_title,text,快捷入口配置弹窗标题,快捷入口配置
+re_recognize,text,捕捉人脸按钮文本,重新识别
+real_person_verification_required,text,捕捉人脸提示文本,请保持真人操作
+recapture,text,捕捉人脸按钮文本,重拍
+recognize_work_content,text,八大步骤(玛氏)的步骤流程名称,检查使用,识别工作内容
+recognized_card_rfid,text,初始化注册卡片表头,已识别的卡片RFID
+recognized_point_rfid,text,初始化注册RFID表头,已识别的点位RFID
+reduce_colocker,text,流程模式设置,步骤功能显示,减少共锁人({0})
+register_failed,text,仓位管理注册硬件失败时弹窗文本,注册失败
+register_success,text,仓位管理注册硬件成功时弹窗文本,注册成功
+release_colocker,text,流程模式设置,步骤标题,解除共锁
+remark,text,通用备注文本,备注
+repeat_new_password,text,重置密码界面,重新密码显示文本,重复新密码(数字、字母、特殊符号、6-20位)
+repeat_password,text,初始化管理员界面,重复密码显示文本,重复密码:(数字、字母、特殊符号、6-20位)
+reset,text,通用重置文本,重置
+reset_data_tv,text,设置人脸和设置工卡,重新设置按钮文本,点击重设
+reset_password_title,text,重置密码标题,重置密码
+reset_user_password_failed,text,重置密码失败弹窗提示文本,用户密码重置失败
+reset_user_password_succeed,text,重置密码成功弹窗提示文本,用户密码重置成功
+restart_app_after_set,text,设置远程地址完成之后自动重启提示,App将在设置完成后重启
+rfid,text,添加RFID显示文本,RFID 标签
+rfid_already_bind,text,添加点位时如果RFID已被绑定是弹窗显示,该Rfid标签已被绑定
+rfid_already_registration,text,初始化注册RFID,添加修改RFID时如果RFID已存在提示文本,RFID标签已录入
+rfid_code,text,RFID列表表头,RFID 编号
+rfid_in_use,text,修改RFID时如果RFID使用中无法进行修改提示,RFID标签使用中,无法修改
+rfid_name,text,RFID列表表头,RFID编号
+rfid_token_manage_delete_failed,text,删除RFID失败时提示,RFID标签删除失败
+rfid_token_manage_delete_succeed,text,删除RFID成功时提示,RFID标签删除成功
+rfid_token_manage_new_rfid_token_title,text,新增RFID标题,新增 RFID 标签
+rfid_token_manage_rfid_token_detail_title,text,修改RFID标题,RFID标签详情
+rfid_token_manage_title,text,RFID管理标题,RFID管理
+role_in_preset_tip,text,删除预设角色时检查是否是预设角色,是的话无法删除提示,预设角色不允许删除
+role_in_use,text,删除角色时检查角色是否使用中,是的话提示,角色已有作业在使用
+role_key_already_exists,text,添加角色时重复角色权限字符检测提示,该角色权限字符已存在
+role_manage_add_title,text,添加角色标题,添加角色
+role_manage_delete_failed,text,删除角色失败时提示,无法删除角色
+role_manage_delete_succeed,text,角色删除成功时提示,角色已删除
+role_manage_permission_string,text,权限字符显示文本,权限字符
+role_manage_role_name,text,角色名称显示文本,角色名称
+role_manage_role_num,text,角色编号显示文本,角色编号
+role_manage_title,text,角色管理标题,角色管理
+save,text,通用保存文本,保存
+save_and_execute,text,通用保存并执行文本,保存并执行
+save_sop_check,text,生成SOP选择框文本,生成SOP
+save_success,text,保存成功通用显示文本,保存成功!
+scan_complete_app_restarting,text,自动扫描串口完成之后自动重启提示,扫描完成,APP将自动重启
+select,text,通用选择文本,选择
+select_colocker_tip,text,选择人员界面选择共锁人提示文本,请在以下人员中选择共锁人
+select_coloker,text,选择人员界面未选择共锁人确认时提示文本,请选择共锁人
+select_group_tip,text,选择点位界面的分组选择操作提示文本,点击分组空白区域进行选中
+select_locker,text,选择人员界面未选择上锁人确认时提示文本,选择上锁人
+select_locker_tip,text,选择人员界面选择上锁人提示文本,请在以下人员中选择[{0}]上锁人
+select_member_title,text,选择人员标题,选择人员
+select_point_title,text,选择点位标题,选择点位
+selected_point_already_in_use,text,删除点位时,检查到存在使用中的点位是提示,存在使用中的点位
+selected_point_info_title,text,默认分组名称,已选择的点位信息
+selected_quick_entrance,text,快捷入口弹窗界面,已选择的标题提示文本,已配置的快捷入口(最多添加8个快捷入口,可拖拽排序)
+selected_rfid_in_use,text,删除RFID时检查到存在使用中的RFID时提示,存在正在使用的RFID标签
+send_ticket_fail,text,作业票下发失败时提示文本,作业票下发失败
+sending_ticket,text,作业票下发中加载弹窗文本,工作票下发中······
+server_address,text,设置服务器地址显示文本,服务地址
+server_address_error,text,设置服务器地址,服务器地址错误提示文本,服务器地址错误
+set_colocker,text,流程模式设置,功能菜单显示,设置共锁人
+set_data_tv,text,设置人脸和设置工卡,未设置时按钮文本,点击设置
+set_face_title,text,设置人脸标题,设置人脸
+set_fingerprint_title,text,设置指纹标题,设置指纹
+set_job_card_title,text,设置工卡标题,设置工卡
+set_locker,text,流程模式设置,功能菜单显示,设置上锁人
+set_password,text,初始化设置管理员,设置密码文本显示,设置密码:(数字、字母、特殊符号、6-20位)
+settings,text,通用设置文本,设置
+show_member_when_selected_sop,text,创建SOP作业人员信息展示区域提示,选择SOP后,将自动展示SOP的人员信息。
+show_points_when_selected_sop,text,创建SOP作业点位信息展示区域提示,选择SOP后,将自动展示SOP的点位信息。
+shutdown,text,八大步骤(玛氏)的步骤流程名称,检查使用,操作停机
+ski_step,text,初始化工卡,按钮文本,跳过该步骤
+skip_and_complete,text,初始化RFId,按钮文本,跳过并完成
+slot_exception_tag,text,归还钥匙时检查锁仓异常时提示,该锁仓已被标记异常
+slots_exception_report,text,锁仓异常上报标题,仓位异常上报
+slots_manage_title,text,仓位管理标题,仓位管理
+sop_create_failed,text,SOP创建失败时弹窗文本,SOP创建失败
+sop_create_succeed,text,SOP创建成功时弹窗文本,SOP创建成功
+sop_job_save_and_execute_failed,text,SOP执行失败时弹窗文本,SOP作业执行失败
+sop_job_save_and_execute_succeed,text,SOP执行成功时弹窗文本,SOP作业开始执行
+sop_job_save_failed,text,SOP保存失败时弹窗文本,SOP作业保存失败
+sop_job_save_succeed,text,SOP保存成功时弹窗文本,SOP作业保存成功
+sop_manage_delete_failed,text,删除SOP时检查是否有正在进行中的作业管理,存在时提示,无法删除选中的SOP
+sop_manage_delete_succeed,text,删除选中的SOP成功时弹窗文本,删除选中的SOP成功
+sop_manage_sop_name,text,SOP列表表头,SOP名称
+sop_manage_title,text,SOP管理标题,SOP管理
+sop_manage_workstation,text,SOP管理表头,所属岗位
+sop_save_failed,text,SOP保存失败时弹窗文本,SOP保存失败
+sop_save_succeed,text,SOP保存成功时弹窗文本,SOP保存成功
+sop_save_tip,text,SOP保存时二次确认弹窗文本,"确定要保存\""{0}\""吗?"
+sop_workstation,text,创建修改SOP作业界面SOP区域选择文本,SOP区域
+start,text,初始化欢迎界面开始按钮文本,开始
+start_detect_key_slot,text,仓位管理检测钥匙仓位开始时加载弹窗文本,开始检测钥匙仓位
+start_detect_lock_slot,text,仓位管理检测挂锁仓位开始时加载弹窗文本,开始检测挂锁仓位
+start_scan_key_mac,text,仓位管理检测钥匙仓位获取钥匙信息时加载弹窗文本,开始扫描钥匙信息
+start_time,text,首页时间范围开始时间,开始时间
+end_time_must_large_then_start_time,text,选择开始时间和结束时间时判断两个时间的大小提示文本,结束时间必须大于开始时间
+start_tip,text,初始化欢迎界面提示文本,根据提示对系统进行初始化
+start_to_send_ticket,text,开始下发工作票加载弹窗文本,开始下发工作票······
+status,text,通用状态文本,状态
+step_confirm_failed,text,作业执行界面步骤确认失败提示文本,步骤确认失败
+take_out_key,text,作业票下发完成之后加载弹窗显示文本,请取出钥匙
+take_out_key_tip,text,作业票下发完成之后加载弹窗显示文本,请从打开的钥匙仓取出钥匙
+take_out_lock_tip,text,去上锁发锁之后加载弹窗显示需要获取的钥匙文本,请从打开的锁仓取出锁,还有{0}把待取出
+take_out_rest_locks,text,去上锁发锁之后再次选择分组提示先取出已经打开卡扣的挂锁文本,请取出剩余开启卡扣的挂锁
+tec_support,text,技术支持文本,温州博士安全用品有限公司
+the_verification_file_not_exists,text,流程模式导入时解压文件的校验文件不存在时提示,校验文件不存在
+ticket_data_error,text,获取工作票数据之后数据转换异常时提示,工作票数据损坏
+ticket_get_failed,text,还钥匙之后钥匙连接失败时提示,作业票获取失败
+ticket_lost,text,获取的工作票已经取消或结束时进行判断,作业票不存在
+time_frame_tv,text,首页时间范围显示文本,时间范围
+todo_header,text,我的待办处理中TAB表头,处理中
+turn_off,text,仓位管理开关按钮文本,关
+turn_on,text,仓位管理开关按钮文本,开
+turn_read,text,仓位管理RFID读取按钮文本,读
+uncolock_complete,text,作业执行界面和我的待办界面解除共锁成功时提示,解除共锁成功
+uncolock_failed,text,作业执行界面和我的待办界面解除共锁失败时提示,解除共锁失败
+unlock,text,流程模式设置,步骤文本,解锁
+unlock_and_restore_switch,text,八大步骤(玛氏)的步骤流程名称,检查使用,拆锁恢复开关
+unzip,text,流程模式导入解压文件时加载弹窗显示,解压中……{0}
+update_card_failed,text,修改卡片信息失败时弹窗提示,更新卡片失败
+update_card_succeed,text,修改卡片信息成功时弹窗提示,更新卡片成功
+update_key_failed,text,修改钥匙信息失败时弹窗提示,更新钥匙失败
+update_key_succeed,text,修改钥匙信息成功时弹窗提示,更新钥匙成功
+update_lock_failed,text,修改挂锁信息失败时弹窗提示,更新挂锁失败
+update_lock_succeed,text,修改挂锁信息成功时弹窗提示,更新挂锁成功
+update_point_failed,text,修改点位信息失败时弹窗提示,保存点位失败
+update_point_succeed,text,修改点位信息成功时弹窗提示,保存点位成功
+update_rfid_token_failed,text,修改RFID信息失败时弹窗提示,更新RFID标签失败
+update_rfid_token_succeed,text,修改RFID信息成功时弹窗提示,更新RFID标签成功
+update_role_failed,text,修改角色信息失败时弹窗提示,角色更新失败
+update_role_succeed,text,修改角色信息成功时弹窗提示,角色更新成功
+update_user_failed,text,修改用户信息失败时弹窗提示,用户更新失败
+update_user_succeed,text,修改用户信息成功时弹窗提示,用户更新成功
+update_workstation_failed,text,修改区域信息失败时弹窗提示,更新区域失败
+update_workstation_succeed,text,修改区域信息成功时弹窗提示,更新区域成功
+user_already_exists,text,添加用户,检测用户信息是否存在,存在时提示,用户已存在
+user_info_title,text,个人信息标题,个人信息
+user_manage_area,text,添加修改用户区域文本,区域
+user_manage_card_code,text,添加修改用户工卡文本,工卡
+user_manage_delete_failed,text,删除用户时检查用户是否在进行中的作业,存在则提示无法删除,无法删除用户
+user_manage_delete_succeed,text,用户删除成功时提示,用户已删除
+user_manage_filter_activate,text,用户筛选状态文本,正常
+user_manage_filter_deactivate,text,用户筛选状态文本,停用
+user_manage_filter_title,text,筛选条件标题,筛选条件
+user_manage_new_user_title,text,新增用户标题,新增用户
+user_manage_role,text,添加修改用户角色文本,角色
+user_manage_title,text,用户管理标题,用户管理
+user_manage_user_detail_title,text,用户详情标题,用户详情
+user_manage_view,text,用户管理列表表头,查看
+user_name,text,添加修改用户用户名文本,登录名
+username,text,通用用户名称文本,用户名称
+username_or_password_error,text,账号密码登录时,账号密码错误提示,账号或密码错误
+username_passowrd_login_success,text,账号密码登录成功提示,账号密码验证通过
+username_password_not_exists,text,账号密码登录时,如果账号不存在提示,账号密码不存在
+username_regex_tip,text,初始化管理员账号,账号不符合要求提示,账号不符合要求
+verify_failed,text,登录失败时提示,验证失败
+view,text,通用查看文本,查看
+wait_header,text,我的待办等待中TAB表头,等待中
+wait_to_colock,text,作业执行界面共锁表头,待共锁({0})
+warn,text,通用警告文本,警告
+welcome_tip,text,初始化欢迎界面文本,您好,欢迎您使用
+workflow_already_exists,text,导入流程模式时,流程模式已存在文本提示,流程模式已存在
+workflow_manage_title,text,流程模式管理标题,流程模式管理
+workflow_mode,text,通用流程模式文本,流程模式
+workflow_mode_manage_delete_succeed,text,删除流程模式成功时提示,删除流程模式成功
+workflow_mode_status_update_failed,text,流程模式状态修改失败时提示,状态修改失败
+workflow_mode_status_update_succeed,text,流程模式状态修改成功时提示,状态修改成功
+workflow_name,text,流程模式表头,流程模式名称
+workflow_setting,text,流程模式设置标题,流程设置
+workflow_step_confirm_member,text,流程模式设置项标题,执行确认人员
+workflow_step_confirm_role,text,流程模式设置项标题,执行确认角色
+workflow_step_confirm_type,text,流程模式设置项标题,执行确认方式
+workflow_step_description,text,流程模式设置项标题,步骤操作说明
+workflow_step_function,text,流程模式设置项标题,步骤功能
+workflow_step_title,text,流程模式设置项标题,步骤标题
+workflow_step_title_short,text,流程模式设置项标题,步骤标题缩写
+workstation_already_exists,text,添加区域是检查同级是否已经存在区域,存在时弹窗文本,区域已存在
+workstation_is_in_bottom,text,区域下移时检测是否已经到底部,到底部提示文本,区域已经在底部
+workstation_is_in_top,text,区域上移时检测是否已经到顶部,到顶部提示文本,区域已经在顶部
+workstation_manage_delete_failed,text,区域删除时检查是否有正在使用的作业,有的话提示无法删除,"无法删除区域\""{0}\"""
+workstation_manage_delete_succeed,text,区域删除成功时提示,"删除区域\""{0}\""成功"
+workstation_manage_new_workstation,text,新增区域标题,新增区域
+workstation_manage_title,text,区域管理标题,区域管理
+workstation_manage_workstation_name,text,新增区域显示文本,区域名称
+you_are_not_locker_tip,text,非上锁人执行上锁/解锁作业时提示文本,您不是上锁人,无法执行此操作
+zone,text,首页区域范围,区域范围
+backup_title,text,备份/还原标题,备份/还原
+backup,text,备份文本,备份
+backup_path,text,备份路径文本,备份路径
+maximum_number_of_backups,text,备份数量上限文本,备份数量上限
+auto_backup,text,自动备份文本,自动备份
+common_enable,text,通用启用文本,启用
+common_disable,text,通用停用文本,停用
+backup_frequency,text,备份频率文本,备份频率
+backup_time,text,备份时间文本,备份时间
+backup_tip,text,备份注意文本,注意:自动备份时必须保证应用处于启动状态。
+backup_now,text,立即备份文本,立即备份
+backup_range,text,备份数量上限提示,范围文本,范围:{0}
+restore,text,还原文本,还原
+common_batch_export,text,通用批量导出文本,批量导出
+common_batch_delete,text,通用批量删除文本,批量删除
+common_export,text,通用导出文本,导出
+MON,text,周一,星期一
+TUE,text,周二,星期二
+WED,text,周三,星期三
+THU,text,周四,星期四
+FRI,text,周五,星期五
+SAT,text,周六,星期六
+SUN,text,周日,星期日
+backup_frequency_every_day,text,备份频率每天,每天
+please_select_backup_frequency,text,选择备份频率提示,请选择备份频率
+maximumNumberOfBackupsNotCorrect,text,备份数量上限不正确提示,请填写正确的备份数量上限
+please_select_time,text,通用选择时间标题,请选择时间
+backup_now_please_wait,text,备份等待文本,正在备份中,请稍等……
+backup_success,text,备份成功文本,备份成功
+backup_failed,text,备份失败文本,备份失败
+delete_backup_file_confirm,text,删除备份提示,是否确认删除该备份,删除后备份无法恢复。
+delete_selected_backup_file_confirm,text,删除选中备份提示,是否确认删除选中备份,删除后备份无法恢复。
+restore_backup_confirm,text,还原备份提醒,还原备份将清除备份日期到当前时间的所有数据,是否确认还原备份?
+restore_backup_success,text,还原备份成功文本,备份还原成功
+export_success,text,通用导出成功,导出成功
+no_backup_data,text,暂无备份数据,暂无备份数据
+loading_backup,text,加载备份中,正在读取备份文件
+max_backup_tip,text,备份达到上限,备份数量已经达到上限,继续备份将移除最老的数据。
+switch,text,切换文本,切换

+ 2 - 1
app/src/main/assets/i18n/zh-CN.csv

@@ -672,4 +672,5 @@ restore_backup_success,text,还原备份成功文本,备份还原成功
 export_success,text,通用导出成功,导出成功
 no_backup_data,text,暂无备份数据,暂无备份数据
 loading_backup,text,加载备份中,正在读取备份文件
-max_backup_tip,text,备份达到上限,备份数量已经达到上限,继续备份将移除最老的数据。
+max_backup_tip,text,备份达到上限,备份数量已经达到上限,继续备份将移除最老的数据。
+switch,text,切换文本,切换

+ 32 - 7
app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt

@@ -22,22 +22,27 @@ import com.grkj.iscs.R
 import com.grkj.iscs.databinding.ActivityLoginBinding
 import com.grkj.iscs.databinding.ItemLoginMethodBinding
 import com.grkj.iscs.features.init.activity.SetRemoteServerActivity
+import com.grkj.iscs.features.login.dialog.ChangeLangDialog
 import com.grkj.iscs.features.login.dialog.LoginDialog
 import com.grkj.iscs.features.login.viewmodel.LoginViewModel
 import com.grkj.iscs.features.main.activity.MainActivity
 import com.grkj.shared.config.Constants
+import com.grkj.shared.utils.extension.toByteArrays
+import com.grkj.shared.utils.extension.toHexStrings
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.shared.utils.i18n.LanguageEntry
+import com.grkj.shared.utils.i18n.LanguageRegistry
+import com.grkj.shared.utils.i18n.LanguageStore
+import com.grkj.shared.utils.i18n.LocaleUtils
 import com.grkj.ui_base.base.BaseActivity
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.event.LoadingEvent
 import com.grkj.ui_base.utils.extension.getAppVersionName
-import com.grkj.shared.utils.extension.toByteArrays
-import com.grkj.shared.utils.extension.toHexStrings
-import com.grkj.shared.utils.i18n.I18nManager
-import com.grkj.ui_base.service.CheckKeyInfoTask
 import com.grkj.ui_base.utils.fingerprint.FingerprintUtil
 import com.sik.sikcore.extension.setDebouncedClickListener
 import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
+import java.util.Locale
 
 /**
  * 登录页
@@ -55,8 +60,22 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
     }
 
     override fun initView() {
-        logger.info("i18n值:${I18nManager.t("loto")}")
-        logger.info("i18n值:${I18nManager.t("back")}")
+        binding.chipLang.text = I18nManager.locale.value.toLanguageTag()
+        binding.chipLang.setOnClickListener {
+            val targetRegion = "US" // 自己决定用什么;也可以是 "CN" / 配置项 / 服务器下发
+            val entries = LanguageRegistry.entriesFromSources(targetRegion)
+
+            ChangeLangDialog.show(
+                canChangeLang = entries,
+                targetRegion = targetRegion
+            ) { selected: LanguageEntry ->
+                // 切换并持久化(MMKV),I18nManager 会热更新
+                LanguageStore.setExplicit(Locale.forLanguageTag(selected.tag))
+
+                // 芯片文本友好化(可选)
+                binding.chipLang.text = I18nManager.locale.value.toLanguageTag()
+            }
+        }
         binding.loginTypeRv.apply {
             if (isLandscape()) {
                 linear(orientation = LinearLayout.HORIZONTAL)
@@ -86,7 +105,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
     private fun BindingAdapter.BindingViewHolder.onLoginMenuBinding(holder: BindingAdapter.BindingViewHolder) {
         val itemBinding = holder.getBinding<ItemLoginMethodBinding>()
         val item = holder.getModel<LoginMenuEntity>()
-        itemBinding.loginTipV.isVisible == item.needTip
+        itemBinding.loginTipV.isVisible = item.needTip
         itemBinding.loginMethodTv.text = item.menuText
         itemBinding.loginMethodIv.setBackgroundResource(item.menuIconId)
         holder.itemView.setDebouncedClickListener {
@@ -96,6 +115,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                     LoginResultEnum.FINGERPRINTER_VERIFY_SUCCESS -> {
                         showToast(CommonUtils.getStr(R.string.fingerprint_login_success).toString())
                         startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+                        finish()
                     }
 
                     LoginResultEnum.FINGERPRINTER_VERIFY_FAILED -> showToast(
@@ -105,6 +125,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                     LoginResultEnum.FACE_VERIFY_SUCCESS -> {
                         showToast(CommonUtils.getStr(R.string.face_login_success).toString())
                         startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+                        finish()
                     }
 
                     LoginResultEnum.FACE_VERIFY_FAILED -> showToast(
@@ -116,6 +137,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                             CommonUtils.getStr(R.string.username_passowrd_login_success).toString()
                         )
                         startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+                        finish()
                     }
 
                     LoginResultEnum.USERNAME_OR_PASSWORD_ERROR -> showToast(
@@ -129,6 +151,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                     LoginResultEnum.JOB_CARD_LOGIN_SUCCESS -> {
                         showToast(CommonUtils.getStr(R.string.job_card_login_success).toString())
                         startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+                        finish()
                     }
 
                     LoginResultEnum.JOB_CARD_LOGIN_FAILED -> showToast(
@@ -208,6 +231,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                     if (isSuccess == LoginResultEnum.FINGERPRINTER_VERIFY_SUCCESS) {
                         showToast(CommonUtils.getStr(R.string.fingerprint_login_success).toString())
                         startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+                        finish()
                     } else {
                         showToast(CommonUtils.getStr(R.string.fingerprint_login_failed).toString())
                     }
@@ -242,6 +266,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                                 CommonUtils.getStr(R.string.job_card_login_success).toString()
                             )
                             startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+                            finish()
                         } else {
                             showToast(
                                 CommonUtils.getStr(R.string.job_card_login_failed).toString()

+ 56 - 0
app/src/main/java/com/grkj/iscs/features/login/dialog/ChangeLangDialog.kt

@@ -0,0 +1,56 @@
+package com.grkj.iscs.features.login.dialog
+
+import android.view.View
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.setup
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogChangeLangBinding
+import com.grkj.iscs.databinding.ItemLangBinding
+import com.grkj.shared.utils.i18n.LanguageEntry
+import com.grkj.shared.utils.i18n.LocaleUtils
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.interfaces.OnBindView
+
+/**
+ * 修改语言弹窗
+ */
+class ChangeLangDialog(
+    private val canChangeLang: List<LanguageEntry>,
+    private val targetRegion: String?,
+    private val onConfirm: (LanguageEntry) -> Unit
+) : OnBindView<CustomDialog>(R.layout.dialog_change_lang) {
+
+    override fun onBind(dialog: CustomDialog, v: View) {
+        val binding = DialogChangeLangBinding.bind(v)
+        binding.listRv.linear().setup {
+            addType<LanguageEntry>(R.layout.item_lang)
+            onBind {
+                val item = getModel<LanguageEntry>()
+                val itemBinding = getBinding<ItemLangBinding>()
+                itemBinding.lang.setOnCheckedChangeListener(null)
+                itemBinding.lang.isChecked = item.isSelected
+                itemBinding.lang.text = LocaleUtils.buildDisplayLabel(item, targetRegion)
+                itemBinding.lang.setOnCheckedChangeListener { _, checked ->
+                    if (!checked) return@setOnCheckedChangeListener   // ← 只响应“被选中”的事件
+                    canChangeLang.forEach { it.isSelected = (it.tag == item.tag) }
+                    notifyDataSetChanged()
+                }
+            }
+        }.models = canChangeLang
+
+        binding.switchBtn.setOnClickListener {
+            canChangeLang.firstOrNull { it.isSelected }?.let(onConfirm)
+            dialog.dismiss()
+        }
+    }
+
+    companion object {
+        fun show(
+            canChangeLang: List<LanguageEntry>,
+            targetRegion: String?,
+            onConfirm: (LanguageEntry) -> Unit
+        ) {
+            CustomDialog.show(ChangeLangDialog(canChangeLang, targetRegion, onConfirm))
+        }
+    }
+}

+ 25 - 8
app/src/main/res/layout-land/activity_login.xml

@@ -49,15 +49,20 @@
             android:orientation="vertical">
 
             <TextView
-                android:id="@+id/version"
+                android:id="@+id/chipLang"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="right"
-                android:layout_marginTop="@dimen/login_version_margin"
-                android:layout_marginRight="@dimen/login_version_margin"
+                android:layout_marginTop="@dimen/common_spacing"
+                android:layout_marginRight="@dimen/common_spacing"
+                android:background="@drawable/common_btn_white_board"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="@dimen/common_spacing_small"
+                android:text="EN"
+                android:textAllCaps="true"
                 android:textColor="@color/white"
-                android:textSize="@dimen/login_version_text_size"
-                tools:text="v1.0" />
+                android:textSize="@dimen/common_text_size" />
+
 
             <com.grkj.ui_base.widget.ShadowTextView
                 android:id="@+id/title_cn"
@@ -65,10 +70,10 @@
                 android:layout_height="wrap_content"
                 android:layout_gravity="center_horizontal"
                 android:layout_marginTop="@dimen/login_main_title_margin_top"
-                app:i18nKey='@{"loto"}'
                 android:textColor="@color/white"
                 android:textSize="@dimen/login_main_title_text_size"
-                android:textStyle="bold" />
+                android:textStyle="bold"
+                app:i18nKey='@{"loto"}' />
 
             <com.grkj.ui_base.widget.ShadowTextView
                 android:id="@+id/title_en"
@@ -127,10 +132,22 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true"
-            android:layout_marginBottom="@dimen/login_tec_support_margin_bottom"
+            android:layout_marginBottom="@dimen/common_spacing"
             android:gravity="center_horizontal"
             android:text="@string/tec_support"
             android:textColor="@color/white"
             android:textSize="@dimen/login_tec_support_text_size" />
+
+        <TextView
+            android:id="@+id/version"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentBottom="true"
+            android:layout_marginRight="@dimen/login_version_margin"
+            android:layout_marginBottom="@dimen/common_spacing"
+            android:textColor="@color/white"
+            android:textSize="@dimen/login_version_text_size"
+            tools:text="v1.0" />
     </RelativeLayout>
 </layout>

+ 37 - 14
app/src/main/res/layout/activity_login.xml

@@ -48,15 +48,20 @@
             android:orientation="vertical">
 
             <TextView
-                android:id="@+id/version"
+                android:id="@+id/chipLang"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="right"
-                android:layout_marginTop="@dimen/login_version_margin"
-                android:layout_marginRight="@dimen/login_version_margin"
+                android:layout_marginTop="@dimen/common_spacing"
+                android:layout_marginRight="@dimen/common_spacing"
+                android:background="@drawable/common_btn_white_board"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="@dimen/common_spacing_small"
+                android:text="EN"
+                android:textAllCaps="true"
                 android:textColor="@color/white"
-                android:textSize="@dimen/login_version_text_size"
-                tools:text="v1.0" />
+                android:textSize="@dimen/common_text_size" />
+
 
             <com.grkj.ui_base.widget.ShadowTextView
                 android:id="@+id/title_cn"
@@ -113,16 +118,34 @@
             </LinearLayout>
         </LinearLayout>
 
-
-        <TextView
-            android:id="@+id/tec_support"
+        <RelativeLayout
+            android:id="@+id/tec_support_layout"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            android:layout_marginBottom="@dimen/login_tec_support_margin_bottom"
-            android:gravity="center_horizontal"
-            android:text="@string/tec_support"
-            android:textColor="@color/white"
-            android:textSize="@dimen/login_tec_support_text_size" />
+            android:layout_marginBottom="@dimen/login_tec_support_margin_bottom">
+
+            <TextView
+                android:id="@+id/tec_support"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:gravity="center_horizontal"
+                android:text="@string/tec_support"
+                android:textColor="@color/white"
+                android:textSize="@dimen/login_tec_support_text_size" />
+
+            <TextView
+                android:id="@+id/version"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_alignParentBottom="true"
+                android:layout_gravity="right"
+                android:layout_marginRight="@dimen/login_version_margin"
+                android:textColor="@color/white"
+                android:textSize="@dimen/login_version_text_size"
+                tools:text="v1.0" />
+        </RelativeLayout>
+
     </RelativeLayout>
 </layout>

+ 30 - 0
app/src/main/res/layout/dialog_change_lang.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_card_login_width"
+        android:layout_height="wrap_content"
+        android:background="@drawable/bg_card_white_r8"
+        android:orientation="vertical"
+        android:padding="@dimen/common_spacing">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/list_rv"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1" />
+
+        <TextView
+            android:id="@+id/switch_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginVertical="@dimen/common_spacing"
+            android:background="@drawable/common_btn_confirm"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:textColor="@color/white"
+            android:textSize="@dimen/common_text_size"
+            app:i18nKey='@{"switch"}' />
+    </LinearLayout>
+</layout>

+ 10 - 0
app/src/main/res/layout/item_lang.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <RadioButton
+        android:id="@+id/lang"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textColor="@color/black"
+        android:textSize="@dimen/common_text_size" />
+</layout>

+ 23 - 5
shared/src/main/java/com/grkj/shared/utils/i18n/I18nManager.kt

@@ -29,11 +29,15 @@ object I18nManager {
     private val _locale = MutableStateFlow(Locale.getDefault())
     val locale: StateFlow<Locale> = _locale
 
+    private val dictVersion = java.util.concurrent.atomic.AtomicLong(0L)
+    private val _dictTick = kotlinx.coroutines.flow.MutableSharedFlow<Long>(replay = 1, extraBufferCapacity = 1)
+    val dictTick: kotlinx.coroutines.flow.SharedFlow<Long> = _dictTick
+
     /** 词典缓存(key -> I18nEntry),原子替换,读路径零锁 */
     private val dictRef = AtomicReference<Map<String, I18nEntry>>(emptyMap())
 
     /** 已注册的数据源(后注册者覆盖先注册者) */
-    private val sources = mutableListOf<I18nSource>()
+    val sources = mutableListOf<I18nSource>()
 
     /** 串行后台执行器:避免并发构建词典产生抖动 */
     private val singleExecutor = Executors.newSingleThreadExecutor { r ->
@@ -94,8 +98,9 @@ object I18nManager {
         currentReloadJob = bg.launch {
             val built = I18nRepository.build(sources, targetLocale)
             dictRef.set(built)
-            // 触发绑定刷新:重复设置相同值也会触发 collectLatest
-            main.launch { _locale.value = targetLocale }
+            // 通知 UI:词典已更新(即使 locale 值没变)
+            val v = dictVersion.incrementAndGet()
+            main.launch { _dictTick.tryEmit(v) }
         }
     }
 
@@ -129,14 +134,24 @@ object I18nManager {
     ): String {
         val entry = dictRef.get()[key] ?: return fallback ?: key
         return when (entry.type) {
-            I18nType.TEXT -> I18nFormatter.format(entry.value ?: fallback ?: key, args, _locale.value)
+            I18nType.TEXT -> I18nFormatter.format(
+                entry.value ?: fallback ?: key,
+                args,
+                _locale.value
+            )
+
             I18nType.PLURAL -> {
                 val n = count ?: 0
                 val form = PluralRules.pick(_locale.value, n)
                 val pattern = entry.plurals[form] ?: entry.plurals["other"]
                 ?: entry.value ?: fallback ?: key
-                I18nFormatter.format(pattern, (args ?: emptyMap()) + mapOf("count" to n), _locale.value)
+                I18nFormatter.format(
+                    pattern,
+                    (args ?: emptyMap()) + mapOf("count" to n),
+                    _locale.value
+                )
             }
+
             I18nType.SELECT -> {
                 val sel = (selectKey ?: "other").lowercase()
                 val pattern = entry.selects[sel] ?: entry.selects["other"]
@@ -145,4 +160,7 @@ object I18nManager {
             }
         }
     }
+
+    @JvmStatic
+    fun availableLocales(): Set<Locale> = LanguageRegistry.availableLocales()
 }

+ 7 - 0
shared/src/main/java/com/grkj/shared/utils/i18n/I18nSource.kt

@@ -8,5 +8,12 @@ import java.util.Locale
  * - 语义:给定 Locale,返回 key->entry 映射(同 key 后覆盖)。
  */
 fun interface I18nSource {
+    /** 读取指定 locale 的词典 */
     fun load(locale: Locale): Map<String, I18nEntry>
+    /** 声明/探测:这个数据源里有哪些语言包(默认无,实现类可覆写) */
+    fun availableLocales(): Set<Locale> = emptySet()
+
+    /** 是否包含某个语言包(默认基于 availableLocales 判断) */
+    fun hasLocale(locale: Locale): Boolean =
+        availableLocales().any { it.toLanguageTag().equals(locale.toLanguageTag(), ignoreCase = true) }
 }

+ 9 - 0
shared/src/main/java/com/grkj/shared/utils/i18n/LanguageEntry.kt

@@ -0,0 +1,9 @@
+package com.grkj.shared.utils.i18n
+
+// 定义一个语言条目的数据结构
+data class LanguageEntry(
+    val tag: String,      // IETF 语言标签,比如 "en-US"
+    val code: String,     // ISO 639-1 语言码,比如 "en"
+    val region: String?,   // ISO 3166-1 地区码,比如 "US"
+    var isSelected: Boolean = false
+)

+ 53 - 0
shared/src/main/java/com/grkj/shared/utils/i18n/LanguageRegistry.kt

@@ -0,0 +1,53 @@
+package com.grkj.shared.utils.i18n
+
+import java.util.Locale
+
+object LanguageRegistry {
+
+    /** 当前生效语言(I18nManager 维护的 StateFlow 快照) */
+    fun currentLocale(): Locale = I18nManager.locale.value
+
+    /** 聚合所有 Source 的可用语言(BCP-47 去重) */
+    fun availableLocales(): Set<Locale> {
+        if (I18nManager.sources.isEmpty()) return emptySet()
+        return I18nManager.sources
+            .flatMap { it.availableLocales() }
+            .map { Locale.forLanguageTag(it.toLanguageTag()) }
+            .toSet()
+    }
+
+    /** 这个 tag 是否真的有语言包(聚合判断) */
+    fun hasLocale(tag: String): Boolean {
+        val t = Locale.forLanguageTag(tag).toLanguageTag()
+        return availableLocales().any { it.toLanguageTag().equals(t, ignoreCase = true) }
+    }
+
+    /**
+     * 🌟【核心】直接从所有 Source 生成 UI 用的候选列表(无任何外部常量依赖)
+     * @param targetRegion 目标地区字符串("US" / "CN"... 用于主显示语言环境)
+     * @param sortByDisplay 是否按展示名排序(默认 true)
+     */
+    fun entriesFromSources(
+        targetRegion: String?,
+        sortByDisplay: Boolean = true
+    ): List<LanguageEntry> {
+        val curTag = currentLocale().toLanguageTag()
+        val list = availableLocales().map { it.toEntry() }.map {
+            it.copy(isSelected = it.tag.equals(curTag, true))
+        }
+        if (!sortByDisplay) return list
+        val dispLocale = LocaleUtils.regionToLocale(targetRegion)
+        return list.sortedBy { entry ->
+            // 用“目标地区”语言环境进行排序,用户看起来更顺
+            val loc = Locale.forLanguageTag(entry.tag)
+            (loc.getDisplayLanguage(dispLocale) + " " + loc.getDisplayCountry(dispLocale)).trim()
+        }
+    }
+
+    /** 从 Locale 生成你的最小数据结构 LanguageEntry */
+    private fun Locale.toEntry(): LanguageEntry = LanguageEntry(
+        tag = this.toLanguageTag(),
+        code = this.language,                 // ISO 639-1
+        region = this.country.ifBlank { null } // ISO 3166-1 或 null
+    )
+}

+ 0 - 10
shared/src/main/java/com/grkj/shared/utils/i18n/LanguageStore.kt

@@ -25,16 +25,6 @@ object LanguageStore {
     // === 对外枚举:可扩展更多语言 ===
     enum class Mode { FOLLOW_SYSTEM, EXPLICIT }
 
-    enum class PresetLocale(val tag: String) {
-        ZH_CN("zh-CN"),
-        EN_US("en-US");
-        // 你要多国语言就在此处追加:JA_JP("ja-JP"), ES_ES("es-ES") 之类
-        fun toLocale(): Locale = Locale.forLanguageTag(tag)
-        companion object {
-            fun fromTag(tag: String?): PresetLocale? = values().firstOrNull { it.tag == tag }
-        }
-    }
-
     private fun kv() = MMKV.mmkvWithID(KV_ID, MMKV.SINGLE_PROCESS_MODE)
 
     /** 读取当前模式 */

+ 114 - 0
shared/src/main/java/com/grkj/shared/utils/i18n/LocaleUtils.kt

@@ -0,0 +1,114 @@
+package com.grkj.shared.utils.i18n
+
+import java.util.Locale
+
+object LocaleUtils {
+
+    /** 目标地区优先的双语显示:主=targetRegion 本地化名;副=当前 UI 语言本地化名 */
+    fun buildDisplayLabel(entry: LanguageEntry, targetRegion: String?): String {
+        val target = Locale.forLanguageTag(entry.tag)
+        val mainLocale = I18nManager.locale.value
+        val uiLocale = regionToLocale(targetRegion)
+        val entryLocale = regionToLocale(entry.region)
+
+        val main = displayName(target, mainLocale)
+        val sub = displayName(target, uiLocale)
+        return if (!entryLocale.toLanguageTag()
+                .equals(regionToLocale(targetRegion).toLanguageTag(), ignoreCase = true)
+        ) main else "$main ($sub)"
+    }
+
+    private fun displayName(target: Locale, inLocale: Locale): String {
+        val lang = target.getDisplayLanguage(inLocale)
+//        val ctry = target.country.takeIf { it.isNotBlank() }?.let {
+//            target.getDisplayCountry(inLocale).takeIf { it.isNotBlank() }?.let { " $it" }.orEmpty()
+//        }.orEmpty()
+//        return (lang + ctry).trim()
+        return lang.trim()
+    }
+
+    /** 仅按地区码给一个“展示用 Locale”;未知则回退 US */
+    fun regionToLocale(region: String?): Locale = when (region?.uppercase()) {
+        // 中文
+        "CN" -> Locale.SIMPLIFIED_CHINESE
+        "TW", "HK", "MO" -> Locale.TRADITIONAL_CHINESE
+
+        // 东亚
+        "JP" -> Locale.JAPAN
+        "KR" -> Locale.KOREA
+
+        // 西欧
+        "DE" -> Locale.GERMANY
+        "FR" -> Locale.FRANCE
+        "ES" -> Locale("es", "ES")
+        "IT" -> Locale.ITALY
+        "PT" -> Locale("pt", "PT") // 葡萄牙(欧洲)
+        "NL" -> Locale("nl", "NL") // 荷兰
+        "BE" -> Locale("nl", "BE") // 比利时(荷兰语优先)
+
+        // 北美
+        "US", null, "" -> Locale.US
+        "CA" -> Locale.CANADA
+        "MX" -> Locale("es", "MX")
+
+        // 南美
+        "BR" -> Locale("pt", "BR")
+        "AR" -> Locale("es", "AR")
+        "CL" -> Locale("es", "CL")
+        "CO" -> Locale("es", "CO")
+        "PE" -> Locale("es", "PE")
+
+        // 东南亚
+        "SG" -> Locale("en", "SG")
+        "MY" -> Locale("ms", "MY")
+        "TH" -> Locale("th", "TH")
+        "VN" -> Locale("vi", "VN")
+        "PH" -> Locale("en", "PH")
+        "ID" -> Locale("id", "ID")
+
+        // 南亚
+        "IN" -> Locale("en", "IN")
+        "PK" -> Locale("ur", "PK")
+        "BD" -> Locale("bn", "BD")
+        "LK" -> Locale("si", "LK")
+        "NP" -> Locale("ne", "NP")
+
+        // 中东 & 北非(阿拉伯语)
+        "AE" -> Locale("ar", "AE")
+        "SA" -> Locale("ar", "SA")
+        "EG" -> Locale("ar", "EG")
+        "MA" -> Locale("ar", "MA")
+        "DZ" -> Locale("ar", "DZ")
+        "TN" -> Locale("ar", "TN")
+        "KW" -> Locale("ar", "KW")
+        "QA" -> Locale("ar", "QA")
+        "OM" -> Locale("ar", "OM")
+        "BH" -> Locale("ar", "BH")
+        "JO" -> Locale("ar", "JO")
+        "LB" -> Locale("ar", "LB")
+        "IQ" -> Locale("ar", "IQ")
+        "YE" -> Locale("ar", "YE")
+
+        // 东欧 & 俄语区
+        "RU" -> Locale("ru", "RU")
+        "UA" -> Locale("uk", "UA")
+        "BY" -> Locale("be", "BY")
+        "PL" -> Locale("pl", "PL")
+        "CZ" -> Locale("cs", "CZ")
+        "SK" -> Locale("sk", "SK")
+        "HU" -> Locale("hu", "HU")
+
+        // 其他常用
+        "TR" -> Locale("tr", "TR")
+        "GR" -> Locale("el", "GR")
+        "IL" -> Locale("he", "IL")
+        "IR" -> Locale("fa", "IR")
+        "ZA" -> Locale("en", "ZA")
+        "AU" -> Locale("en", "AU")
+        "NZ" -> Locale("en", "NZ")
+        "GB", "UK" -> Locale.UK
+
+        else -> Locale(I18nManager.locale.value.language, region.uppercase())
+    }
+
+}

+ 19 - 8
shared/src/main/java/com/grkj/shared/utils/i18n/databinding/I18nBindingAdapters.kt

@@ -19,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap
 private val i18nJobs = WeakHashMap<View, Job>()
 private val listenerAdded = WeakHashMap<View, Boolean>()
 
-private fun View.observeLocale(onChange: () -> Unit) {
+private fun View.observeI18n(onChange: () -> Unit) {
     val owner = findViewTreeLifecycleOwner()
     if (owner == null) {
         if (listenerAdded[this] != true) {
@@ -27,7 +27,7 @@ private fun View.observeLocale(onChange: () -> Unit) {
                 override fun onViewAttachedToWindow(v: View) {
                     v.removeOnAttachStateChangeListener(this)
                     listenerAdded.remove(v)
-                    v.observeLocale(onChange)
+                    v.observeI18n(onChange)
                 }
                 override fun onViewDetachedFromWindow(v: View) { /* no-op */ }
             })
@@ -38,15 +38,26 @@ private fun View.observeLocale(onChange: () -> Unit) {
 
     i18nJobs.remove(this)?.cancel()
     val job = owner.lifecycleScope.launch {
+        // 1) 先收 locale(切换语言立刻刷新一次)
         I18nManager.locale.collectLatest {
-            try {
-                onChange()
-            } catch (t: Throwable) {
-                Log.e("I18n/Binding", "apply on locale change failed", t)
+            runCatching { onChange() }.onFailure {
+                Log.e("I18n/Binding", "apply on locale change failed", it)
             }
         }
     }
-    i18nJobs[this] = job
+    // 2) 再收 dictTick(词典重建完成再次刷新一次)
+    val job2 = owner.lifecycleScope.launch {
+        I18nManager.dictTick.collectLatest {
+            runCatching { onChange() }.onFailure {
+                Log.e("I18n/Binding", "apply on dict tick failed", it)
+            }
+        }
+    }
+    i18nJobs[this] = Job().also { parent ->
+        parent.invokeOnCompletion {
+            job.cancel(); job2.cancel()
+        }
+    }
 
     if (listenerAdded[this] != true) {
         addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
@@ -214,5 +225,5 @@ fun bindI18n(
 
     // 初次应用 & 订阅语言变化
     applyOnce()
-    view.observeLocale { applyOnce() }
+    view.observeI18n { applyOnce() }
 }

+ 57 - 0
shared/src/main/java/com/grkj/shared/utils/i18n/source/AssetsCsvSource.kt

@@ -25,6 +25,10 @@ class AssetsCsvSource(
     private val mergedFileName: String = "all.csv"
 ) : I18nSource {
     private val logger: Logger = LoggerFactory.getLogger(this::class.java)
+
+    /** —— 新增:可用语言缓存(assets 基本不变,懒加载一次即可) */
+    private val cachedLocales: Set<Locale> by lazy { scanAvailableLocales() }
+
     override fun load(locale: Locale): Map<String, I18nEntry> {
         return if (!mergedMode) {
             // 依次尝试:zh-CN.csv -> zh.csv -> zh_CN.csv -> ZH-CN.csv(大小写/下划线容错)
@@ -53,6 +57,59 @@ class AssetsCsvSource(
         }
     }
 
+    /** —— 新增:列出我有哪些语言包 */
+    override fun availableLocales(): Set<Locale> = cachedLocales
+
+    /** —— 可选覆写:更快判断(避免逐个打开) */
+    override fun hasLocale(locale: Locale): Boolean {
+        val tag = locale.toLanguageTag()
+        return cachedLocales.any { it.toLanguageTag().equals(tag, true) }
+    }
+
+    /** 扫描 assets 下现有语言(单表=文件名;合表=CSV 表头) */
+    private fun scanAvailableLocales(): Set<Locale> {
+        return if (!mergedMode) {
+            // 扫描文件名:xx.csv / xx-YY.csv / xx_YY.csv
+            val files = runCatching { context.assets.list(dir)?.toList().orEmpty() }.getOrDefault(emptyList())
+            files.asSequence()
+                .filter { it.endsWith(".csv", true) || it.endsWith(".json", true) }
+                .map { it.substringBeforeLast('.') }
+                .mapNotNull { nameToLocaleOrNull(it) }
+                .toSet()
+        } else {
+            // 嗅探合表第一行表头:key, zh-CN, en-US, ...
+            val path = "$dir/$mergedFileName"
+            runCatching {
+                context.assets.open(path).use { input ->
+                    BufferedReader(InputStreamReader(input, StandardCharsets.UTF_8)).use { br ->
+                        val header = br.readLine()?.trim().orEmpty()
+                        parseLocalesFromHeader(header)
+                    }
+                }
+            }.getOrElse { emptySet() }
+        }
+    }
+
+    /** 解析 CSV 表头中的语言列名(忽略首列 key) */
+    private fun parseLocalesFromHeader(header: String): Set<Locale> {
+        if (header.isBlank()) return emptySet()
+        val cols = header.split(',').map { it.trim().trim('"', '\'') }
+        return cols.drop(1) // 跳过 key
+            .mapNotNull { col -> nameToLocaleOrNull(col) }
+            .toSet()
+    }
+
+    /** 将 "zh-CN" / "zh_CN" / "en" / "EN-us" 这类名字转 Locale,非法则 null */
+    private fun nameToLocaleOrNull(name: String): Locale? {
+        val norm = name.replace('_', '-')
+        val tag = when {
+            // 简单 lang 或 lang-REGION 或 lang-Script-Region
+            Regex("""^[A-Za-z]{2,3}(-[A-Za-z]{4})?(-[A-Za-z]{2}|\-\d{3})?$""").matches(norm) -> norm
+            else -> return null
+        }
+        return try { Locale.forLanguageTag(tag) } catch (_: Throwable) { null }
+    }
+
     /**
      * 基于 BCP47 解析:忽略脚本/变体,仅提取 language + region,并生成最可能的文件名:
      * zh-CN.csv → zh_CN.csv → zh.csv

+ 76 - 4
shared/src/main/java/com/grkj/shared/utils/i18n/source/FileCsvSource.kt

@@ -24,6 +24,15 @@ class FileCsvSource(
     private val mergedFileName: String = "all.csv"
 ) : I18nSource {
 
+    /** —— 新增:扫描时机和缓存(文件会变,别用 lazy 永久缓存) */
+    @Volatile
+    private var lastScanMillis: Long = 0L
+    @Volatile
+    private var lastLocales: Set<Locale> = emptySet()
+    private val SCAN_INTERVAL_MS = 1500L // 1.5s 保护,避免频繁 IO
+
+    private fun dir(): File = File(context.filesDir, dirName).apply { mkdirs() }
+
     override fun load(locale: Locale): Map<String, I18nEntry> {
         val dir = File(context.filesDir, dirName).apply { mkdirs() }
         return if (!mergedMode) {
@@ -54,6 +63,65 @@ class FileCsvSource(
         }
     }
 
+    /** —— 新增:目录扫描 + 合表头嗅探 */
+    override fun availableLocales(): Set<Locale> {
+        val now = System.currentTimeMillis()
+        if (now - lastScanMillis <= SCAN_INTERVAL_MS) return lastLocales
+
+        val root = dir()
+        val locales = if (!mergedMode) {
+            root.listFiles().orEmpty().asSequence()
+                .filter {
+                    it.isFile && (it.name.endsWith(".csv", true) || it.name.endsWith(
+                        ".json",
+                        true
+                    ))
+                }
+                .map { it.nameWithoutExtension }
+                .mapNotNull { nameToLocaleOrNull(it) }
+                .toSet()
+        } else {
+            val f = File(root, mergedFileName)
+            if (!f.exists()) emptySet()
+            else runCatching {
+                FileInputStream(f).use { fis ->
+                    BufferedReader(InputStreamReader(fis, StandardCharsets.UTF_8)).use { br ->
+                        val header = br.readLine()?.trim().orEmpty()
+                        parseLocalesFromHeader(header)
+                    }
+                }
+            }.getOrElse { emptySet() }
+        }
+
+        lastLocales = locales
+        lastScanMillis = now
+        return locales
+    }
+
+    override fun hasLocale(locale: Locale): Boolean {
+        val tag = locale.toLanguageTag()
+        return availableLocales().any { it.toLanguageTag().equals(tag, true) }
+    }
+
+    private fun parseLocalesFromHeader(header: String): Set<Locale> {
+        if (header.isBlank()) return emptySet()
+        val cols = header.split(',').map { it.trim().trim('"', '\'') }
+        return cols.drop(1).mapNotNull { nameToLocaleOrNull(it) }.toSet()
+    }
+
+    private fun nameToLocaleOrNull(name: String): Locale? {
+        val norm = name.replace('_', '-')
+        val tag = when {
+            Regex("""^[A-Za-z]{2,3}(-[A-Za-z]{4})?(-[A-Za-z]{2}|\-\d{3})?$""").matches(norm) -> norm
+            else -> return null
+        }
+        return try {
+            Locale.forLanguageTag(tag)
+        } catch (_: Throwable) {
+            null
+        }
+    }
+
     /**
      * 基于 BCP47 解析:忽略脚本/变体,仅提取 language + region,并生成最可能的文件名:
      * zh-CN.csv → zh_CN.csv → zh.csv
@@ -105,21 +173,25 @@ class FileCsvSource(
         // 优先用标准 API
         var lang = locale.language?.lowercase(Locale.ROOT).orEmpty()
         var script = locale.script?.let {
-            if (it.isNotBlank()) it.substring(0,1).uppercase(Locale.ROOT) + it.substring(1).lowercase(Locale.ROOT)
+            if (it.isNotBlank()) it.substring(0, 1).uppercase(Locale.ROOT) + it.substring(1)
+                .lowercase(Locale.ROOT)
             else null
         }
         var region = locale.country?.uppercase(Locale.ROOT).orEmpty().ifBlank { null }
 
         // 有些厂商定制 Locale 会把脚本/地区塞进 variant,额外兜底一次
         if (script.isNullOrEmpty() || region == null) {
-            val parts = locale.toLanguageTag().replace('_','-').split('-').filter { it.isNotBlank() }
+            val parts =
+                locale.toLanguageTag().replace('_', '-').split('-').filter { it.isNotBlank() }
             if (parts.isNotEmpty()) {
                 lang = parts.first().lowercase(Locale.ROOT)
                 // BCP47:脚本 = 4 个字母,首字母大写;地区=2字母或3数字
                 val sc = parts.firstOrNull { it.length == 4 && it.all { ch -> ch.isLetter() } }
-                val rg = parts.firstOrNull { (it.length == 2 && it.all { ch -> ch.isLetter() }) || (it.length == 3 && it.all { ch -> ch.isDigit() }) }
+                val rg =
+                    parts.firstOrNull { (it.length == 2 && it.all { ch -> ch.isLetter() }) || (it.length == 3 && it.all { ch -> ch.isDigit() }) }
                 if (script.isNullOrEmpty() && sc != null) {
-                    script = sc.substring(0,1).uppercase(Locale.ROOT) + sc.substring(1).lowercase(Locale.ROOT)
+                    script = sc.substring(0, 1).uppercase(Locale.ROOT) + sc.substring(1)
+                        .lowercase(Locale.ROOT)
                 }
                 if (region == null && rg != null) region = rg.uppercase(Locale.ROOT)
             }