Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,63 @@
"rules_update_failed": "Failed to update rule. Please try again.",
"rules_invalid_rule_id": "Invalid rule ID",
"rules_rule_not_found": "Rule not found",
"rules_new_page_title": "New Rules - CropWatch",
"rules_new_create_page_title": "Create Rule Template - CropWatch",
"rules_new_edit_page_title": "Edit Rule Template - CropWatch",
"rules_new_configured_templates": "Rule Templates",
"rules_new_create_template": "Create Rule Template",
"rules_new_edit_template": "Edit Rule Template",
"rules_new_delete_template": "Delete Rule Template",
"rules_new_template_id": "Template ID",
"rules_new_description_placeholder": "Optional notes for the team",
"rules_new_active_rule": "Active rule",
"rules_new_active_rule_description": "Active templates are evaluated by the alert handler.",
"rules_new_status": "Status",
"rules_new_active": "Active",
"rules_new_inactive": "Inactive",
"rules_new_assigned_devices": "Assigned Devices",
"rules_new_triggered_devices": "Triggered Devices",
"rules_new_actions": "Actions",
"rules_new_action_type": "Action Type",
"rules_new_no_assignments": "No assigned devices",
"rules_new_summary_more": "{summary}, +{count} more",
"rules_new_step_template": "1. Template",
"rules_new_step_template_subtitle": "Name the template and control whether it is evaluated.",
"rules_new_step_assignments": "2. Device Assignments",
"rules_new_step_assignments_subtitle": "Choose the devices that should use this template.",
"rules_new_assignment_number": "Assignment {count}",
"rules_new_add_device_assignment": "Add Device Assignment",
"rules_new_duplicate_devices": "Each device can only be assigned once.",
"rules_new_step_actions": "4. Alert Actions",
"rules_new_step_actions_subtitle": "Choose what the alert handler receives when the template matches.",
"rules_new_action_number": "Action {count}",
"rules_new_add_action": "Add Action",
"rules_new_test_rule": "Test Rule",
"rules_new_test_rule_subtitle": "Paste a decoded payload to preview trigger and reset behavior.",
"rules_new_test_payload": "Decoded Payload JSON",
"rules_new_test_button": "Test Rule",
"rules_new_test_matches": "This payload triggers the rule.",
"rules_new_test_no_match": "This payload does not trigger the rule.",
"rules_new_test_can_reset": "This payload reaches the reset point.",
"rules_new_test_missing_criteria": "Add at least one condition before testing.",
"rules_new_test_parse_failed": "Enter valid JSON before testing.",
"rules_new_test_payload_object_required": "The payload must be a JSON object.",
"rules_new_test_actual_value": "Actual value",
"rules_new_test_trigger_passed": "Trigger passed",
"rules_new_test_trigger_failed": "Trigger failed",
"rules_new_test_reset_passed": "Reset passed",
"rules_new_test_reset_failed": "Reset failed",
"rules_new_test_reason_unsupported_operator": "Unsupported operator.",
"rules_new_test_reason_non_comparable_value": "The payload value cannot be compared as a number.",
"rules_new_test_reason_reset_not_reached": "The reset value has not been reached.",
"rules_new_created_success": "Rule template \"{name}\" created successfully.",
"rules_new_updated_success": "Rule template \"{name}\" updated successfully.",
"rules_new_deleted_success": "Rule template \"{name}\" deleted successfully.",
"rules_new_load_failed": "Unable to load rule templates.",
"rules_new_save_failed": "Unable to save this rule template.",
"rules_new_delete_failed": "Unable to delete this rule template.",
"rules_new_invalid_template_id": "Invalid rule template ID.",
"rules_new_rule_template_not_found": "Rule template not found.",
"reports_page_title": "Reports - CropWatch",
"reports_weekly_reports": "Weekly Reports",
"reports_for_device": "For Device",
Expand Down Expand Up @@ -430,6 +487,15 @@
"reports_create_alert_points_description_greater_than": "> {value} {unit}",
"reports_create_alert_points_description_greater_than_or_equal": ">= {value} {unit}",
"reports_create_alert_points_overlap_error": "Overlaps with {labels}. Each alert must cover a unique part of the number line.",
"reports_create_alert_points_reset_never_happens_preview_single": "1 reset will never happen because every reset value is covered by another alert.",
"reports_create_alert_points_reset_never_happens_preview_multiple": "{count} resets will never happen because every reset value is covered by another alert.",
"reports_create_alert_points_reset_never_happens_error": "Reset will never happen because every reset value is covered by another alert.",
"reports_create_alert_points_reset_waiting_for_value": "Reset waiting for a value.",
"reports_create_alert_points_reset_description_not_equals": "Reset != {value} {unit}",
"reports_create_alert_points_reset_description_less_than": "Reset < {value} {unit}",
"reports_create_alert_points_reset_description_less_than_or_equal": "Reset <= {value} {unit}",
"reports_create_alert_points_reset_description_greater_than": "Reset > {value} {unit}",
"reports_create_alert_points_reset_description_greater_than_or_equal": "Reset >= {value} {unit}",
"reports_create_alert_user_label": "User ID (optional)",
"reports_create_alert_user_placeholder": "Leave blank unless needed",
"reports_create_advanced_alert_metadata": "System fields",
Expand Down Expand Up @@ -791,6 +857,9 @@
"display_relay_history": "Relay History",
"display_relay_one": "Relay 1",
"display_relay_two": "Relay 2",
"display_relay_state_on": "ON",
"display_relay_state_off": "OFF",
"display_relay_state_unknown": "—",
"display_save_note": "Save Note",
"display_searchable_sortable": "Searchable, sortable",
"display_searchable_sortable_data": "Searchable, sortable data",
Expand Down Expand Up @@ -911,5 +980,11 @@
"gateways_public": "Public",
"gateways_online": "Online",
"gateways_offline": "Offline",
"gateways_private": "Private"
"gateways_private": "Private",
"rule_action_forever_acknowledge_title": "Confirm Permanent Device State",
"rule_action_forever_acknowledge_message": "This action will set the device to the chosen state and leave it there indefinitely. The device will not return to its previous state on its own.",
"rule_action_forever_acknowledge_responsibility": "You are responsible for periodically verifying that this device remains in the correct state. CropWatch will not change it back for you.",
"rule_action_forever_acknowledge_option": "I understand and accept responsibility for monitoring this device.",
"rule_action_forever_acknowledge_confirm": "I Understand, Continue",
"rule_action_revert_on_reset": "Revert relay state when the rule resets"
}
77 changes: 76 additions & 1 deletion messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,63 @@
"rules_update_failed": "ルールの更新に失敗しました。もう一度お試しください。",
"rules_invalid_rule_id": "無効なルール ID です",
"rules_rule_not_found": "ルールが見つかりません",
"rules_new_page_title": "新ルール - CropWatch",
"rules_new_create_page_title": "ルールテンプレートを作成 - CropWatch",
"rules_new_edit_page_title": "ルールテンプレートを編集 - CropWatch",
"rules_new_configured_templates": "ルールテンプレート",
"rules_new_create_template": "ルールテンプレートを作成",
"rules_new_edit_template": "ルールテンプレートを編集",
"rules_new_delete_template": "ルールテンプレートを削除",
"rules_new_template_id": "テンプレート ID",
"rules_new_description_placeholder": "チーム向けのメモ (任意)",
"rules_new_active_rule": "ルールを有効化",
"rules_new_active_rule_description": "有効なテンプレートはアラート処理で評価されます。",
"rules_new_status": "ステータス",
"rules_new_active": "有効",
"rules_new_inactive": "無効",
"rules_new_assigned_devices": "割り当てデバイス",
"rules_new_triggered_devices": "発火中デバイス",
"rules_new_actions": "アクション",
"rules_new_action_type": "アクションタイプ",
"rules_new_no_assignments": "割り当てデバイスなし",
"rules_new_summary_more": "{summary}、他 {count} 件",
"rules_new_step_template": "1. テンプレート",
"rules_new_step_template_subtitle": "テンプレート名と評価の有効/無効を設定します。",
"rules_new_step_assignments": "2. デバイス割り当て",
"rules_new_step_assignments_subtitle": "このテンプレートを使用するデバイスを選択します。",
"rules_new_assignment_number": "割り当て {count}",
"rules_new_add_device_assignment": "デバイス割り当てを追加",
"rules_new_duplicate_devices": "同じデバイスは一度だけ割り当てできます。",
"rules_new_step_actions": "4. アラートアクション",
"rules_new_step_actions_subtitle": "テンプレートが一致したときにアラート処理へ渡す内容を選択します。",
"rules_new_action_number": "アクション {count}",
"rules_new_add_action": "アクションを追加",
"rules_new_test_rule": "ルールをテスト",
"rules_new_test_rule_subtitle": "デコード済みペイロードを貼り付けて、発火とリセットの挙動を確認します。",
"rules_new_test_payload": "デコード済みペイロード JSON",
"rules_new_test_button": "ルールをテスト",
"rules_new_test_matches": "このペイロードはルールを発火します。",
"rules_new_test_no_match": "このペイロードはルールを発火しません。",
"rules_new_test_can_reset": "このペイロードはリセット条件に到達しています。",
"rules_new_test_missing_criteria": "テスト前に条件を 1 つ以上追加してください。",
"rules_new_test_parse_failed": "テスト前に有効な JSON を入力してください。",
"rules_new_test_payload_object_required": "ペイロードは JSON オブジェクトである必要があります。",
"rules_new_test_actual_value": "実測値",
"rules_new_test_trigger_passed": "発火条件一致",
"rules_new_test_trigger_failed": "発火条件不一致",
"rules_new_test_reset_passed": "リセット条件一致",
"rules_new_test_reset_failed": "リセット条件不一致",
"rules_new_test_reason_unsupported_operator": "未対応の演算子です。",
"rules_new_test_reason_non_comparable_value": "ペイロードの値を数値として比較できません。",
"rules_new_test_reason_reset_not_reached": "リセット値に到達していません。",
"rules_new_created_success": "ルールテンプレート「{name}」を作成しました。",
"rules_new_updated_success": "ルールテンプレート「{name}」を更新しました。",
"rules_new_deleted_success": "ルールテンプレート「{name}」を削除しました。",
"rules_new_load_failed": "ルールテンプレートを読み込めません。",
"rules_new_save_failed": "このルールテンプレートを保存できません。",
"rules_new_delete_failed": "このルールテンプレートを削除できません。",
"rules_new_invalid_template_id": "無効なルールテンプレート ID です。",
"rules_new_rule_template_not_found": "ルールテンプレートが見つかりません。",
"reports_page_title": "レポート - CropWatch",
"reports_weekly_reports": "週次レポート",
"reports_for_device": "対象デバイス",
Expand Down Expand Up @@ -430,6 +487,15 @@
"reports_create_alert_points_description_greater_than": "> {value} {unit}",
"reports_create_alert_points_description_greater_than_or_equal": ">= {value} {unit}",
"reports_create_alert_points_overlap_error": "{labels} と範囲が重複しています。各条件が数直線上で重ならないように値を調整してください。",
"reports_create_alert_points_reset_never_happens_preview_single": "1 件のリセットは、他の条件によってリセット値が完全に覆われているため発生しません。",
"reports_create_alert_points_reset_never_happens_preview_multiple": "{count} 件のリセットは、他の条件によってリセット値が完全に覆われているため発生しません。",
"reports_create_alert_points_reset_never_happens_error": "他の条件によってリセット値が完全に覆われているため、リセットは発生しません。",
"reports_create_alert_points_reset_waiting_for_value": "リセット値の入力待ちです。",
"reports_create_alert_points_reset_description_not_equals": "リセット != {value} {unit}",
"reports_create_alert_points_reset_description_less_than": "リセット < {value} {unit}",
"reports_create_alert_points_reset_description_less_than_or_equal": "リセット <= {value} {unit}",
"reports_create_alert_points_reset_description_greater_than": "リセット > {value} {unit}",
"reports_create_alert_points_reset_description_greater_than_or_equal": "リセット >= {value} {unit}",
"reports_create_alert_user_label": "ユーザー ID(任意)",
"reports_create_alert_user_placeholder": "必要な場合のみ入力",
"reports_create_advanced_alert_metadata": "システム項目",
Expand Down Expand Up @@ -791,6 +857,9 @@
"display_relay_history": "リレー履歴",
"display_relay_one": "リレー 1",
"display_relay_two": "リレー 2",
"display_relay_state_on": "ON",
"display_relay_state_off": "OFF",
"display_relay_state_unknown": "—",
"display_save_note": "メモを保存",
"display_searchable_sortable": "検索・並べ替え可能",
"display_searchable_sortable_data": "検索・並べ替え可能なデータ",
Expand Down Expand Up @@ -911,5 +980,11 @@
"gateways_public": "公開",
"gateways_online": "オンライン",
"gateways_offline": "オフライン",
"gateways_private": "非公開"
"gateways_private": "非公開",
"rule_action_forever_acknowledge_title": "デバイスの永続的な状態を確認",
"rule_action_forever_acknowledge_message": "このアクションを実行すると、デバイスは指定された状態に設定され、その状態のまま無期限に維持されます。デバイスが自動的に元の状態に戻ることはありません。",
"rule_action_forever_acknowledge_responsibility": "このデバイスが正しい状態にあることを定期的に確認する責任は、お客様にあります。CropWatchが自動で元に戻すことはありません。",
"rule_action_forever_acknowledge_option": "このデバイスの監視責任を理解し、引き受けます。",
"rule_action_forever_acknowledge_confirm": "理解しました、続行",
"rule_action_revert_on_reset": "ルールがリセットされた時にリレーの状態を元に戻す"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
"packageManager": "pnpm@10.18.2+sha512.9fb969fa749b3ade6035e0f109f0b8a60b5d08a1a87fdf72e337da90dcc93336e2280ca4e44f2358a649b83c17959e9993e777c2080879f3801e6f0d999ad3dd",
"dependencies": {
"@cropwatchdevelopment/cwui": "0.1.84",
"@cropwatchdevelopment/cwui": "0.1.91",
"@supabase/supabase-js": "^2.98.0"
}
}
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 25 additions & 24 deletions src/lib/api/api-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,46 @@
* - `{ payload: { message: ... } }` (nested)
* - A plain string
*
* Call this anywhere you need to turn a caught API error into a display string:
* Call this anywhere you need to turn a caught API error or error payload into a display string:
*
* ```ts
* import { readApiErrorMessage } from '$lib/api/api-error';
* import { ApiServiceError } from '$lib/api/api.service';
*
* try {
* await api.doSomething();
* } catch (err) {
* const payload = err instanceof ApiServiceError ? err.payload : err;
* return fail(500, { error: readApiErrorMessage(payload, m.generic_error()) });
* return fail(500, { error: readApiErrorMessage(err, m.generic_error()) });
* }
* ```
*/
export function readApiErrorMessage(payload: unknown, fallback: string): string {
if (payload && typeof payload === 'object') {
const record = payload as Record<string, unknown>;
const message = record.message;

if (typeof message === 'string' && message.trim().length > 0) {
return message.trim();
}
return readApiErrorMessageValue(payload) ?? fallback;
}

if (Array.isArray(message)) {
const combined = message
.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)
.join(', ');
if (combined.length > 0) return combined;
}
function readApiErrorMessageValue(payload: unknown): string | null {
if (typeof payload === 'string') {
const trimmed = payload.trim();
return trimmed.length > 0 ? trimmed : null;
}

// Recurse into a nested payload envelope.
if (record.payload !== undefined) {
return readApiErrorMessage(record.payload, fallback);
}
if (Array.isArray(payload)) {
const combined = payload
.map(readApiErrorMessageValue)
.filter((entry): entry is string => !!entry)
.join(', ');
return combined.length > 0 ? combined : null;
}

if (typeof payload === 'string' && payload.trim().length > 0) {
return payload.trim();
if (!payload || typeof payload !== 'object') {
return null;
}

return fallback;
const record = payload as Record<string, unknown>;

return (
readApiErrorMessageValue(record.payload) ??
readApiErrorMessageValue(record.message) ??
readApiErrorMessageValue(record.error) ??
readApiErrorMessageValue(record.data)
);
}
Loading
Loading